runpod 2.0.1.dev1__tar.gz → 2.1.0__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.
- {runpod-2.0.1.dev1 → runpod-2.1.0}/CHANGELOG.md +14 -0
- {runpod-2.0.1.dev1/runpod.egg-info → runpod-2.1.0}/PKG-INFO +1 -1
- runpod-2.1.0/docs/api/handling_errors.md +69 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/cli/references/command_line_interface.md +4 -1
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/cli/start_here.md +1 -1
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/_version.py +3 -3
- runpod-2.1.0/runpod/api/_rate_limit.py +259 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/api/graphql.py +2 -3
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/api/queries/apps.py +1 -2
- runpod-2.1.0/runpod/api/rest.py +279 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/api.py +14 -5
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/auth.py +39 -26
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/error.py +14 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/main.py +34 -7
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_fastapi.py +1 -1
- {runpod-2.0.1.dev1 → runpod-2.1.0/runpod.egg-info}/PKG-INFO +1 -1
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod.egg-info/SOURCES.txt +2 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod.egg-info/scm_file_list.json +2 -0
- runpod-2.1.0/runpod.egg-info/scm_version.json +8 -0
- runpod-2.1.0/tests/test_api/test_rate_limit.py +811 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_api_client.py +37 -4
- runpod-2.1.0/tests/test_apps/test_auth.py +122 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_management_commands.py +38 -4
- runpod-2.0.1.dev1/docs/api/handling_errors.md +0 -28
- runpod-2.0.1.dev1/runpod/api/rest.py +0 -185
- runpod-2.0.1.dev1/runpod.egg-info/scm_version.json +0 -8
- runpod-2.0.1.dev1/tests/test_apps/test_auth.py +0 -60
- {runpod-2.0.1.dev1 → runpod-2.1.0}/.dockerignore +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/.github/dependabot.yml +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/.github/tape_collection/cli_config.tape +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/.github/tape_collection/cli_help.tape +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/.github/tape_collection/cli_ssh.tape +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/.github/tests.json +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/.github/workflows/cd.yml +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/.github/workflows/ci.yml +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/.github/workflows/vhs.yml +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/.gitignore +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/ARCHITECTURE.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/CONTRIBUTING.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/LICENSE +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/MANIFEST.in +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/Makefile +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/README.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/SECURITY.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/build_tools/compile_gpu_test.sh +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/build_tools/gpu_test.c +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/api/queries.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/cli/demos/config.gif +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/cli/demos/help.gif +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/cli/demos/ssh.gif +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/cli/references/projects.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/cli/references/runpod.toml.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/getting_Started.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/images/env_var_location.png +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/functions/error_handling.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/functions/logging.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/gpu_binary_compilation.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/local_testing.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/utils/rp_cleanup.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/utils/rp_debugger.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/utils/rp_download.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/utils/rp_upload.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/utils/rp_validator.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/volume_cache.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/worker.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/worker_fitness_checks.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/docs/serverless/worker_realtime.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/api/create_container_registry_auth.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/api/create_endpoint.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/api/create_template.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/api/get_endpoints.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/apps/README.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/apps/gpu_inference.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/apps/hello_world.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/apps/streaming.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/apps/train_and_eval.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/apps/web_service.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/endpoints/asyncio_job_request.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/endpoints/health.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/endpoints/run.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/endpoints/run_sync.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/endpoints/streaming.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/rest_wrapper.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/serverless/concurrent_handler.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/serverless/logger.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/serverless/simple_handler.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/examples/serverless/volume_cache_handler.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/pyproject.toml +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/pytest.ini +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/requirements.txt +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/_binary_helpers.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/_health/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/_health/cuda.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/_health/fitness.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/_health/gpu.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/_health/system.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/_logger.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/_startup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/agent.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/api/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/api/ctl_commands.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/api/mutations/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/api/mutations/apps.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/api/mutations/container_register_auth.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/api/queries/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/api/queries/user.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/app.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/build.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/context.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/datacenter.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/deploy.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/dev.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/discovery.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/discovery_state.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/entrypoint.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/errors.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/gpu.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/handles.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/images.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/init.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/invoker.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/job.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/logs.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/manage.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/markers.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/model.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/monitor.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/placement.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/protocol.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/registry.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/schedule.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/secret.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/serialization.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/shim.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/spec.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/stubs.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/targets.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/tasks.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/utils/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/utils/client.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/utils/events.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/utils/lookup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/utils/names.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/utils/network.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/volume.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/apps/watch.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/entry.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/groups/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/groups/config/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/groups/config/functions.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/groups/pod/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/groups/pod/commands.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/groups/ssh/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/groups/ssh/commands.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/groups/ssh/functions.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/utils/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/utils/rp_info.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/utils/rp_runpodignore.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/utils/rp_userspace.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/cli/utils/ssh_cmd.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/endpoint/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/endpoint/asyncio/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/endpoint/asyncio/asyncio_runner.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/endpoint/helpers.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/endpoint/runner.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/http_client.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/api.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/catalog.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/compute.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/console.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/endpoint.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/endpoint_config.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/logs.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/options.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/output.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/pod.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/update.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/rp_cli/volume.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/binaries/README.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/binaries/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/binaries/gpu_test +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_capture.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_fitness.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_gpu_fitness.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_handler.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_http.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_job.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_local.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_logger.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_ping.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_prestart.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_progress.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_scale.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_system_fitness.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/rp_tips.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/modules/worker_state.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/utils/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/utils/rp_cleanup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/utils/rp_cuda.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/utils/rp_debugger.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/utils/rp_download.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/utils/rp_model_cache.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/utils/rp_ssrf.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/utils/rp_upload.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/utils/rp_validator.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/utils/rp_volume_cache.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/serverless/worker.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/user_agent.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod/version.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod.egg-info/dependency_links.txt +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod.egg-info/entry_points.txt +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod.egg-info/requires.txt +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/runpod.egg-info/top_level.txt +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/scripts/README.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/scripts/benchmark_cold_start.sh +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/scripts/compare_benchmarks.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/setup.cfg +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/setup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/apps_live_smoke.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/conftest.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/e2e_provisioner.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/01_hello_world.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/02_invocation_styles.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/03_gpu.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/04_dependencies.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/05_api_service.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/06_tasks.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/07_pipelines.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/08_volumes.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/09_secrets.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/10_cached_models.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/11_train_eval.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/12_custom_image.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/13_streaming.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/README.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/examples/run_all.sh +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/fixtures/cold_start/handler.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/fixtures/cold_start/pyproject.toml +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/matrix/main.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/matrix/run.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/task_pod_smoke.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/test_cold_start.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/test_mock_worker.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/e2e/tests.json +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/fixtures/mock_gpu_test +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_agent.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_api/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_api/test_ctl_commands.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_api/test_rest.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/conftest.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_agent_auth.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_app.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_build.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_compat_lock.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_config_params.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_deploy.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_dev.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_discovery.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_dispatch.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_gpu_catalog.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_images.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_job.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_logs.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_manage.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_markers.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_model.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_monitor.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_placement.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_registry.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_retry.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_secret.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_shim.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_targets.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_tasks.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_volume.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_apps/test_watch.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_agent_login.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_catalog_command.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_api.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_groups/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_groups/test_config_functions.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_groups/test_pod_commands.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_groups/test_ssh_commands.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_groups/test_ssh_dispatch.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_groups/test_ssh_functions.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_logs.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_options.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_output.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_utils/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_utils/test_info.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_utils/test_runpodignore.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_utils/test_ssh_cmd.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_cli_utils/test_userspace.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_compute.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_console.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_deploy_command.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_endpoint_command.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_init_command.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_pod_command.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_update_command.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_cli/test_volume_command.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_endpoint/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_endpoint/test_asyncio_init.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_endpoint/test_asyncio_runner.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_endpoint/test_init.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_endpoint/test_runner.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_error.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_init.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_performance/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_performance/test_cold_start.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/local_sim/.env_example +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/local_sim/Makefile +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/local_sim/localhost.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/local_sim/worker.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_capture.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_init.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_integration_worker_state.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_fastapi.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_fitness/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_fitness/conftest.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_fitness/test_force_kill.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_fitness/test_gpu_checks.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_fitness/test_gpu_integration.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_fitness/test_registration.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_fitness/test_safe_startup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_fitness/test_startup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_fitness/test_system_checks.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_handler.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_http.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_job.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_local.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_logger.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_ping.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_progress.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_scale.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_state.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_modules/test_tips.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_prestart.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_prestart_lifecycle.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_rp_scale.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_scale_mirror.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_utils/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_utils/test_cleanup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_utils/test_cuda.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_utils/test_debugger.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_utils/test_download.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_utils/test_model_cache.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_utils/test_rp_volume_cache.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_utils/test_ssrf.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_utils/test_upload.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_utils/test_validate.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_utils_init.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_serverless/test_worker.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_shared/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_shared/test_auth.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_user_agent.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_version.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/test_whatever.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.1.0}/tests/whatever.py +0 -0
|
@@ -8,12 +8,26 @@
|
|
|
8
8
|
|
|
9
9
|
### Bug Fixes
|
|
10
10
|
|
|
11
|
+
* [BREAKING] `rp login` asks you to paste the API key shown in the console after approval. It no longer collects the key by polling the auth request, and it fails fast without an interactive terminal.
|
|
11
12
|
* Task calls support indefinite execution with bounded interruption cleanup. Failed deletion retains the pod ID for recovery.
|
|
12
13
|
* Network volume creation intersects catalog storage support with every sharing resource's hardware stock and datacenter constraints. Unsupported explicit pins fail before creation; existing volumes retain their authoritative IDs and datacenters.
|
|
13
14
|
* Global volume attachments require GPU compute for tasks and endpoints.
|
|
14
15
|
* Deployment artifacts apply project ignore rules and mandatory secret-file exclusions while preserving vendored dependency assets.
|
|
15
16
|
* Synchronous calls wait across polling timeouts on Python 3.10 and propagate operation timeouts.
|
|
16
17
|
|
|
18
|
+
## [2.1.0](https://github.com/runpod/runpod-python/compare/v2.0.0...v2.1.0) (2026-11-03)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* rp login prompts for the api key instead of polling ([#675](https://github.com/runpod/runpod-python/issues/675)) ([2217b6d](https://github.com/runpod/runpod-python/commit/2217b6d15a0554e2652506068f438d740392ad37))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Bug Fixes
|
|
27
|
+
|
|
28
|
+
* coordinate REST API rate limits and bound retries ([#618](https://github.com/runpod/runpod-python/issues/618)) ([d6ce9a8](https://github.com/runpod/runpod-python/commit/d6ce9a8c572ac117f0641846f45dadaa40954e57))
|
|
29
|
+
* require https for documented cli installation ([#674](https://github.com/runpod/runpod-python/issues/674)) ([946afb4](https://github.com/runpod/runpod-python/commit/946afb4ea45ac526c81106783faeac73caf17ee6))
|
|
30
|
+
|
|
17
31
|
## [2.1.0](https://github.com/runpod/runpod-python/compare/v2.0.0...v2.1.0) (2026-10-28)
|
|
18
32
|
|
|
19
33
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Handling API errors
|
|
2
|
+
|
|
3
|
+
Authentication failures raise `AuthenticationError`:
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import runpod
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
runpod.get_pods()
|
|
10
|
+
except runpod.error.AuthenticationError as err:
|
|
11
|
+
print(err)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
REST API problem responses raise `QueryError`. The exception includes the HTTP
|
|
15
|
+
status code, request method and path, and request-validation errors when present:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
try:
|
|
19
|
+
runpod.create_pod(
|
|
20
|
+
"training",
|
|
21
|
+
"runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404",
|
|
22
|
+
"NVIDIA GeForce RTX 4090",
|
|
23
|
+
)
|
|
24
|
+
except runpod.error.QueryError as err:
|
|
25
|
+
print(err.status_code)
|
|
26
|
+
print(err.query)
|
|
27
|
+
print(err.errors)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## REST rate limits
|
|
31
|
+
|
|
32
|
+
REST calls share Runpod's minute, hour, and day quotas across routes within the
|
|
33
|
+
process, keyed by API origin and API key. Both synchronous and asynchronous calls
|
|
34
|
+
reserve the remaining budget advertised by the `RateLimit` response header and
|
|
35
|
+
wait when a window is exhausted. Separate processes and different API keys for the
|
|
36
|
+
same account cannot coordinate their local budgets.
|
|
37
|
+
|
|
38
|
+
Quota estimates account for outstanding requests separately from response
|
|
39
|
+
snapshots. A window refreshes when the response timing establishes a later reset;
|
|
40
|
+
out-of-order snapshots within a window cannot restore spent quota. Discovery and
|
|
41
|
+
reset probes coordinate concurrent callers without consuming their rate-limit
|
|
42
|
+
wait budgets.
|
|
43
|
+
|
|
44
|
+
HTTP 429 responses honor `Retry-After` in seconds or HTTP-date format. Missing or
|
|
45
|
+
invalid retry hints use capped exponential backoff with jitter. By default, a call
|
|
46
|
+
allows three retries and at most 60 seconds of cumulative rate-limit sleeps.
|
|
47
|
+
Network failures and other HTTP errors are not retried, since a failed response
|
|
48
|
+
can follow a successful resource mutation.
|
|
49
|
+
|
|
50
|
+
An exhausted retry or wait budget raises `RateLimitError`, a `QueryError` subclass
|
|
51
|
+
with `status_code=429` and a `retry_after` delay in seconds. A successful call that
|
|
52
|
+
uses the last available token still returns immediately; subsequent calls wait or
|
|
53
|
+
raise if the required delay exceeds their wait budget.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
try:
|
|
57
|
+
pods = runpod.get_pods()
|
|
58
|
+
except runpod.error.RateLimitError as err:
|
|
59
|
+
print(f"Quota exhausted. Retry after {err.retry_after:.1f} seconds.")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The low-level `runpod.api.rest.run_rest_request` and `run_rest_request_async`
|
|
63
|
+
functions accept `max_retries` and `max_rate_limit_wait` keyword arguments. Set
|
|
64
|
+
`max_retries=0` to surface the first 429, or `max_rate_limit_wait=0` to fail rather
|
|
65
|
+
than sleep for an exhausted quota. `timeout` applies separately to each HTTP
|
|
66
|
+
attempt. Async quota waits are cancellable and do not block the event loop.
|
|
67
|
+
|
|
68
|
+
These protections apply to the management REST API transport. GraphQL calls and
|
|
69
|
+
Serverless job execution use separate transports.
|
|
@@ -34,11 +34,14 @@ rp --help
|
|
|
34
34
|
### Authenticate
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
rp login # browser approval
|
|
37
|
+
rp login # browser approval, then paste the key
|
|
38
38
|
rp login --api-key YOUR_KEY # store a key directly
|
|
39
39
|
rp login --profile staging # save browser credentials in a named profile
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
`rp login` opens the console to approve the request. After you approve, the console shows a new API key; paste it at the hidden prompt. The CLI checks the key before saving it.
|
|
43
|
+
`rp login` needs an interactive terminal. In CI or scripts, set `RUNPOD_API_KEY` or use `rp login --api-key`.
|
|
44
|
+
|
|
42
45
|
Credentials are stored in `~/.runpod/config.toml`.
|
|
43
46
|
Use `--profile NAME` with either login method to update only that profile; the default is `default`.
|
|
44
47
|
|
|
@@ -8,7 +8,7 @@ Note: This CLI is not the same as runpodctl and provides a different set of feat
|
|
|
8
8
|
|
|
9
9
|
### Authenticate
|
|
10
10
|
|
|
11
|
-
Store your Runpod API key by running `rp login` (browser
|
|
11
|
+
Store your Runpod API key by running `rp login` (approve in the browser, then paste the key the console shows) or `rp login --api-key YOUR_KEY`. Credentials are stored in `~/.runpod/config.toml`.
|
|
12
12
|
|
|
13
13
|
Pass `--profile staging` with either login method to update a named credential profile without changing the others. The default profile is `default`.
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2,
|
|
21
|
+
__version__ = version = '2.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 1, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g0d59088b9'
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""Shared REST quotas for threads and asynchronous callers."""
|
|
2
|
+
|
|
3
|
+
import hmac
|
|
4
|
+
import math
|
|
5
|
+
import os
|
|
6
|
+
import random
|
|
7
|
+
import re
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from concurrent.futures import Future
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
from datetime import timezone
|
|
13
|
+
from email.utils import parsedate_to_datetime
|
|
14
|
+
from typing import Mapping, Optional
|
|
15
|
+
from urllib.parse import urlsplit
|
|
16
|
+
|
|
17
|
+
from runpod.error import RateLimitError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _retry_after(value: Optional[str]) -> Optional[float]:
|
|
21
|
+
if value is None:
|
|
22
|
+
return None
|
|
23
|
+
try:
|
|
24
|
+
delay = float(value)
|
|
25
|
+
except ValueError:
|
|
26
|
+
try:
|
|
27
|
+
date = parsedate_to_datetime(value)
|
|
28
|
+
if date.tzinfo is None:
|
|
29
|
+
date = date.replace(tzinfo=timezone.utc)
|
|
30
|
+
delay = date.timestamp() - time.time()
|
|
31
|
+
except (ValueError, TypeError, OverflowError):
|
|
32
|
+
return None
|
|
33
|
+
delay = max(0.0, delay)
|
|
34
|
+
return delay if math.isfinite(delay) and delay >= 0 else None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _windows(value: str) -> dict[str, tuple[int, float]]:
|
|
38
|
+
windows = {}
|
|
39
|
+
for member in value.split(","):
|
|
40
|
+
parts = member.strip().split(";")
|
|
41
|
+
if parts[0] not in {'"minute"', '"hour"', '"day"'}:
|
|
42
|
+
continue
|
|
43
|
+
parameters = {}
|
|
44
|
+
for part in parts[1:]:
|
|
45
|
+
match = re.fullmatch(r"\s*([a-z]+)\s*=\s*([0-9]{1,15})\s*", part)
|
|
46
|
+
if match:
|
|
47
|
+
parameters[match[1]] = int(match[2])
|
|
48
|
+
if "r" in parameters and "t" in parameters:
|
|
49
|
+
windows[parts[0]] = (parameters["r"], float(parameters["t"]))
|
|
50
|
+
return windows
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class _RetryBudget:
|
|
54
|
+
def __init__(self, max_retries: int, max_wait: float, query: str):
|
|
55
|
+
self.max_retries = max_retries
|
|
56
|
+
self.remaining_wait = max_wait
|
|
57
|
+
self.query = query
|
|
58
|
+
|
|
59
|
+
def wait(self, delay: float) -> None:
|
|
60
|
+
if delay > self.remaining_wait:
|
|
61
|
+
raise RateLimitError(
|
|
62
|
+
"Rate limit wait budget exceeded", self.query, retry_after=delay
|
|
63
|
+
)
|
|
64
|
+
self.remaining_wait -= delay
|
|
65
|
+
|
|
66
|
+
def can_retry(self, attempt: int, retry_after: Optional[float]) -> bool:
|
|
67
|
+
return (
|
|
68
|
+
retry_after is not None
|
|
69
|
+
and attempt < self.max_retries
|
|
70
|
+
and retry_after <= self.remaining_wait
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class _Bucket:
|
|
75
|
+
def __init__(self):
|
|
76
|
+
self.lock = threading.Lock()
|
|
77
|
+
self.waiters: set[Future[None]] = set()
|
|
78
|
+
self.windows: dict[str, tuple[Optional[int], float]] = {}
|
|
79
|
+
self.estimates: dict[str, int] = {}
|
|
80
|
+
self.reset_lower_bounds: dict[str, float] = {}
|
|
81
|
+
self.blocked_until = 0.0
|
|
82
|
+
self.discovered = False
|
|
83
|
+
self.in_flight = 0
|
|
84
|
+
self.users = 0
|
|
85
|
+
self.last_used = time.monotonic()
|
|
86
|
+
|
|
87
|
+
def acquire(self, budget: Optional[_RetryBudget] = None) -> float | Future[None]:
|
|
88
|
+
with self.lock:
|
|
89
|
+
now = time.monotonic()
|
|
90
|
+
delay = max(0.0, self.blocked_until - now)
|
|
91
|
+
for name, (remaining, reset_at) in self.windows.items():
|
|
92
|
+
if remaining is not None and now >= reset_at:
|
|
93
|
+
self.windows[name] = (None, reset_at)
|
|
94
|
+
if remaining == 0:
|
|
95
|
+
self.discovered = False
|
|
96
|
+
elif remaining == 0:
|
|
97
|
+
delay = max(delay, reset_at - now)
|
|
98
|
+
if delay:
|
|
99
|
+
if budget is not None:
|
|
100
|
+
budget.wait(delay)
|
|
101
|
+
return delay
|
|
102
|
+
if self.in_flight and (
|
|
103
|
+
not self.discovered or (self.blocked_until and not delay)
|
|
104
|
+
):
|
|
105
|
+
waiter: Future[None] = Future()
|
|
106
|
+
self.waiters.add(waiter)
|
|
107
|
+
waiter.add_done_callback(self._remove_waiter)
|
|
108
|
+
return waiter
|
|
109
|
+
self.in_flight += 1
|
|
110
|
+
for name, (remaining, reset_at) in self.windows.items():
|
|
111
|
+
if remaining is not None:
|
|
112
|
+
self.windows[name] = (max(0, remaining - 1), reset_at)
|
|
113
|
+
self.estimates[name] = max(0, self.estimates[name] - 1)
|
|
114
|
+
return 0.0
|
|
115
|
+
|
|
116
|
+
def _remove_waiter(self, waiter: Future[None]) -> None:
|
|
117
|
+
with self.lock:
|
|
118
|
+
self.waiters.discard(waiter)
|
|
119
|
+
|
|
120
|
+
def _notify_waiters(self) -> None:
|
|
121
|
+
with self.lock:
|
|
122
|
+
waiters, self.waiters = self.waiters, set()
|
|
123
|
+
for waiter in waiters:
|
|
124
|
+
if waiter.set_running_or_notify_cancel():
|
|
125
|
+
waiter.set_result(None)
|
|
126
|
+
|
|
127
|
+
def update(
|
|
128
|
+
self,
|
|
129
|
+
status: int,
|
|
130
|
+
headers: Mapping[str, str],
|
|
131
|
+
attempt: int,
|
|
132
|
+
*,
|
|
133
|
+
started_at: Optional[float] = None,
|
|
134
|
+
) -> Optional[float]:
|
|
135
|
+
windows = _windows(
|
|
136
|
+
", ".join(
|
|
137
|
+
value for key, value in headers.items() if key.lower() == "ratelimit"
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
headers = {key.lower(): value for key, value in headers.items()}
|
|
141
|
+
retry_after = None
|
|
142
|
+
if status == 429:
|
|
143
|
+
retry_after = _retry_after(headers.get("retry-after"))
|
|
144
|
+
if retry_after is None:
|
|
145
|
+
retry_after = random.uniform(0.5, 1.0) * 2 ** min(attempt, 6)
|
|
146
|
+
with self.lock:
|
|
147
|
+
now = time.monotonic()
|
|
148
|
+
self.discovered = True
|
|
149
|
+
if self.blocked_until <= now:
|
|
150
|
+
self.blocked_until = 0.0
|
|
151
|
+
for name, (remaining, reset_after) in windows.items():
|
|
152
|
+
reset_at = now + reset_after
|
|
153
|
+
reset_lower = (now if started_at is None else started_at) + max(
|
|
154
|
+
0.0, reset_after - 1.0
|
|
155
|
+
)
|
|
156
|
+
previous, previous_reset = self.windows.get(name, (None, 0.0))
|
|
157
|
+
previous_lower = self.reset_lower_bounds.get(name, 0.0)
|
|
158
|
+
if reset_at < previous_lower:
|
|
159
|
+
continue
|
|
160
|
+
if (
|
|
161
|
+
previous is not None
|
|
162
|
+
and now < previous_reset
|
|
163
|
+
and reset_lower <= previous_reset
|
|
164
|
+
):
|
|
165
|
+
remaining = min(remaining, self.estimates[name])
|
|
166
|
+
reset_at = min(reset_at, previous_reset)
|
|
167
|
+
reset_lower = max(reset_lower, previous_lower)
|
|
168
|
+
self.estimates[name] = remaining
|
|
169
|
+
self.reset_lower_bounds[name] = reset_lower
|
|
170
|
+
self.windows[name] = (
|
|
171
|
+
max(0, remaining - max(0, self.in_flight - 1)),
|
|
172
|
+
reset_at,
|
|
173
|
+
)
|
|
174
|
+
for name in list(self.windows):
|
|
175
|
+
if name not in windows and self.windows[name][1] <= now:
|
|
176
|
+
del self.windows[name]
|
|
177
|
+
del self.estimates[name]
|
|
178
|
+
del self.reset_lower_bounds[name]
|
|
179
|
+
if retry_after is not None:
|
|
180
|
+
retry_after = max(
|
|
181
|
+
retry_after,
|
|
182
|
+
self.blocked_until - now,
|
|
183
|
+
*(
|
|
184
|
+
reset - now
|
|
185
|
+
for name, (_, reset) in self.windows.items()
|
|
186
|
+
if self.estimates[name] == 0
|
|
187
|
+
),
|
|
188
|
+
0.0,
|
|
189
|
+
)
|
|
190
|
+
self.blocked_until = max(self.blocked_until, now + retry_after)
|
|
191
|
+
self._notify_waiters()
|
|
192
|
+
return retry_after
|
|
193
|
+
|
|
194
|
+
def release(self):
|
|
195
|
+
with self.lock:
|
|
196
|
+
self.in_flight -= 1
|
|
197
|
+
for name, (remaining, reset_at) in self.windows.items():
|
|
198
|
+
if remaining is not None:
|
|
199
|
+
self.windows[name] = (
|
|
200
|
+
max(0, self.estimates[name] - self.in_flight),
|
|
201
|
+
reset_at,
|
|
202
|
+
)
|
|
203
|
+
self._notify_waiters()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class _RateLimits:
|
|
207
|
+
def __init__(self):
|
|
208
|
+
self.lock = threading.Lock()
|
|
209
|
+
self.fingerprint_key = os.urandom(32)
|
|
210
|
+
self.buckets: dict[tuple[str, str, bytes], _Bucket] = {}
|
|
211
|
+
self.next_cleanup = 0.0
|
|
212
|
+
|
|
213
|
+
@contextmanager
|
|
214
|
+
def bucket(self, url: str, api_key: str):
|
|
215
|
+
origin = urlsplit(url)
|
|
216
|
+
key = (
|
|
217
|
+
origin.scheme.lower(),
|
|
218
|
+
origin.netloc.lower(),
|
|
219
|
+
hmac.digest(self.fingerprint_key, api_key.encode(), "sha256"),
|
|
220
|
+
)
|
|
221
|
+
with self.lock:
|
|
222
|
+
now = time.monotonic()
|
|
223
|
+
if now >= self.next_cleanup:
|
|
224
|
+
for cached_key, bucket in list(self.buckets.items()):
|
|
225
|
+
if bucket.users or now - bucket.last_used < 300:
|
|
226
|
+
continue
|
|
227
|
+
with bucket.lock:
|
|
228
|
+
if bucket.blocked_until <= now and all(
|
|
229
|
+
reset <= now for _, reset in bucket.windows.values()
|
|
230
|
+
):
|
|
231
|
+
del self.buckets[cached_key]
|
|
232
|
+
self.next_cleanup = now + 60
|
|
233
|
+
bucket = self.buckets.setdefault(key, _Bucket())
|
|
234
|
+
bucket.users += 1
|
|
235
|
+
try:
|
|
236
|
+
yield bucket
|
|
237
|
+
finally:
|
|
238
|
+
with self.lock:
|
|
239
|
+
bucket.users -= 1
|
|
240
|
+
bucket.last_used = time.monotonic()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _validate_retry_options(max_retries: int, max_rate_limit_wait: float) -> None:
|
|
244
|
+
if (
|
|
245
|
+
isinstance(max_retries, bool)
|
|
246
|
+
or not isinstance(max_retries, int)
|
|
247
|
+
or max_retries < 0
|
|
248
|
+
):
|
|
249
|
+
raise ValueError("max_retries must be a non-negative integer")
|
|
250
|
+
if (
|
|
251
|
+
isinstance(max_rate_limit_wait, bool)
|
|
252
|
+
or not isinstance(max_rate_limit_wait, (int, float))
|
|
253
|
+
or not math.isfinite(max_rate_limit_wait)
|
|
254
|
+
or max_rate_limit_wait < 0
|
|
255
|
+
):
|
|
256
|
+
raise ValueError("max_rate_limit_wait must be finite and non-negative")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
rate_limits = _RateLimits()
|
|
@@ -98,9 +98,8 @@ async def run_graphql_query_async(
|
|
|
98
98
|
variables: Optional GraphQL variables to send with the query.
|
|
99
99
|
timeout: Total request timeout in seconds.
|
|
100
100
|
anonymous: Send the request without credentials even when a key
|
|
101
|
-
is available.
|
|
102
|
-
|
|
103
|
-
a stale stored key would make the poll spin forever.
|
|
101
|
+
is available. Login creates its auth request this way so a
|
|
102
|
+
stale stored key cannot fail the request.
|
|
104
103
|
"""
|
|
105
104
|
import aiohttp # pylint: disable=import-outside-toplevel
|
|
106
105
|
|
|
@@ -66,13 +66,12 @@ query getFlashEnvironmentByName($input: FlashEnvironmentByNameInput!) {
|
|
|
66
66
|
}
|
|
67
67
|
"""
|
|
68
68
|
|
|
69
|
+
# no apiKey: the console shows the key only to the approver.
|
|
69
70
|
QUERY_FLASH_AUTH_REQUEST_STATUS = """
|
|
70
71
|
query flashAuthRequestStatus($flashAuthRequestId: String!) {
|
|
71
72
|
flashAuthRequestStatus(flashAuthRequestId: $flashAuthRequestId) {
|
|
72
73
|
id
|
|
73
74
|
status
|
|
74
|
-
expiresAt
|
|
75
|
-
apiKey
|
|
76
75
|
}
|
|
77
76
|
}
|
|
78
77
|
"""
|