modal 1.6.4.dev10__tar.gz → 1.6.4.dev11__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.6.4.dev10 → modal-1.6.4.dev11}/PKG-INFO +1 -1
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/__init__.py +11 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_function_variants.py +1 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_functions.py +39 -4
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_image.py +21 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_type_manager.py +7 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/name_utils.py +8 -1
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/profile.py +40 -29
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/token.py +83 -14
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/client.py +1 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/client.pyi +2 -2
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/config.py +1 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/functions.pyi +13 -6
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/token_flow.py +32 -2
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/token_flow.pyi +2 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/api_grpc.py +16 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/api_pb2.py +1791 -1645
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/api_pb2.pyi +310 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/api_pb2_grpc.py +33 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/api_pb2_grpc.pyi +10 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/modal_api_grpc.py +1 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_version/__init__.py +1 -1
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/pyproject.toml +1 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/LICENSE +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/README.md +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/__main__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_audit_logs/__init__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_audit_logs/_rpcs.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_audit_logs/count.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_audit_logs/list.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_billing.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_cluster.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_cluster.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_container_entrypoint.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_environments.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_grpc_client.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_ipython.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_load_context.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_location.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_logs.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_logs_manager.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_logs_manager.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_metrics.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_metrics.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_object.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_outbound_policy.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_outbound_policy.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_output/__init__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_output/manager.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_output/pty.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_output/rich.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_output/status.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_partial_function.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_resolver.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_resources.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/__init__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/asgi.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/execution_context.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/telemetry.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_serialization.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_server.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_server.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_supports_logs.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_traceback.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_tunnel.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_tunnel.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/__init__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/app_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/async_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/blob_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/browser_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/curl_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/deprecation.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/display_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/docker_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/function_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/git_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/hash_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/http_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/idle_countdown.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/logger.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/mount_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/oauth.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/package_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/proxy_support.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/shell_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_utils/time_utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_vendor/__init__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_vendor/tblib.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_vendor/version.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_watcher.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/_workspace.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/app.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/app.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/billing.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/billing.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/2023.12.txt +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/2024.04.txt +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/2024.10.txt +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/2025.06.txt +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/2026.10.txt +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/README.md +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/builder/base-images.json +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/call_graph.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/__init__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/_download.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/_help.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/_logs.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/_stats.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/_traceback.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/app.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/billing.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/changelog.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/config.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/container.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/curl.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/dashboard.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/dict.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/endpoint.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/entry_point.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/environment.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/function.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/image.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/import_refs.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/logo.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/network_file_system.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/queues.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/run.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/secret.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/selector.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/server.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/shell.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/skills.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/utils.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/volume.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cli/workspace.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cls.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cls.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cluster.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/cluster.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/container_process.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/container_process.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/dict.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/dict.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/environments.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/environments.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/exception.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/__init__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/_flash_container_entry.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/flash.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/flash.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/flash_server.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/flash_server.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/experimental/ipython.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/file_pattern_matcher.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/functions.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/image.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/image.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/io_streams.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/io_streams.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/mount.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/mount.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/network_file_system.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/network_file_system.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/object.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/object.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/output.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/parallel_map.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/parallel_map.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/partial_function.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/partial_function.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/proxy.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/proxy.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/py.typed +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/queue.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/queue.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/retries.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/runner.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/runner.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/running_app.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/__init__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_common.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_filesystem.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_impl_persistent.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_impl_v1.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_impl_v2.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_sandbox.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_sidecar.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_snapshot.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/_task_command_router_client.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/sandbox.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/sandbox/sandbox.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/schedule.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/scheduler_placement.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/secret.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/secret.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/server.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/server.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/serving.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/serving.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/stream_type.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/types.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/volume.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/volume.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/workspace.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal/workspace.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal.egg-info/requires.txt +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal.egg-info/top_level.txt +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/__init__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/mdmd/types.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_docs/reference_intro.md +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/__init__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/py.typed +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/modal_version/__main__.py +0 -0
- {modal-1.6.4.dev10 → modal-1.6.4.dev11}/setup.cfg +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
|
+
import os
|
|
2
3
|
import sys
|
|
3
4
|
|
|
4
5
|
if sys.version_info[:2] < (3, 10):
|
|
@@ -59,6 +60,16 @@ except Exception:
|
|
|
59
60
|
print()
|
|
60
61
|
raise
|
|
61
62
|
|
|
63
|
+
if sys.version_info[:2] == (3, 10) and os.environ.get("MODAL_IS_REMOTE") != "1":
|
|
64
|
+
from ._utils.deprecation import deprecation_warning
|
|
65
|
+
|
|
66
|
+
deprecation_warning(
|
|
67
|
+
(2026, 11, 4),
|
|
68
|
+
"Python 3.10 is EOL and Modal will not support it in version 1.7.0. "
|
|
69
|
+
"Please upgrade your local Python to 3.11 or later.",
|
|
70
|
+
show_source=False,
|
|
71
|
+
)
|
|
72
|
+
|
|
62
73
|
|
|
63
74
|
__all__ = [
|
|
64
75
|
"__version__",
|
|
@@ -408,6 +408,7 @@ def _make_function_variant(
|
|
|
408
408
|
if not base_function._is_hydrated:
|
|
409
409
|
await base_function.hydrate(load_context.client)
|
|
410
410
|
|
|
411
|
+
base_function._check_not_observability_only()
|
|
411
412
|
assert base_function._client and base_function._client._stub
|
|
412
413
|
|
|
413
414
|
if parameter_schema is None:
|
|
@@ -119,6 +119,15 @@ if TYPE_CHECKING:
|
|
|
119
119
|
import modal.functions
|
|
120
120
|
|
|
121
121
|
MAX_INTERNAL_FAILURE_COUNT = 8
|
|
122
|
+
_OBSERVABILITY_ONLY_APIS = (
|
|
123
|
+
".logs",
|
|
124
|
+
".metrics",
|
|
125
|
+
".info()",
|
|
126
|
+
".stats()",
|
|
127
|
+
".get_current_stats()",
|
|
128
|
+
".get_web_url()",
|
|
129
|
+
".autoscaler.settings()",
|
|
130
|
+
)
|
|
122
131
|
TERMINAL_STATUSES = (
|
|
123
132
|
api_pb2.GenericResult.GENERIC_STATUS_SUCCESS,
|
|
124
133
|
api_pb2.GenericResult.GENERIC_STATUS_TERMINATED,
|
|
@@ -651,6 +660,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
651
660
|
_base_function: "_Function | None" = None
|
|
652
661
|
_app_id: str | None = None
|
|
653
662
|
_function_info: FunctionInfo | None = None
|
|
663
|
+
_is_observability_only: bool = False
|
|
654
664
|
|
|
655
665
|
async def _get_log_query_data(self) -> _LogQueryData:
|
|
656
666
|
await self.hydrate()
|
|
@@ -1398,6 +1408,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1398
1408
|
|
|
1399
1409
|
"""
|
|
1400
1410
|
|
|
1411
|
+
self._check_not_observability_only()
|
|
1401
1412
|
deprecation_warning(
|
|
1402
1413
|
(2026, 10, 16),
|
|
1403
1414
|
"`Function.update_autoscaler()` is deprecated and will be removed in a future version "
|
|
@@ -1485,16 +1496,15 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1485
1496
|
# refine the error message
|
|
1486
1497
|
msg = f"Lookup failed for {called_from} '{function_id}': {exc}."
|
|
1487
1498
|
raise NotFoundError(msg) from None
|
|
1488
|
-
if called_from == "Function" and
|
|
1489
|
-
|
|
1490
|
-
raise InvalidError(f"{function_id} is a Server. Use \n ``modal.Server.from_id('{function_id}')``")
|
|
1491
|
-
raise InvalidError(f"{function_id} is a Cls and cannot be loaded with Function.from_id().")
|
|
1499
|
+
if called_from == "Function" and response.function.is_server:
|
|
1500
|
+
raise InvalidError(f"{function_id} is a Server. Use \n ``modal.Server.from_id('{function_id}')``")
|
|
1492
1501
|
if called_from == "Server" and not response.function.is_server:
|
|
1493
1502
|
if response.function.is_class:
|
|
1494
1503
|
raise InvalidError(f"{function_id} is a Cls and cannot be loaded with Server.from_id().")
|
|
1495
1504
|
raise InvalidError(f"{function_id} is a Function. Use \n ``modal.Function.from_id('{function_id}')``")
|
|
1496
1505
|
self._hydrate(function_id, load_context.client, response.handle_metadata)
|
|
1497
1506
|
self._function_info = FunctionInfo._from_function_proto(response.function)
|
|
1507
|
+
self._is_observability_only = called_from == "Function" and response.function.is_class
|
|
1498
1508
|
|
|
1499
1509
|
rep = f"modal.Function.from_id('{function_id}')"
|
|
1500
1510
|
return cls._from_loader(
|
|
@@ -1514,6 +1524,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1514
1524
|
object with metadata from Modal servers until the first
|
|
1515
1525
|
time it is actually used.
|
|
1516
1526
|
|
|
1527
|
+
If the ID belongs to a Cls, the Function only supports the following APIs:
|
|
1528
|
+
`.logs`, `.metrics`, `.info()`, `.stats()`, `.get_current_stats()`,
|
|
1529
|
+
`.get_web_url()` and `.autoscaler.settings()`. Use `modal.Cls.from_name(...)`
|
|
1530
|
+
to invoke all other methods.
|
|
1531
|
+
|
|
1517
1532
|
Args:
|
|
1518
1533
|
function_id: ID of the function.
|
|
1519
1534
|
client: Modal client to use; defaults to `Client.from_env()` when omitted.
|
|
@@ -1744,6 +1759,18 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1744
1759
|
+ f"or call it locally: {self._function_name}.local()"
|
|
1745
1760
|
)
|
|
1746
1761
|
|
|
1762
|
+
def _check_not_observability_only(self):
|
|
1763
|
+
if self._is_observability_only:
|
|
1764
|
+
assert self._function_name
|
|
1765
|
+
class_name = self._function_name.removesuffix(".*")
|
|
1766
|
+
apis = [f"`{name}`" for name in _OBSERVABILITY_ONLY_APIS]
|
|
1767
|
+
allowed_apis = ", ".join(apis[:-1]) + " and " + apis[-1]
|
|
1768
|
+
raise InvalidError(
|
|
1769
|
+
f"`{self.object_id}` belongs to the Cls `{class_name}`. A Function loaded with "
|
|
1770
|
+
f"`Function.from_id` only supports {allowed_apis}. "
|
|
1771
|
+
"Use `modal.Cls.from_name(...)` to invoke all other methods."
|
|
1772
|
+
)
|
|
1773
|
+
|
|
1747
1774
|
@live_method
|
|
1748
1775
|
async def get_web_url(self) -> str | None:
|
|
1749
1776
|
"""URL for addressing a Web Function via HTTP.
|
|
@@ -1779,6 +1806,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1779
1806
|
config_method_name: str,
|
|
1780
1807
|
) -> "_Function[P, ReturnType, OriginalReturnType]":
|
|
1781
1808
|
base_function = self._base_function or self
|
|
1809
|
+
base_function._check_not_observability_only()
|
|
1782
1810
|
combined_options = self._options.merge_options(new_options)
|
|
1783
1811
|
|
|
1784
1812
|
# For "True Functions" (i.e. not the Function inside a Cls), we will use
|
|
@@ -1941,6 +1969,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1941
1969
|
_SynchronizedQueue is used instead of asyncio.Queue so that the main thread can put
|
|
1942
1970
|
items in the queue safely.
|
|
1943
1971
|
"""
|
|
1972
|
+
self._check_not_observability_only()
|
|
1944
1973
|
self._check_no_web_url("map")
|
|
1945
1974
|
if self._is_generator:
|
|
1946
1975
|
raise InvalidError("A generator function cannot be called with `.map(...)`.")
|
|
@@ -1978,6 +2007,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1978
2007
|
|
|
1979
2008
|
@live_method
|
|
1980
2009
|
async def _spawn_map(self, input_queue: _SynchronizedQueue) -> "_FunctionCall[ReturnType]":
|
|
2010
|
+
self._check_not_observability_only()
|
|
1981
2011
|
self._check_no_web_url("spawn_map")
|
|
1982
2012
|
if self._is_generator:
|
|
1983
2013
|
raise InvalidError("A generator function cannot be called with `.spawn_map(...)`.")
|
|
@@ -1995,6 +2025,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1995
2025
|
return fc
|
|
1996
2026
|
|
|
1997
2027
|
async def _call_function(self, args, kwargs) -> ReturnType:
|
|
2028
|
+
self._check_not_observability_only()
|
|
1998
2029
|
invocation: _Invocation | _InputPlaneInvocation
|
|
1999
2030
|
if self._input_plane_url:
|
|
2000
2031
|
invocation = await _InputPlaneInvocation.create(
|
|
@@ -2024,6 +2055,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
2024
2055
|
function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType",
|
|
2025
2056
|
from_spawn_map: bool = False,
|
|
2026
2057
|
) -> _Invocation:
|
|
2058
|
+
self._check_not_observability_only()
|
|
2027
2059
|
return await _Invocation.create(
|
|
2028
2060
|
self,
|
|
2029
2061
|
args,
|
|
@@ -2037,6 +2069,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
2037
2069
|
@live_method_gen
|
|
2038
2070
|
@synchronizer.no_input_translation
|
|
2039
2071
|
async def _call_generator(self, args, kwargs):
|
|
2072
|
+
self._check_not_observability_only()
|
|
2040
2073
|
invocation: _Invocation | _InputPlaneInvocation
|
|
2041
2074
|
if self._input_plane_url:
|
|
2042
2075
|
invocation = await _InputPlaneInvocation.create(
|
|
@@ -2093,6 +2126,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
2093
2126
|
Values produced by the remote generator.
|
|
2094
2127
|
"""
|
|
2095
2128
|
# TODO: Generics/TypeVars
|
|
2129
|
+
self._check_not_observability_only()
|
|
2096
2130
|
self._check_no_web_url("remote_gen")
|
|
2097
2131
|
|
|
2098
2132
|
if not self._is_generator:
|
|
@@ -2669,6 +2703,7 @@ class _FunctionAutoscalerManager:
|
|
|
2669
2703
|
if not self._function._is_hydrated:
|
|
2670
2704
|
await self._function.hydrate()
|
|
2671
2705
|
|
|
2706
|
+
self._function._check_not_observability_only()
|
|
2672
2707
|
settings: api_pb2.AutoscalerSettings = api_pb2.AutoscalerSettings()
|
|
2673
2708
|
|
|
2674
2709
|
if min_containers != UNSET_PARAMETER:
|
|
@@ -37,6 +37,7 @@ from ._serialization import get_preferred_payload_format, serialize
|
|
|
37
37
|
from ._supports_logs import _ImageLogQueryData
|
|
38
38
|
from ._utils.async_utils import TaskContext, deprecate_aio_usage, synchronizer
|
|
39
39
|
from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
|
|
40
|
+
from ._utils.deprecation import deprecation_warning
|
|
40
41
|
from ._utils.docker_utils import (
|
|
41
42
|
extract_copy_command_patterns,
|
|
42
43
|
find_dockerignore_file,
|
|
@@ -87,6 +88,23 @@ SUPPORTED_PYTHON_SERIES: dict[ImageBuilderVersion, list[str]] = {
|
|
|
87
88
|
"2023.12": ["3.10", "3.11", "3.12"],
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
|
|
92
|
+
def _uses_python_310(metadata: api_pb2.ImageMetadata | None) -> bool:
|
|
93
|
+
return metadata is not None and bool(re.match(r"Python 3\.10\.", metadata.python_version_info))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _warn_if_python_310(image_id: str, metadata: api_pb2.ImageMetadata | None) -> None:
|
|
97
|
+
if _uses_python_310(metadata):
|
|
98
|
+
deprecation_warning(
|
|
99
|
+
(2026, 11, 4),
|
|
100
|
+
f"Image {image_id} uses Python 3.10, which is EOL. Modal 1.7.0 and later will not run or deploy apps "
|
|
101
|
+
"that use Images with Python 3.10; apps already deployed with older clients keep working. "
|
|
102
|
+
"Please update the Image to use Python 3.11 or later "
|
|
103
|
+
"(e.g. via `python_version=` or `add_python=`).",
|
|
104
|
+
show_source=False,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
90
108
|
LOCAL_REQUIREMENTS_DIR = Path(__file__).parent / "builder"
|
|
91
109
|
CONTAINER_REQUIREMENTS_PATH = "/modal_requirements.txt"
|
|
92
110
|
|
|
@@ -877,6 +895,8 @@ class _Image(_Object, type_prefix="im"):
|
|
|
877
895
|
raise ImageBuildError("Unknown status %s!" % result.status, image_id)
|
|
878
896
|
|
|
879
897
|
self._hydrate(image_id, load_context.client, metadata)
|
|
898
|
+
if not any(_uses_python_310(base._metadata) for base in base_images.values()):
|
|
899
|
+
_warn_if_python_310(image_id, metadata)
|
|
880
900
|
local_mounts: set[_Mount] = set()
|
|
881
901
|
for base in base_images.values():
|
|
882
902
|
local_mounts |= base._serve_mounts
|
|
@@ -1098,6 +1118,7 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1098
1118
|
async def _load(self: _Image, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None):
|
|
1099
1119
|
resp = await load_context.client._stub.ImageFromId(api_pb2.ImageFromIdRequest(image_id=image_id))
|
|
1100
1120
|
self._hydrate(resp.image_id, load_context.client, resp.metadata)
|
|
1121
|
+
_warn_if_python_310(resp.image_id, resp.metadata)
|
|
1101
1122
|
|
|
1102
1123
|
rep = f"Image.from_id({image_id!r})"
|
|
1103
1124
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Copyright Modal Labs 2025
|
|
2
|
+
import types
|
|
2
3
|
import typing
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
@@ -186,6 +187,12 @@ class SchemaRegistry:
|
|
|
186
187
|
|
|
187
188
|
def get_proto_generic_type(self, declared_type: type):
|
|
188
189
|
if origin := typing_extensions.get_origin(declared_type):
|
|
190
|
+
if origin in (typing.Union, types.UnionType):
|
|
191
|
+
args = [arg for arg in typing_extensions.get_args(declared_type) if arg is not type(None)]
|
|
192
|
+
if len(args) == 1:
|
|
193
|
+
return self.get_proto_generic_type(args[0])
|
|
194
|
+
return unknown_type_schema(declared_type)
|
|
195
|
+
|
|
189
196
|
base_type = origin
|
|
190
197
|
else:
|
|
191
198
|
base_type = declared_type
|
|
@@ -16,6 +16,13 @@ def replace_invalid_subdomain_chars(label: str) -> str:
|
|
|
16
16
|
return re.sub("[^a-z0-9-]", "-", label.lower())
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
app_id_regex = re.compile("^ap-[a-zA-Z0-9]{22}$")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def is_app_id(identifier: str) -> bool:
|
|
23
|
+
return app_id_regex.match(identifier) is not None
|
|
24
|
+
|
|
25
|
+
|
|
19
26
|
def is_valid_object_name(name: str) -> bool:
|
|
20
27
|
return (
|
|
21
28
|
# Limit object name length
|
|
@@ -23,7 +30,7 @@ def is_valid_object_name(name: str) -> bool:
|
|
|
23
30
|
# Limit character set
|
|
24
31
|
and re.match("^[a-zA-Z0-9-_.]+$", name) is not None
|
|
25
32
|
# Avoid collisions with App IDs
|
|
26
|
-
and
|
|
33
|
+
and not is_app_id(name)
|
|
27
34
|
)
|
|
28
35
|
|
|
29
36
|
|
|
@@ -5,11 +5,12 @@ import os
|
|
|
5
5
|
from contextlib import suppress
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
|
-
from rich.
|
|
9
|
-
from rich.
|
|
8
|
+
from rich.box import SIMPLE_HEAD
|
|
9
|
+
from rich.text import Text
|
|
10
10
|
|
|
11
11
|
from modal import config as config_module
|
|
12
12
|
from modal._utils.async_utils import synchronizer
|
|
13
|
+
from modal.cli.utils import display_table
|
|
13
14
|
from modal.config import (
|
|
14
15
|
Config,
|
|
15
16
|
_lookup_workspace,
|
|
@@ -28,7 +29,7 @@ profile_cli = ModalGroup(name="profile", help="Switch between Modal profiles.")
|
|
|
28
29
|
@profile_cli.command("list", help="Show all Modal profiles and highlight the active one.")
|
|
29
30
|
@click.option("--json", is_flag=True, default=False)
|
|
30
31
|
@synchronizer.create_blocking
|
|
31
|
-
async def list_(json: bool
|
|
32
|
+
async def list_(json: bool = False):
|
|
32
33
|
config = Config()
|
|
33
34
|
profiles = config_profiles()
|
|
34
35
|
lookup_coros = [
|
|
@@ -44,22 +45,6 @@ async def list_(json: bool | None = False):
|
|
|
44
45
|
]
|
|
45
46
|
responses = await asyncio.gather(*lookup_coros, return_exceptions=True)
|
|
46
47
|
|
|
47
|
-
rows = []
|
|
48
|
-
for profile, resp in zip(profiles, responses):
|
|
49
|
-
active = profile == config_module._profile
|
|
50
|
-
if isinstance(resp, AuthError):
|
|
51
|
-
workspace = "Unknown (authentication failure)"
|
|
52
|
-
elif isinstance(resp, TimeoutError):
|
|
53
|
-
workspace = "Unknown (timed out)"
|
|
54
|
-
elif isinstance(resp, Exception):
|
|
55
|
-
# Catch-all for other exceptions, like incorrect server url
|
|
56
|
-
workspace = "Unknown (profile misconfigured)"
|
|
57
|
-
else:
|
|
58
|
-
assert not isinstance(resp, BaseException)
|
|
59
|
-
workspace = resp.username
|
|
60
|
-
content = ["•" if active else "", profile, workspace]
|
|
61
|
-
rows.append((active, content))
|
|
62
|
-
|
|
63
48
|
env_based_workspace: str | None = None
|
|
64
49
|
credential_env_vars = (
|
|
65
50
|
"MODAL_TOKEN_ID",
|
|
@@ -87,18 +72,44 @@ async def list_(json: bool | None = False):
|
|
|
87
72
|
|
|
88
73
|
output = OutputManager.get()
|
|
89
74
|
highlight = "bold green" if env_based_workspace is None else "yellow"
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
json_data.append({"name": content[1], "workspace": content[2], "active": active})
|
|
94
|
-
output.print(JSON.from_data(json_data))
|
|
75
|
+
|
|
76
|
+
if not json:
|
|
77
|
+
table_columns = [" Profile", "Workspace"]
|
|
95
78
|
else:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
79
|
+
table_columns = ["name", "workspace", "active"]
|
|
80
|
+
|
|
81
|
+
table_rows: list[list[str | Text | bool]] = []
|
|
82
|
+
for profile, resp in zip(profiles, responses):
|
|
83
|
+
active = profile == config_module._profile
|
|
84
|
+
|
|
85
|
+
if isinstance(resp, AuthError):
|
|
86
|
+
workspace = "Unknown (authentication failure)"
|
|
87
|
+
elif isinstance(resp, TimeoutError):
|
|
88
|
+
workspace = "Unknown (timed out)"
|
|
89
|
+
elif isinstance(resp, Exception):
|
|
90
|
+
# Catch-all for other exceptions, like incorrect server url
|
|
91
|
+
workspace = "Unknown (profile misconfigured)"
|
|
92
|
+
else:
|
|
93
|
+
assert not isinstance(resp, BaseException)
|
|
94
|
+
workspace = resp.username
|
|
95
|
+
|
|
96
|
+
if json:
|
|
97
|
+
table_rows.append([profile, workspace, active])
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
rendered_profile = f"• {profile}" if active else f" {profile}"
|
|
101
|
+
style = highlight if active else "dim"
|
|
102
|
+
|
|
103
|
+
table_rows.append(
|
|
104
|
+
[
|
|
105
|
+
Text(rendered_profile, style=style),
|
|
106
|
+
Text(workspace, style=style),
|
|
107
|
+
]
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
display_table(table_columns, table_rows, json=json, table_box=SIMPLE_HEAD)
|
|
100
111
|
|
|
101
|
-
if env_based_workspace is not None:
|
|
112
|
+
if not json and env_based_workspace is not None:
|
|
102
113
|
output.print(
|
|
103
114
|
f"[yellow]Using [bold]{env_based_workspace}[/bold] workspace based on environment variables[/yellow]"
|
|
104
115
|
)
|
|
@@ -70,18 +70,7 @@ async def info():
|
|
|
70
70
|
output.print(f"[bold]Expires at:[/bold] [white]{expires_dt.strftime('%Y-%m-%d %H:%M:%S %Z')}[/white]")
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
@click.option("--activate/--no-activate", default=True, help=_ACTIVATE_HELP)
|
|
75
|
-
@click.option("--verify/--no-verify", default=True, help=_VERIFY_HELP)
|
|
76
|
-
@click.option(
|
|
77
|
-
"--expires-in",
|
|
78
|
-
default=None,
|
|
79
|
-
help="Lifetime of the new token, e.g. 12h, 7d, or 90d. Defaults to the workspace's maximum allowed lifetime.",
|
|
80
|
-
)
|
|
81
|
-
@click.option("--source", default=None, hidden=True)
|
|
82
|
-
@synchronizer.create_blocking
|
|
83
|
-
async def new(activate: bool, verify: bool, expires_in: str | None, source: str | None):
|
|
84
|
-
"""Create a new token by using an authenticated web session."""
|
|
73
|
+
def _validate_expires_in(expires_in: str | None) -> int | None:
|
|
85
74
|
expires_in_seconds: int | None = None
|
|
86
75
|
if expires_in is not None:
|
|
87
76
|
try:
|
|
@@ -94,7 +83,74 @@ async def new(activate: bool, verify: bool, expires_in: str | None, source: str
|
|
|
94
83
|
raise click.BadParameter("Duration must be positive.", param_hint="--expires-in")
|
|
95
84
|
if expires_in_seconds > 2**32 - 1:
|
|
96
85
|
raise click.BadParameter("Duration is too large.", param_hint="--expires-in")
|
|
97
|
-
|
|
86
|
+
|
|
87
|
+
return expires_in_seconds
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@token_cli.command("new")
|
|
91
|
+
@click.option("--activate/--no-activate", default=True, help=_ACTIVATE_HELP)
|
|
92
|
+
@click.option("--verify/--no-verify", default=True, help=_VERIFY_HELP)
|
|
93
|
+
@click.option(
|
|
94
|
+
"--expires-in",
|
|
95
|
+
default=None,
|
|
96
|
+
help="Lifetime of the new token, e.g. 12h, 7d, or 90d. Defaults to the workspace's maximum allowed lifetime.",
|
|
97
|
+
)
|
|
98
|
+
@click.option(
|
|
99
|
+
"--delete-existing",
|
|
100
|
+
default=False,
|
|
101
|
+
is_flag=True,
|
|
102
|
+
help="In addition to creating a new token, delete the existing token in the active profile",
|
|
103
|
+
)
|
|
104
|
+
@click.option("--source", default=None, hidden=True)
|
|
105
|
+
@synchronizer.create_blocking
|
|
106
|
+
async def new(
|
|
107
|
+
activate: bool,
|
|
108
|
+
verify: bool,
|
|
109
|
+
expires_in: str | None,
|
|
110
|
+
delete_existing: bool,
|
|
111
|
+
source: str | None,
|
|
112
|
+
):
|
|
113
|
+
"""Create a new token by using an authenticated web session."""
|
|
114
|
+
|
|
115
|
+
await _new_token(
|
|
116
|
+
activate=activate,
|
|
117
|
+
verify=verify,
|
|
118
|
+
source=source,
|
|
119
|
+
expires_in_seconds=_validate_expires_in(expires_in),
|
|
120
|
+
delete_existing=delete_existing,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@token_cli.command("rotate")
|
|
125
|
+
@click.option(
|
|
126
|
+
"--expires-in",
|
|
127
|
+
default=None,
|
|
128
|
+
help="Lifetime of the new token, e.g. 12h, 7d, or 90d. Defaults to the workspace's maximum allowed lifetime.",
|
|
129
|
+
)
|
|
130
|
+
@synchronizer.create_blocking
|
|
131
|
+
async def rotate(expires_in: str | None):
|
|
132
|
+
"""Create a new token in a web session, then delete the current token.
|
|
133
|
+
|
|
134
|
+
Note that this is equivalent to `modal token new --delete-existing --no-activate`. You may also
|
|
135
|
+
optionally provide a custom expiration window with `--expires-in`. If not provided, the token's
|
|
136
|
+
TTL will fall back to the workspace default.
|
|
137
|
+
|
|
138
|
+
Examples:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
modal token rotate
|
|
142
|
+
|
|
143
|
+
# Set a custom TTL of 1 week
|
|
144
|
+
modal token rotate --expires-in 7d
|
|
145
|
+
```
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
await _new_token(
|
|
149
|
+
activate=False,
|
|
150
|
+
verify=True,
|
|
151
|
+
expires_in_seconds=_validate_expires_in(expires_in),
|
|
152
|
+
delete_existing=True,
|
|
153
|
+
)
|
|
98
154
|
|
|
99
155
|
|
|
100
156
|
@token_cli.command("set")
|
|
@@ -102,12 +158,19 @@ async def new(activate: bool, verify: bool, expires_in: str | None, source: str
|
|
|
102
158
|
@click.option("--token-secret", default=None, help="Account token secret.")
|
|
103
159
|
@click.option("--activate/--no-activate", default=True, help=_ACTIVATE_HELP)
|
|
104
160
|
@click.option("--verify/--no-verify", default=True, help=_VERIFY_HELP)
|
|
161
|
+
@click.option(
|
|
162
|
+
"--delete-existing",
|
|
163
|
+
default=False,
|
|
164
|
+
is_flag=True,
|
|
165
|
+
help="In addition to setting a new token, delete the existing token in the active profile",
|
|
166
|
+
)
|
|
105
167
|
@synchronizer.create_blocking
|
|
106
168
|
async def set(
|
|
107
169
|
token_id: str | None,
|
|
108
170
|
token_secret: str | None,
|
|
109
171
|
activate: bool,
|
|
110
172
|
verify: bool,
|
|
173
|
+
delete_existing: bool,
|
|
111
174
|
):
|
|
112
175
|
"""Set account credentials for connecting to Modal.
|
|
113
176
|
|
|
@@ -117,4 +180,10 @@ async def set(
|
|
|
117
180
|
token_id = getpass.getpass("Token ID:")
|
|
118
181
|
if token_secret is None:
|
|
119
182
|
token_secret = getpass.getpass("Token secret:")
|
|
120
|
-
await _set_token(
|
|
183
|
+
await _set_token(
|
|
184
|
+
token_id,
|
|
185
|
+
token_secret,
|
|
186
|
+
activate=activate,
|
|
187
|
+
verify=verify,
|
|
188
|
+
delete_existing=delete_existing,
|
|
189
|
+
)
|
|
@@ -360,6 +360,7 @@ class _Client:
|
|
|
360
360
|
"Run `modal profile list` to see available profiles, or "
|
|
361
361
|
f"`modal token new --profile {profile}` to configure it."
|
|
362
362
|
)
|
|
363
|
+
|
|
363
364
|
raise AuthError(
|
|
364
365
|
"Token missing. Could not authenticate client."
|
|
365
366
|
" If you have token credentials, see modal.com/docs/sdk/py/latest/config for setup help."
|
|
@@ -87,7 +87,7 @@ class _Client:
|
|
|
87
87
|
server_url: str,
|
|
88
88
|
client_type: int,
|
|
89
89
|
credentials: typing.Optional[tuple[str, str]],
|
|
90
|
-
version: str = "1.6.4.
|
|
90
|
+
version: str = "1.6.4.dev11",
|
|
91
91
|
*,
|
|
92
92
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
93
93
|
):
|
|
@@ -304,7 +304,7 @@ class Client:
|
|
|
304
304
|
server_url: str,
|
|
305
305
|
client_type: int,
|
|
306
306
|
credentials: typing.Optional[tuple[str, str]],
|
|
307
|
-
version: str = "1.6.4.
|
|
307
|
+
version: str = "1.6.4.dev11",
|
|
308
308
|
*,
|
|
309
309
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
310
310
|
):
|
|
@@ -521,6 +521,7 @@ def _store_user_config(
|
|
|
521
521
|
"""Internal method, used by the CLI to set tokens."""
|
|
522
522
|
if profile is None:
|
|
523
523
|
profile = _profile
|
|
524
|
+
|
|
524
525
|
user_config = _read_user_config()
|
|
525
526
|
user_config.setdefault(profile, {}).update(**new_settings)
|
|
526
527
|
if active_profile is not None:
|
|
@@ -65,6 +65,7 @@ class Function(
|
|
|
65
65
|
_base_function: typing.Optional[Function]
|
|
66
66
|
_app_id: typing.Optional[str]
|
|
67
67
|
_function_info: typing.Optional[modal.types.FunctionInfo]
|
|
68
|
+
_is_observability_only: bool
|
|
68
69
|
|
|
69
70
|
def __init__(self, *args, **kwargs):
|
|
70
71
|
"""mdmd:hidden"""
|
|
@@ -319,6 +320,11 @@ class Function(
|
|
|
319
320
|
object with metadata from Modal servers until the first
|
|
320
321
|
time it is actually used.
|
|
321
322
|
|
|
323
|
+
If the ID belongs to a Cls, the Function only supports the following APIs:
|
|
324
|
+
`.logs`, `.metrics`, `.info()`, `.stats()`, `.get_current_stats()`,
|
|
325
|
+
`.get_web_url()` and `.autoscaler.settings()`. Use `modal.Cls.from_name(...)`
|
|
326
|
+
to invoke all other methods.
|
|
327
|
+
|
|
322
328
|
Args:
|
|
323
329
|
function_id: ID of the function.
|
|
324
330
|
client: Modal client to use; defaults to `Client.from_env()` when omitted.
|
|
@@ -428,6 +434,7 @@ class Function(
|
|
|
428
434
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
429
435
|
def _get_metadata(self): ...
|
|
430
436
|
def _check_no_web_url(self, fn_name: str): ...
|
|
437
|
+
def _check_not_observability_only(self): ...
|
|
431
438
|
|
|
432
439
|
class __get_web_url_spec(typing_extensions.Protocol):
|
|
433
440
|
def __call__(self, /) -> typing.Optional[str]:
|
|
@@ -619,7 +626,7 @@ class Function(
|
|
|
619
626
|
|
|
620
627
|
_call_generator: ___call_generator_spec
|
|
621
628
|
|
|
622
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
629
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
623
630
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
624
631
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result.
|
|
625
632
|
|
|
@@ -644,7 +651,7 @@ class Function(
|
|
|
644
651
|
"""
|
|
645
652
|
...
|
|
646
653
|
|
|
647
|
-
remote: __remote_spec[modal._functions.
|
|
654
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
|
|
648
655
|
|
|
649
656
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
650
657
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -694,7 +701,7 @@ class Function(
|
|
|
694
701
|
"""
|
|
695
702
|
...
|
|
696
703
|
|
|
697
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
704
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
698
705
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
699
706
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
700
707
|
|
|
@@ -727,7 +734,7 @@ class Function(
|
|
|
727
734
|
"""
|
|
728
735
|
...
|
|
729
736
|
|
|
730
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
737
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
731
738
|
|
|
732
739
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
733
740
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -735,7 +742,7 @@ class Function(
|
|
|
735
742
|
|
|
736
743
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
737
744
|
|
|
738
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
745
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
739
746
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
740
747
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
741
748
|
|
|
@@ -768,7 +775,7 @@ class Function(
|
|
|
768
775
|
"""
|
|
769
776
|
...
|
|
770
777
|
|
|
771
|
-
spawn: __spawn_spec[modal._functions.
|
|
778
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
772
779
|
|
|
773
780
|
@property
|
|
774
781
|
def _raw_f_(self) -> collections.abc.Callable[..., typing.Any]: ...
|