modal 1.5.5.dev4__tar.gz → 1.5.5.dev6__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.5.dev4 → modal-1.5.5.dev6}/PKG-INFO +1 -1
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_environments.py +4 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/app.py +1 -28
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/app.pyi +0 -26
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/_help.py +143 -4
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/app.py +51 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/config.py +3 -2
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/entry_point.py +9 -4
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/environment.py +18 -2
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/profile.py +4 -4
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/run.py +28 -1
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/token.py +3 -11
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/client.py +8 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/client.pyi +2 -2
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/config.py +7 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/environments.pyi +2 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/experimental/__init__.py +2 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/sandbox.py +6 -1
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/sandbox.pyi +15 -3
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/api_grpc.py +16 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/api_pb2.py +905 -905
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/api_pb2.pyi +8 -2
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/api_pb2_grpc.py +33 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/api_pb2_grpc.pyi +10 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/modal_api_grpc.py +1 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_version/__init__.py +1 -1
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/LICENSE +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/README.md +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/__init__.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/__main__.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_billing.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_clustered_functions.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_function_variants.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_functions.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_grpc_client.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_image.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_ipython.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_load_context.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_location.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_logs.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_logs_manager.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_logs_manager.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_object.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_output/__init__.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_output/manager.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_output/pty.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_output/rich.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_output/status.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_partial_function.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_resolver.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_resources.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_serialization.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_server.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_supports_logs.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_traceback.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_tunnel.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_tunnel.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_type_manager.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/__init__.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/display_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/logger.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_vendor/version.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_watcher.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/_workspace.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/billing.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/billing.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/builder/README.md +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/builder/base-images.json +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/call_graph.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/__init__.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/_download.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/_traceback.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/billing.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/changelog.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/cluster.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/container.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/curl.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/dashboard.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/dict.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/endpoint.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/image.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/import_refs.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/launch.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/logo.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/queues.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/secret.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/selector.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/shell.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/skills.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/utils.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/volume.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cli/workspace.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cls.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/cls.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/container_process.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/container_process.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/dict.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/dict.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/environments.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/exception.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/experimental/flash.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/experimental/ipython.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/file_io.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/file_io.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/functions.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/functions.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/image.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/image.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/io_streams.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/io_streams.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/mount.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/mount.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/network_file_system.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/network_file_system.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/object.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/object.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/output.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/parallel_map.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/parallel_map.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/partial_function.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/partial_function.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/proxy.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/proxy.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/py.typed +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/queue.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/queue.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/retries.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/runner.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/runner.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/running_app.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/sandbox_fs.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/schedule.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/scheduler_placement.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/secret.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/secret.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/server.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/server.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/serving.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/serving.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/snapshot.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/snapshot.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/stream_type.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/token_flow.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/token_flow.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/types.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/volume.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/volume.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/workspace.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal/workspace.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_docs/__init__.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/__init__.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/py.typed +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/modal_version/__main__.py +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/pyproject.toml +0 -0
- {modal-1.5.5.dev4 → modal-1.5.5.dev6}/setup.cfg +0 -0
|
@@ -39,6 +39,7 @@ class _EnvironmentManager:
|
|
|
39
39
|
name: str, # Name to use for the new Environment
|
|
40
40
|
*,
|
|
41
41
|
restricted: bool = False, # If True, enable RBAC restrictions on the Environment
|
|
42
|
+
default_role: str | None = None, # Default member Role for the Restricted Environment
|
|
42
43
|
experimental_options: dict[str, Any] | None = None, # Experimental options for Environment creation
|
|
43
44
|
client: _Client | None = None, # Optional client with Modal credentials
|
|
44
45
|
) -> None:
|
|
@@ -51,8 +52,11 @@ class _EnvironmentManager:
|
|
|
51
52
|
```
|
|
52
53
|
"""
|
|
53
54
|
check_environment_name(name)
|
|
55
|
+
|
|
54
56
|
client = await _Client.from_env() if client is None else client
|
|
55
57
|
request = api_pb2.EnvironmentCreateRequest(name=name, is_managed=restricted)
|
|
58
|
+
if default_role:
|
|
59
|
+
request.default_member_role_str = default_role
|
|
56
60
|
if (experimental_options or {}).get("is_public", False):
|
|
57
61
|
request.environment_type = api_pb2.ENVIRONMENT_TYPE_PUBLIC
|
|
58
62
|
await client.stub.EnvironmentCreate(request)
|
|
@@ -1579,33 +1579,6 @@ class _App:
|
|
|
1579
1579
|
resp = await client.stub.AppGetTags(req)
|
|
1580
1580
|
return dict(resp.tags)
|
|
1581
1581
|
|
|
1582
|
-
async def _logs(self, client: _Client | None = None) -> AsyncGenerator[str, None]:
|
|
1583
|
-
"""Stream logs from the app.
|
|
1584
|
-
|
|
1585
|
-
This method is considered private and its interface may change - use at your own risk!
|
|
1586
|
-
"""
|
|
1587
|
-
if not self._app_id:
|
|
1588
|
-
raise InvalidError("`app._logs` requires a running/stopped app.")
|
|
1589
|
-
|
|
1590
|
-
client = client or self._client or await _Client.from_env()
|
|
1591
|
-
|
|
1592
|
-
last_log_batch_entry_id: str | None = None
|
|
1593
|
-
while True:
|
|
1594
|
-
request = api_pb2.AppGetLogsRequest(
|
|
1595
|
-
app_id=self._app_id,
|
|
1596
|
-
timeout=55,
|
|
1597
|
-
last_entry_id=last_log_batch_entry_id,
|
|
1598
|
-
)
|
|
1599
|
-
async for log_batch in client.stub.AppGetLogs.unary_stream(request):
|
|
1600
|
-
if log_batch.entry_id:
|
|
1601
|
-
# log_batch entry_id is empty for fd="server" messages from AppGetLogs
|
|
1602
|
-
last_log_batch_entry_id = log_batch.entry_id
|
|
1603
|
-
if log_batch.app_done:
|
|
1604
|
-
return
|
|
1605
|
-
for log in log_batch.items:
|
|
1606
|
-
if log.data:
|
|
1607
|
-
yield log.data
|
|
1608
|
-
|
|
1609
1582
|
@classmethod
|
|
1610
1583
|
def _get_container_app(cls) -> "_App | None":
|
|
1611
1584
|
"""Returns the `App` running inside a container.
|
|
@@ -1621,7 +1594,7 @@ class _App:
|
|
|
1621
1594
|
async def _get_log_query_data(self) -> _LogQueryData:
|
|
1622
1595
|
"""Get the data needed to query logs for this app."""
|
|
1623
1596
|
if not self._app_id:
|
|
1624
|
-
raise InvalidError("`app.
|
|
1597
|
+
raise InvalidError("`app.logs` requires a running/stopped app.")
|
|
1625
1598
|
client = self._client or await _Client.from_env()
|
|
1626
1599
|
filters = LogsFilters()
|
|
1627
1600
|
return _LogQueryData(client=client, app_id=self._app_id, filters=filters, source_object_id=self._app_id)
|
|
@@ -898,13 +898,6 @@ class _App:
|
|
|
898
898
|
"""
|
|
899
899
|
...
|
|
900
900
|
|
|
901
|
-
def _logs(self, client: typing.Optional[modal.client._Client] = None) -> collections.abc.AsyncGenerator[str, None]:
|
|
902
|
-
"""Stream logs from the app.
|
|
903
|
-
|
|
904
|
-
This method is considered private and its interface may change - use at your own risk!
|
|
905
|
-
"""
|
|
906
|
-
...
|
|
907
|
-
|
|
908
901
|
@classmethod
|
|
909
902
|
def _get_container_app(cls) -> typing.Optional[_App]:
|
|
910
903
|
"""Returns the `App` running inside a container.
|
|
@@ -1960,25 +1953,6 @@ class App:
|
|
|
1960
1953
|
|
|
1961
1954
|
get_tags: __get_tags_spec
|
|
1962
1955
|
|
|
1963
|
-
class ___logs_spec(typing_extensions.Protocol):
|
|
1964
|
-
def __call__(self, /, client: typing.Optional[modal.client.Client] = None) -> typing.Generator[str, None, None]:
|
|
1965
|
-
"""Stream logs from the app.
|
|
1966
|
-
|
|
1967
|
-
This method is considered private and its interface may change - use at your own risk!
|
|
1968
|
-
"""
|
|
1969
|
-
...
|
|
1970
|
-
|
|
1971
|
-
def aio(
|
|
1972
|
-
self, /, client: typing.Optional[modal.client.Client] = None
|
|
1973
|
-
) -> collections.abc.AsyncGenerator[str, None]:
|
|
1974
|
-
"""Stream logs from the app.
|
|
1975
|
-
|
|
1976
|
-
This method is considered private and its interface may change - use at your own risk!
|
|
1977
|
-
"""
|
|
1978
|
-
...
|
|
1979
|
-
|
|
1980
|
-
_logs: ___logs_spec
|
|
1981
|
-
|
|
1982
1956
|
@classmethod
|
|
1983
1957
|
def _get_container_app(cls) -> typing.Optional[App]:
|
|
1984
1958
|
"""Returns the `App` running inside a container.
|
|
@@ -7,6 +7,7 @@ import inspect
|
|
|
7
7
|
import os
|
|
8
8
|
import shutil
|
|
9
9
|
import sys
|
|
10
|
+
from collections.abc import Sequence
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
12
13
|
import click
|
|
@@ -28,6 +29,83 @@ _MAX_HELP_WIDTH = 80
|
|
|
28
29
|
_HELP_PADDING = 1
|
|
29
30
|
|
|
30
31
|
|
|
32
|
+
class ModalGlobalOption(click.Option):
|
|
33
|
+
"""An option supported at every level of the Modal CLI."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, *args: Any, environment_variable: str, **kwargs: Any) -> None:
|
|
36
|
+
kwargs.setdefault("expose_value", False)
|
|
37
|
+
super().__init__(*args, **kwargs)
|
|
38
|
+
self.environment_variable = environment_variable
|
|
39
|
+
|
|
40
|
+
def set_environment_value(self, value: object) -> None:
|
|
41
|
+
os.environ[self.environment_variable] = str(value)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ModalProfileOption(ModalGlobalOption):
|
|
45
|
+
"""Global option that updates Modal's active-profile cache."""
|
|
46
|
+
|
|
47
|
+
def set_environment_value(self, value: object) -> None:
|
|
48
|
+
from modal.config import _set_profile
|
|
49
|
+
|
|
50
|
+
# The profile is a global variable and currently set when modal is imported
|
|
51
|
+
# We could probably clean this up and simplify to just setting MODAL_PROFILE
|
|
52
|
+
_set_profile(str(value))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _root_global_options(ctx: click.Context) -> list[tuple[ModalGlobalOption, click.Context]]:
|
|
56
|
+
root_ctx = ctx.find_root()
|
|
57
|
+
return [
|
|
58
|
+
(param, root_ctx) for param in root_ctx.command.get_params(root_ctx) if isinstance(param, ModalGlobalOption)
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _global_option_token_length(ctx: click.Context, args: list[str], index: int) -> int:
|
|
63
|
+
option_name = args[index].split("=", 1)[0]
|
|
64
|
+
for option, _ in _root_global_options(ctx):
|
|
65
|
+
if option_name in (*option.opts, *option.secondary_opts):
|
|
66
|
+
return 1 if "=" in args[index] or option.is_flag else option.nargs + 1
|
|
67
|
+
return 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _consume_global_options(ctx: click.Context, args: list[str]) -> list[str]:
|
|
71
|
+
remaining_args: list[str] = []
|
|
72
|
+
value: str | bool
|
|
73
|
+
index = 0
|
|
74
|
+
while index < len(args):
|
|
75
|
+
arg = args[index]
|
|
76
|
+
option_name, separator, option_value = arg.partition("=")
|
|
77
|
+
global_option = next(
|
|
78
|
+
(
|
|
79
|
+
option
|
|
80
|
+
for option, _ in _root_global_options(ctx)
|
|
81
|
+
if option_name in (*option.opts, *option.secondary_opts)
|
|
82
|
+
),
|
|
83
|
+
None,
|
|
84
|
+
)
|
|
85
|
+
if global_option is None:
|
|
86
|
+
remaining_args.append(arg)
|
|
87
|
+
index += 1
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
if separator:
|
|
91
|
+
value = option_value
|
|
92
|
+
index += 1
|
|
93
|
+
elif global_option.is_flag:
|
|
94
|
+
value = global_option.flag_value
|
|
95
|
+
if option_name in global_option.secondary_opts and isinstance(value, bool):
|
|
96
|
+
value = not value
|
|
97
|
+
index += 1
|
|
98
|
+
else:
|
|
99
|
+
if index + global_option.nargs >= len(args):
|
|
100
|
+
raise click.UsageError(f"Option '{option_name}' requires an argument.", ctx)
|
|
101
|
+
value = args[index + 1]
|
|
102
|
+
index += global_option.nargs + 1
|
|
103
|
+
|
|
104
|
+
global_option.set_environment_value(value)
|
|
105
|
+
|
|
106
|
+
return remaining_args
|
|
107
|
+
|
|
108
|
+
|
|
31
109
|
def use_rich_style() -> bool:
|
|
32
110
|
"""Whether help output should be rendered in the rich style."""
|
|
33
111
|
env = os.environ.get("MODAL_RICH_CLI") # TODO move to config
|
|
@@ -82,11 +160,11 @@ def _option_label(param: click.Parameter, ctx: click.Context) -> Text:
|
|
|
82
160
|
|
|
83
161
|
def _build_options(cmd: click.Command, ctx: click.Context) -> RenderableType | None:
|
|
84
162
|
rows: list[tuple[Text, str]] = []
|
|
85
|
-
for param in cmd
|
|
86
|
-
rec = param.get_help_record(
|
|
163
|
+
for param, param_ctx in _options_with_global_options(cmd, ctx):
|
|
164
|
+
rec = param.get_help_record(param_ctx)
|
|
87
165
|
if rec is None: # skips arguments and hidden options
|
|
88
166
|
continue
|
|
89
|
-
rows.append((_option_label(param,
|
|
167
|
+
rows.append((_option_label(param, param_ctx), rec[1] or ""))
|
|
90
168
|
if not rows:
|
|
91
169
|
return None
|
|
92
170
|
|
|
@@ -98,6 +176,36 @@ def _build_options(cmd: click.Command, ctx: click.Context) -> RenderableType | N
|
|
|
98
176
|
return Group(Text("Options", style=_HEADING_STYLE), table)
|
|
99
177
|
|
|
100
178
|
|
|
179
|
+
def _global_options(ctx: click.Context) -> Sequence[tuple[click.Option, click.Context]]:
|
|
180
|
+
root_ctx = ctx.find_root()
|
|
181
|
+
if root_ctx is ctx:
|
|
182
|
+
return []
|
|
183
|
+
return _root_global_options(ctx)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _options_with_global_options(cmd: click.Command, ctx: click.Context) -> list[tuple[click.Parameter, click.Context]]:
|
|
187
|
+
params = [(param, ctx) for param in cmd.get_params(ctx)]
|
|
188
|
+
if global_options := _global_options(ctx):
|
|
189
|
+
for index, (param, _) in enumerate(params):
|
|
190
|
+
if "--help" in param.opts or "--help" in param.secondary_opts:
|
|
191
|
+
params[index:index] = global_options
|
|
192
|
+
break
|
|
193
|
+
else:
|
|
194
|
+
params.extend(global_options)
|
|
195
|
+
return params
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _format_options(cmd: click.Command, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
199
|
+
records = [
|
|
200
|
+
rec
|
|
201
|
+
for param, param_ctx in _options_with_global_options(cmd, ctx)
|
|
202
|
+
if (rec := param.get_help_record(param_ctx)) is not None
|
|
203
|
+
]
|
|
204
|
+
if records:
|
|
205
|
+
with formatter.section("Options"):
|
|
206
|
+
formatter.write_dl(records)
|
|
207
|
+
|
|
208
|
+
|
|
101
209
|
def _build_epilog(cmd: click.Command) -> RenderableType | None:
|
|
102
210
|
if not cmd.epilog:
|
|
103
211
|
return None
|
|
@@ -114,6 +222,10 @@ def group_commands_by_panel(group: click.Group) -> dict[str, list[tuple[str, cli
|
|
|
114
222
|
return panels
|
|
115
223
|
|
|
116
224
|
|
|
225
|
+
def _has_visible_commands(group: click.Group) -> bool:
|
|
226
|
+
return bool(group_commands_by_panel(group))
|
|
227
|
+
|
|
228
|
+
|
|
117
229
|
def _build_commands(group: click.Group, available_width: int) -> RenderableType | None:
|
|
118
230
|
panels = group_commands_by_panel(group)
|
|
119
231
|
if not panels:
|
|
@@ -189,6 +301,9 @@ class ModalCommand(click.Command):
|
|
|
189
301
|
super().__init__(*args, **kwargs)
|
|
190
302
|
self.panel = panel
|
|
191
303
|
|
|
304
|
+
def format_options(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
305
|
+
_format_options(self, ctx, formatter)
|
|
306
|
+
|
|
192
307
|
def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
193
308
|
if not use_rich_style():
|
|
194
309
|
return super().format_help(ctx, formatter)
|
|
@@ -210,6 +325,7 @@ class ModalGroup(click.Group):
|
|
|
210
325
|
|
|
211
326
|
command_class = ModalCommand
|
|
212
327
|
group_class = type # nested @group.group() reuses the enclosing class
|
|
328
|
+
defer_global_option_parsing = False
|
|
213
329
|
|
|
214
330
|
def __init__(self, *args: Any, panel: str | None = None, **kwargs: Any) -> None:
|
|
215
331
|
# Default to showing help when a group is invoked with no subcommand.
|
|
@@ -232,6 +348,29 @@ class ModalGroup(click.Group):
|
|
|
232
348
|
if hidden is not None:
|
|
233
349
|
cmd.hidden = hidden
|
|
234
350
|
|
|
351
|
+
def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
|
|
352
|
+
index = 0
|
|
353
|
+
while index < len(args):
|
|
354
|
+
arg = args[index]
|
|
355
|
+
if global_option_length := _global_option_token_length(ctx, args, index):
|
|
356
|
+
index += global_option_length
|
|
357
|
+
continue
|
|
358
|
+
command = self.commands.get(arg)
|
|
359
|
+
if command and getattr(command, "defer_global_option_parsing", False):
|
|
360
|
+
args[:] = _consume_global_options(ctx, args[:index]) + args[index:]
|
|
361
|
+
break
|
|
362
|
+
index += 1
|
|
363
|
+
else:
|
|
364
|
+
args[:] = _consume_global_options(ctx, args)
|
|
365
|
+
|
|
366
|
+
return super().parse_args(ctx, args)
|
|
367
|
+
|
|
368
|
+
def format_options(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
369
|
+
if _has_visible_commands(self):
|
|
370
|
+
self.format_commands(ctx, formatter)
|
|
371
|
+
else:
|
|
372
|
+
_format_options(self, ctx, formatter)
|
|
373
|
+
|
|
235
374
|
def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
236
375
|
# Replaces click's single flat "Commands:" section with one section per
|
|
237
376
|
# panel so the simple-style help output still preserves grouping.
|
|
@@ -249,7 +388,7 @@ class ModalGroup(click.Group):
|
|
|
249
388
|
[
|
|
250
389
|
_build_usage(self, ctx),
|
|
251
390
|
_build_help_text(self),
|
|
252
|
-
_build_options(self, ctx),
|
|
391
|
+
None if _has_visible_commands(self) else _build_options(self, ctx),
|
|
253
392
|
_build_commands(self, _available_width(console)),
|
|
254
393
|
_build_epilog(self),
|
|
255
394
|
],
|
|
@@ -351,6 +351,57 @@ async def logs(
|
|
|
351
351
|
)
|
|
352
352
|
|
|
353
353
|
|
|
354
|
+
@app_cli.command("promote", no_args_is_help=True, hidden=True)
|
|
355
|
+
@click.argument("app_identifier")
|
|
356
|
+
@click.argument("version")
|
|
357
|
+
@env_option
|
|
358
|
+
@synchronizer.create_blocking
|
|
359
|
+
async def promote(
|
|
360
|
+
app_identifier: str,
|
|
361
|
+
version: str,
|
|
362
|
+
*,
|
|
363
|
+
env: str | None = None,
|
|
364
|
+
):
|
|
365
|
+
"""Deploy a staged version from an App's deployment history.
|
|
366
|
+
|
|
367
|
+
When a staged version gets promoted, the app is deployed with a new version that
|
|
368
|
+
refers back to the staged version.
|
|
369
|
+
|
|
370
|
+
Examples:
|
|
371
|
+
|
|
372
|
+
Promote an App to a specific version:
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
modal app promote my-app v5
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Promote an App using its App ID instead of its name:
|
|
379
|
+
|
|
380
|
+
```
|
|
381
|
+
modal app promote ap-abcdefghABCDEFGH123456 v5
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
"""
|
|
385
|
+
if m := re.fullmatch(r"v?([1-9]\d*)", version):
|
|
386
|
+
version_number = int(m.group(1))
|
|
387
|
+
else:
|
|
388
|
+
raise UsageError(f"Invalid version specifier: {version}. Expected a positive version number, e.g. 'v5' or '5'.")
|
|
389
|
+
|
|
390
|
+
env = ensure_env(env)
|
|
391
|
+
client = await _Client.from_env()
|
|
392
|
+
app_id, environment_name, lifecycle = await resolve_app_identifier(app_identifier, env, client)
|
|
393
|
+
if lifecycle.app_state != api_pb2.APP_STATE_DEPLOYED:
|
|
394
|
+
env_suffix = f" in the '{environment_name}' environment" if environment_name else ""
|
|
395
|
+
raise InvalidError(f"App '{app_identifier}' is not deployed{env_suffix}.")
|
|
396
|
+
|
|
397
|
+
resp = await client.stub.AppPromote(api_pb2.AppPromoteRequest(app_id=app_id, version=version_number))
|
|
398
|
+
print_server_warnings(resp.server_warnings)
|
|
399
|
+
|
|
400
|
+
output_mgr = OutputManager.get()
|
|
401
|
+
output_mgr.print(f"[green]✓[/green] Promoted App to v{version_number}!")
|
|
402
|
+
output_mgr.print(f"\nView Deployment: [magenta]{resp.url}[/magenta]")
|
|
403
|
+
|
|
404
|
+
|
|
354
405
|
@app_cli.command("rollback", no_args_is_help=True, context_settings={"ignore_unknown_options": True})
|
|
355
406
|
@click.argument("app_identifier")
|
|
356
407
|
@click.argument("version", default="")
|
|
@@ -3,7 +3,8 @@ import json
|
|
|
3
3
|
|
|
4
4
|
import click
|
|
5
5
|
|
|
6
|
-
from modal
|
|
6
|
+
from modal import config as config_module
|
|
7
|
+
from modal.config import _store_user_config, config
|
|
7
8
|
from modal.environments import Environment
|
|
8
9
|
from modal.output import OutputManager
|
|
9
10
|
|
|
@@ -46,7 +47,7 @@ def set_environment(environment_name: str):
|
|
|
46
47
|
# Confirm that the environment exists by looking it up
|
|
47
48
|
Environment.from_name(environment_name).hydrate()
|
|
48
49
|
_store_user_config({"environment": environment_name})
|
|
49
|
-
click.echo(f"New default environment for profile {_profile}: {environment_name}")
|
|
50
|
+
click.echo(f"New default environment for profile {config_module._profile}: {environment_name}")
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
@config_cli.command("set", hidden=True, no_args_is_help=True)
|
|
@@ -8,7 +8,7 @@ from modal._utils.async_utils import synchronizer
|
|
|
8
8
|
from modal.output import OutputManager
|
|
9
9
|
|
|
10
10
|
from . import run, shell as shell_module
|
|
11
|
-
from ._help import ModalCommand, ModalGroup
|
|
11
|
+
from ._help import ModalCommand, ModalGroup, ModalProfileOption
|
|
12
12
|
from .app import app_cli
|
|
13
13
|
from .billing import billing_cli
|
|
14
14
|
from .bootstrap import bootstrap
|
|
@@ -52,6 +52,12 @@ from .workspace import workspace_cli
|
|
|
52
52
|
expose_value=False,
|
|
53
53
|
callback=lambda ctx, param, value: _version_callback(ctx, value),
|
|
54
54
|
)
|
|
55
|
+
@click.option(
|
|
56
|
+
"--profile",
|
|
57
|
+
cls=ModalProfileOption,
|
|
58
|
+
environment_variable="MODAL_PROFILE",
|
|
59
|
+
help="Use this Modal profile for the command.",
|
|
60
|
+
)
|
|
55
61
|
def entrypoint_cli():
|
|
56
62
|
pass
|
|
57
63
|
|
|
@@ -88,14 +94,13 @@ def check_path():
|
|
|
88
94
|
|
|
89
95
|
|
|
90
96
|
@click.command("setup", cls=ModalCommand, help="Bootstrap Modal's configuration.")
|
|
91
|
-
@click.option("--profile", default=None)
|
|
92
97
|
@synchronizer.create_blocking
|
|
93
|
-
async def setup(
|
|
98
|
+
async def setup():
|
|
94
99
|
check_path()
|
|
95
100
|
print_logo()
|
|
96
101
|
|
|
97
102
|
# Fetch a new token (same as `modal token new` but redirect to /home once finishes)
|
|
98
|
-
await _new_token(
|
|
103
|
+
await _new_token(next_url="/home")
|
|
99
104
|
|
|
100
105
|
output = OutputManager.get()
|
|
101
106
|
output.print("[green]→[/green] Run [bold]modal skills install[/bold] to install agent skills")
|
|
@@ -72,10 +72,26 @@ def list_(json: bool = False):
|
|
|
72
72
|
@environment_cli.command("create", help="Create a new environment in the current workspace.", no_args_is_help=True)
|
|
73
73
|
@click.argument("name")
|
|
74
74
|
@click.option("--restricted", is_flag=True, default=False, help="Enable RBAC restrictions on the new environment")
|
|
75
|
+
@click.option(
|
|
76
|
+
"--default-role",
|
|
77
|
+
type=str,
|
|
78
|
+
default=None,
|
|
79
|
+
help="Default member Role for the Restricted Environment",
|
|
80
|
+
)
|
|
75
81
|
@click.option("--public", is_flag=True, default=False, hidden=True)
|
|
76
|
-
def create(
|
|
82
|
+
def create(
|
|
83
|
+
name: str,
|
|
84
|
+
restricted: bool = False,
|
|
85
|
+
default_role: str | None = None,
|
|
86
|
+
public: bool = False,
|
|
87
|
+
):
|
|
77
88
|
check_environment_name(name)
|
|
78
|
-
Environment.objects.create(
|
|
89
|
+
Environment.objects.create(
|
|
90
|
+
name,
|
|
91
|
+
restricted=restricted,
|
|
92
|
+
default_role=default_role,
|
|
93
|
+
experimental_options={"is_public": public},
|
|
94
|
+
)
|
|
79
95
|
prefix = "Public " if public else "Restricted " if restricted else ""
|
|
80
96
|
rich.print(f"[green]✓[/green] {prefix}Environment created: {name}")
|
|
81
97
|
|
|
@@ -8,11 +8,11 @@ import click
|
|
|
8
8
|
from rich.json import JSON
|
|
9
9
|
from rich.table import Table
|
|
10
10
|
|
|
11
|
+
from modal import config as config_module
|
|
11
12
|
from modal._utils.async_utils import synchronizer
|
|
12
13
|
from modal.config import (
|
|
13
14
|
Config,
|
|
14
15
|
_lookup_workspace,
|
|
15
|
-
_profile,
|
|
16
16
|
config_profiles,
|
|
17
17
|
config_set_active_profile,
|
|
18
18
|
)
|
|
@@ -41,7 +41,7 @@ def activate(profile: str):
|
|
|
41
41
|
|
|
42
42
|
@profile_cli.command("current", help="Print the currently active Modal profile.")
|
|
43
43
|
def current():
|
|
44
|
-
click.echo(_profile)
|
|
44
|
+
click.echo(config_module._profile)
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
@profile_cli.command("list", help="Show all Modal profiles and highlight the active one.")
|
|
@@ -62,7 +62,7 @@ async def list_(json: bool | None = False):
|
|
|
62
62
|
|
|
63
63
|
rows = []
|
|
64
64
|
for profile, resp in zip(profiles, responses):
|
|
65
|
-
active = profile == _profile
|
|
65
|
+
active = profile == config_module._profile
|
|
66
66
|
if isinstance(resp, AuthError):
|
|
67
67
|
workspace = "Unknown (authentication failure)"
|
|
68
68
|
elif isinstance(resp, TimeoutError):
|
|
@@ -80,7 +80,7 @@ async def list_(json: bool | None = False):
|
|
|
80
80
|
if "MODAL_TOKEN_ID" in os.environ:
|
|
81
81
|
try:
|
|
82
82
|
env_based_resp = await _lookup_workspace(
|
|
83
|
-
config.get("server_url", profile=_profile),
|
|
83
|
+
config.get("server_url", profile=config_module._profile),
|
|
84
84
|
os.environ["MODAL_TOKEN_ID"],
|
|
85
85
|
os.environ.get("MODAL_TOKEN_SECRET", ""),
|
|
86
86
|
)
|
|
@@ -23,7 +23,7 @@ from ..functions import Function
|
|
|
23
23
|
from ..output import OutputManager
|
|
24
24
|
from ..runner import DEPLOYMENT_STRATEGY_TYPE, deploy_app, run_app
|
|
25
25
|
from ..serving import serve_app
|
|
26
|
-
from ._help import ModalCommand, ModalGroup
|
|
26
|
+
from ._help import ModalCommand, ModalGroup, _consume_global_options, _global_option_token_length
|
|
27
27
|
from .import_refs import (
|
|
28
28
|
CLICommand,
|
|
29
29
|
MethodReference,
|
|
@@ -411,6 +411,33 @@ def _get_runnable_list(all_usable_commands: list[CLICommand]) -> str:
|
|
|
411
411
|
class RunGroup(ModalGroup):
|
|
412
412
|
"""Click group that resolves subcommands dynamically from a file/module ref."""
|
|
413
413
|
|
|
414
|
+
defer_global_option_parsing = True
|
|
415
|
+
|
|
416
|
+
def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
|
|
417
|
+
option_params: dict[str, click.Option] = {}
|
|
418
|
+
for param in self.get_params(ctx):
|
|
419
|
+
if isinstance(param, click.Option):
|
|
420
|
+
for option in (*param.opts, *param.secondary_opts):
|
|
421
|
+
option_params[option] = param
|
|
422
|
+
|
|
423
|
+
func_ref_index = 0
|
|
424
|
+
while func_ref_index < len(args):
|
|
425
|
+
arg = args[func_ref_index]
|
|
426
|
+
if global_option_length := _global_option_token_length(ctx, args, func_ref_index):
|
|
427
|
+
func_ref_index += global_option_length
|
|
428
|
+
elif arg == "--":
|
|
429
|
+
func_ref_index += 1
|
|
430
|
+
break
|
|
431
|
+
elif param := option_params.get(arg.split("=", 1)[0]):
|
|
432
|
+
func_ref_index += 1 if "=" in arg or param.is_flag else param.nargs + 1
|
|
433
|
+
elif arg.startswith("-"):
|
|
434
|
+
func_ref_index += 1
|
|
435
|
+
else:
|
|
436
|
+
break
|
|
437
|
+
|
|
438
|
+
args[:] = _consume_global_options(ctx, args[:func_ref_index]) + args[func_ref_index:]
|
|
439
|
+
return click.Group.parse_args(self, ctx, args)
|
|
440
|
+
|
|
414
441
|
def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
|
|
415
442
|
# note: get_command here is run before the "group logic" in the `run` logic below
|
|
416
443
|
# so to ensure that `env` has been globally populated before user code is loaded, it
|
|
@@ -15,11 +15,6 @@ from ._help import ModalGroup
|
|
|
15
15
|
|
|
16
16
|
token_cli = ModalGroup(name="token", help="Manage tokens.")
|
|
17
17
|
|
|
18
|
-
_PROFILE_HELP = (
|
|
19
|
-
"Modal profile to set credentials for. If unspecified "
|
|
20
|
-
"(and MODAL_PROFILE environment variable is not set), "
|
|
21
|
-
"uses the workspace name associated with the credentials."
|
|
22
|
-
)
|
|
23
18
|
_ACTIVATE_HELP = "Activate the profile containing this token after creation."
|
|
24
19
|
_VERIFY_HELP = "Make a test request to verify the new credentials."
|
|
25
20
|
|
|
@@ -27,14 +22,12 @@ _VERIFY_HELP = "Make a test request to verify the new credentials."
|
|
|
27
22
|
@token_cli.command("set")
|
|
28
23
|
@click.option("--token-id", default=None, help="Account token ID.")
|
|
29
24
|
@click.option("--token-secret", default=None, help="Account token secret.")
|
|
30
|
-
@click.option("--profile", default=None, help=_PROFILE_HELP)
|
|
31
25
|
@click.option("--activate/--no-activate", default=True, help=_ACTIVATE_HELP)
|
|
32
26
|
@click.option("--verify/--no-verify", default=True, help=_VERIFY_HELP)
|
|
33
27
|
@synchronizer.create_blocking
|
|
34
28
|
async def set(
|
|
35
29
|
token_id: str | None,
|
|
36
30
|
token_secret: str | None,
|
|
37
|
-
profile: str | None,
|
|
38
31
|
activate: bool,
|
|
39
32
|
verify: bool,
|
|
40
33
|
):
|
|
@@ -46,18 +39,17 @@ async def set(
|
|
|
46
39
|
token_id = getpass.getpass("Token ID:")
|
|
47
40
|
if token_secret is None:
|
|
48
41
|
token_secret = getpass.getpass("Token secret:")
|
|
49
|
-
await _set_token(token_id, token_secret,
|
|
42
|
+
await _set_token(token_id, token_secret, activate=activate, verify=verify)
|
|
50
43
|
|
|
51
44
|
|
|
52
45
|
@token_cli.command("new")
|
|
53
|
-
@click.option("--profile", default=None, help=_PROFILE_HELP)
|
|
54
46
|
@click.option("--activate/--no-activate", default=True, help=_ACTIVATE_HELP)
|
|
55
47
|
@click.option("--verify/--no-verify", default=True, help=_VERIFY_HELP)
|
|
56
48
|
@click.option("--source", default=None, hidden=True)
|
|
57
49
|
@synchronizer.create_blocking
|
|
58
|
-
async def new(
|
|
50
|
+
async def new(activate: bool, verify: bool, source: str | None):
|
|
59
51
|
"""Create a new token by using an authenticated web session."""
|
|
60
|
-
await _new_token(
|
|
52
|
+
await _new_token(activate=activate, verify=verify, source=source)
|
|
61
53
|
|
|
62
54
|
|
|
63
55
|
@token_cli.command("info")
|
|
@@ -17,6 +17,7 @@ from modal._utils.async_utils import synchronizer
|
|
|
17
17
|
from modal_proto import api_pb2, modal_api_grpc
|
|
18
18
|
from modal_version import __version__
|
|
19
19
|
|
|
20
|
+
from . import config as config_module
|
|
20
21
|
from ._traceback import print_server_warnings
|
|
21
22
|
from ._utils import async_utils
|
|
22
23
|
from ._utils.async_utils import TaskContext, synchronize_api
|
|
@@ -240,6 +241,13 @@ class _Client:
|
|
|
240
241
|
client_type = api_pb2.CLIENT_TYPE_CLIENT
|
|
241
242
|
credentials = (token_id, token_secret)
|
|
242
243
|
else:
|
|
244
|
+
profile = config_module._profile
|
|
245
|
+
if os.environ.get("MODAL_PROFILE") and profile not in config_module._user_config:
|
|
246
|
+
raise AuthError(
|
|
247
|
+
f"Modal profile '{profile}' was not found in {config_module.user_config_path}. "
|
|
248
|
+
"Run `modal profile list` to see available profiles, or "
|
|
249
|
+
f"`modal token new --profile {profile}` to configure it."
|
|
250
|
+
)
|
|
243
251
|
raise AuthError(
|
|
244
252
|
"Token missing. Could not authenticate client."
|
|
245
253
|
" If you have token credentials, see modal.com/docs/sdk/py/latest/config for setup help."
|
|
@@ -38,7 +38,7 @@ class _Client:
|
|
|
38
38
|
server_url: str,
|
|
39
39
|
client_type: int,
|
|
40
40
|
credentials: typing.Optional[tuple[str, str]],
|
|
41
|
-
version: str = "1.5.5.
|
|
41
|
+
version: str = "1.5.5.dev6",
|
|
42
42
|
):
|
|
43
43
|
"""mdmd:hidden
|
|
44
44
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -209,7 +209,7 @@ class Client:
|
|
|
209
209
|
server_url: str,
|
|
210
210
|
client_type: int,
|
|
211
211
|
credentials: typing.Optional[tuple[str, str]],
|
|
212
|
-
version: str = "1.5.5.
|
|
212
|
+
version: str = "1.5.5.dev6",
|
|
213
213
|
):
|
|
214
214
|
"""mdmd:hidden
|
|
215
215
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -242,6 +242,13 @@ def _check_config() -> None:
|
|
|
242
242
|
|
|
243
243
|
_profile = os.environ.get("MODAL_PROFILE") or _config_active_profile()
|
|
244
244
|
|
|
245
|
+
|
|
246
|
+
def _set_profile(profile: str) -> None:
|
|
247
|
+
os.environ["MODAL_PROFILE"] = profile
|
|
248
|
+
global _profile
|
|
249
|
+
_profile = profile
|
|
250
|
+
|
|
251
|
+
|
|
245
252
|
# Define settings
|
|
246
253
|
|
|
247
254
|
|
|
@@ -23,6 +23,7 @@ class EnvironmentManager:
|
|
|
23
23
|
name: str,
|
|
24
24
|
*,
|
|
25
25
|
restricted: bool = False,
|
|
26
|
+
default_role: typing.Optional[str] = None,
|
|
26
27
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
27
28
|
client: typing.Optional[modal.client.Client] = None,
|
|
28
29
|
) -> None:
|
|
@@ -42,6 +43,7 @@ class EnvironmentManager:
|
|
|
42
43
|
name: str,
|
|
43
44
|
*,
|
|
44
45
|
restricted: bool = False,
|
|
46
|
+
default_role: typing.Optional[str] = None,
|
|
45
47
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
46
48
|
client: typing.Optional[modal.client.Client] = None,
|
|
47
49
|
) -> None:
|