runpod 2.0.1.dev1__tar.gz → 2.2.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.2.0}/CHANGELOG.md +41 -0
- {runpod-2.0.1.dev1/runpod.egg-info → runpod-2.2.0}/PKG-INFO +13 -2
- {runpod-2.0.1.dev1 → runpod-2.2.0}/README.md +12 -0
- runpod-2.2.0/docs/api/handling_errors.md +69 -0
- runpod-2.2.0/docs/cli/references/command_line_interface.md +104 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/cli/start_here.md +3 -1
- runpod-2.2.0/docs/getting_started.md +34 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/requirements.txt +0 -1
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/__init__.py +15 -2
- runpod-2.2.0/runpod/_hosts.py +54 -0
- runpod-2.2.0/runpod/_serialization.py +21 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_version.py +3 -3
- runpod-2.2.0/runpod/_volume_transfer_utils.py +171 -0
- runpod-2.2.0/runpod/api/_rate_limit.py +259 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/graphql.py +4 -4
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/mutations/apps.py +6 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/queries/apps.py +1 -2
- runpod-2.2.0/runpod/api/rest.py +279 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/__init__.py +2 -1
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/api.py +56 -5
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/app.py +11 -1
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/auth.py +41 -29
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/build.py +0 -1
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/context.py +22 -1
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/datacenter.py +16 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/deploy.py +127 -31
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/handles.py +27 -5
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/images.py +3 -5
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/placement.py +8 -4
- runpod-2.2.0/runpod/apps/protocol.py +86 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/serialization.py +28 -35
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/spec.py +32 -4
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/stubs.py +31 -2
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/targets.py +121 -59
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/tasks.py +130 -82
- runpod-2.2.0/runpod/apps/utils/network.py +65 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/volume.py +96 -2
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/config/functions.py +46 -18
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/error.py +14 -0
- runpod-2.2.0/runpod/network_volume_transfer.py +981 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/console.py +9 -9
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/endpoint.py +2 -1
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/main.py +53 -14
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/pod.py +2 -1
- runpod-2.2.0/runpod/rp_cli/volume.py +610 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_fastapi.py +14 -5
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_http.py +15 -2
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_job.py +10 -1
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_scale.py +7 -2
- {runpod-2.0.1.dev1 → runpod-2.2.0/runpod.egg-info}/PKG-INFO +13 -2
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod.egg-info/SOURCES.txt +17 -1
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod.egg-info/requires.txt +0 -1
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod.egg-info/scm_file_list.json +17 -1
- runpod-2.2.0/runpod.egg-info/scm_version.json +8 -0
- runpod-2.2.0/tests/e2e/test_network_volume_transfer_live.py +74 -0
- runpod-2.2.0/tests/test_api/test_rate_limit.py +811 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_api_client.py +37 -4
- runpod-2.2.0/tests/test_apps/test_auth.py +127 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_config_params.py +54 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_deploy.py +35 -4
- runpod-2.2.0/tests/test_apps/test_deployed_tasks.py +471 -0
- runpod-2.2.0/tests/test_apps/test_deployment_validation.py +345 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_dispatch.py +25 -9
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_placement.py +44 -0
- runpod-2.2.0/tests/test_apps/test_runtime_call_validation.py +172 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_tasks.py +15 -13
- runpod-2.2.0/tests/test_apps/test_transport_parity.py +170 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_volume.py +16 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_console.py +7 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_deploy_command.py +28 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_endpoint_command.py +18 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_management_commands.py +67 -4
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_pod_command.py +8 -0
- runpod-2.2.0/tests/test_hosts.py +67 -0
- runpod-2.2.0/tests/test_network_volume_transfer.py +2004 -0
- runpod-2.2.0/tests/test_profile.py +79 -0
- runpod-2.2.0/tests/test_serialization.py +231 -0
- runpod-2.2.0/tests/test_serverless/test_refresh_worker.py +640 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_rp_volume_cache.py +3 -0
- runpod-2.0.1.dev1/docs/api/handling_errors.md +0 -28
- runpod-2.0.1.dev1/docs/cli/references/command_line_interface.md +0 -61
- runpod-2.0.1.dev1/docs/getting_Started.md +0 -21
- runpod-2.0.1.dev1/runpod/api/rest.py +0 -185
- runpod-2.0.1.dev1/runpod/apps/protocol.py +0 -60
- runpod-2.0.1.dev1/runpod/apps/utils/network.py +0 -33
- runpod-2.0.1.dev1/runpod/rp_cli/volume.py +0 -298
- 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.2.0}/.dockerignore +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/dependabot.yml +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/tape_collection/cli_config.tape +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/tape_collection/cli_help.tape +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/tape_collection/cli_ssh.tape +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/tests.json +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/workflows/cd.yml +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/workflows/ci.yml +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/workflows/vhs.yml +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/.gitignore +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/ARCHITECTURE.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/CONTRIBUTING.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/LICENSE +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/MANIFEST.in +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/Makefile +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/SECURITY.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/build_tools/compile_gpu_test.sh +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/build_tools/gpu_test.c +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/api/queries.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/cli/demos/config.gif +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/cli/demos/help.gif +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/cli/demos/ssh.gif +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/cli/references/projects.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/cli/references/runpod.toml.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/images/env_var_location.png +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/functions/error_handling.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/functions/logging.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/gpu_binary_compilation.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/local_testing.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/utils/rp_cleanup.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/utils/rp_debugger.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/utils/rp_download.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/utils/rp_upload.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/utils/rp_validator.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/volume_cache.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/worker.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/worker_fitness_checks.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/worker_realtime.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/api/create_container_registry_auth.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/api/create_endpoint.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/api/create_template.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/api/get_endpoints.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/apps/README.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/apps/gpu_inference.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/apps/hello_world.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/apps/streaming.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/apps/train_and_eval.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/apps/web_service.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/endpoints/asyncio_job_request.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/endpoints/health.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/endpoints/run.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/endpoints/run_sync.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/endpoints/streaming.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/rest_wrapper.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/serverless/concurrent_handler.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/serverless/logger.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/serverless/simple_handler.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/serverless/volume_cache_handler.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/pyproject.toml +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/pytest.ini +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_binary_helpers.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_health/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_health/cuda.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_health/fitness.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_health/gpu.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_health/system.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_logger.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_startup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/agent.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/ctl_commands.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/mutations/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/mutations/container_register_auth.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/queries/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/queries/user.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/dev.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/discovery.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/discovery_state.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/entrypoint.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/errors.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/gpu.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/init.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/invoker.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/job.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/logs.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/manage.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/markers.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/model.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/monitor.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/registry.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/schedule.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/secret.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/shim.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/utils/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/utils/client.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/utils/events.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/utils/lookup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/utils/names.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/watch.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/entry.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/config/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/pod/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/pod/commands.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/ssh/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/ssh/commands.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/ssh/functions.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/utils/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/utils/rp_info.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/utils/rp_runpodignore.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/utils/rp_userspace.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/utils/ssh_cmd.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/endpoint/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/endpoint/asyncio/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/endpoint/asyncio/asyncio_runner.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/endpoint/helpers.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/endpoint/runner.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/http_client.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/api.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/catalog.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/compute.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/endpoint_config.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/logs.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/options.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/output.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/update.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/binaries/README.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/binaries/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/binaries/gpu_test +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_capture.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_fitness.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_gpu_fitness.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_handler.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_local.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_logger.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_ping.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_prestart.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_progress.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_system_fitness.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_tips.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/worker_state.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_cleanup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_cuda.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_debugger.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_download.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_model_cache.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_ssrf.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_upload.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_validator.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_volume_cache.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/worker.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/user_agent.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/version.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod.egg-info/dependency_links.txt +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod.egg-info/entry_points.txt +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod.egg-info/top_level.txt +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/scripts/README.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/scripts/benchmark_cold_start.sh +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/scripts/compare_benchmarks.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/setup.cfg +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/setup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/apps_live_smoke.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/conftest.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/e2e_provisioner.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/01_hello_world.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/02_invocation_styles.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/03_gpu.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/04_dependencies.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/05_api_service.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/06_tasks.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/07_pipelines.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/08_volumes.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/09_secrets.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/10_cached_models.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/11_train_eval.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/12_custom_image.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/13_streaming.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/README.md +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/run_all.sh +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/fixtures/cold_start/handler.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/fixtures/cold_start/pyproject.toml +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/matrix/main.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/matrix/run.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/task_pod_smoke.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/test_cold_start.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/test_mock_worker.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/tests.json +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/fixtures/mock_gpu_test +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_agent.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_api/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_api/test_ctl_commands.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_api/test_rest.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/conftest.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_agent_auth.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_app.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_build.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_compat_lock.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_dev.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_discovery.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_gpu_catalog.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_images.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_job.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_logs.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_manage.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_markers.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_model.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_monitor.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_registry.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_retry.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_secret.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_shim.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_targets.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_watch.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_agent_login.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_catalog_command.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_api.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_groups/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_groups/test_config_functions.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_groups/test_pod_commands.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_groups/test_ssh_commands.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_groups/test_ssh_dispatch.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_groups/test_ssh_functions.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_logs.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_options.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_output.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_utils/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_utils/test_info.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_utils/test_runpodignore.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_utils/test_ssh_cmd.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_utils/test_userspace.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_compute.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_init_command.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_update_command.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_volume_command.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_endpoint/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_endpoint/test_asyncio_init.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_endpoint/test_asyncio_runner.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_endpoint/test_init.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_endpoint/test_runner.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_error.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_init.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_performance/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_performance/test_cold_start.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/local_sim/.env_example +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/local_sim/Makefile +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/local_sim/localhost.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/local_sim/worker.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_capture.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_init.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_integration_worker_state.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fastapi.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/conftest.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_force_kill.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_gpu_checks.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_gpu_integration.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_registration.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_safe_startup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_startup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_system_checks.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_handler.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_http.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_job.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_local.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_logger.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_ping.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_progress.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_scale.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_state.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_tips.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_prestart.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_prestart_lifecycle.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_rp_scale.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_scale_mirror.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_cleanup.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_cuda.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_debugger.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_download.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_model_cache.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_ssrf.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_upload.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_validate.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils_init.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_worker.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_shared/__init__.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_shared/test_auth.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_user_agent.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_version.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_whatever.py +0 -0
- {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/whatever.py +0 -0
|
@@ -5,15 +5,56 @@
|
|
|
5
5
|
### Features
|
|
6
6
|
|
|
7
7
|
* Apps expose `NetworkVolume` and `GlobalVolume` resources with explicit path-keyed mounts, lazy create-if-missing provisioning, and execution-bound filesystem access. Global volumes use GraphQL for lookup and creation.
|
|
8
|
+
* Deployed Flash calls use endpoint or pod runtime credentials. Release requires deployed support from [RunPod#6686](https://github.com/runpod/RunPod/pull/6686), [ai-api#1176](https://github.com/runpod/ai-api/pull/1176), and [sdk-runtime#4](https://github.com/runpod-workers/sdk-runtime/pull/4).
|
|
9
|
+
|
|
10
|
+
### Breaking Changes
|
|
11
|
+
|
|
12
|
+
* Production endpoint environment variables reserve `RUNPOD_API_KEY` and runtime identity fields. Workers authenticate Flash calls with platform-issued runtime credentials; application credentials must use non-reserved environment names.
|
|
13
|
+
* Deployed workers can invoke Flash resources only within their own app and environment, including through `Queue`, `Api`, and `Task` stubs. Local clients use account credentials subject to their permissions.
|
|
14
|
+
* Remote queues, tasks, and JSON API calls require finite JSON-compatible arguments and results in local, dev, and deployed execution. Dev workers receive source separately from the JSON argument contract. Explicit `.local()` calls run Python directly.
|
|
8
15
|
|
|
9
16
|
### Bug Fixes
|
|
10
17
|
|
|
18
|
+
* [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
19
|
* Task calls support indefinite execution with bounded interruption cleanup. Failed deletion retains the pod ID for recovery.
|
|
12
20
|
* 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
21
|
* Global volume attachments require GPU compute for tasks and endpoints.
|
|
14
22
|
* Deployment artifacts apply project ignore rules and mandatory secret-file exclusions while preserving vendored dependency assets.
|
|
15
23
|
* Synchronous calls wait across polling timeouts on Python 3.10 and propagate operation timeouts.
|
|
16
24
|
|
|
25
|
+
## [2.2.0](https://github.com/runpod/runpod-python/compare/v2.1.0...v2.2.0) (2026-11-09)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
* invoke deployed flash tasks with runtime credentials (SEC-375 CON-1833) ([#644](https://github.com/runpod/runpod-python/issues/644)) ([367f174](https://github.com/runpod/runpod-python/commit/367f174ef14cc7fcf909a3b26413c3f2d38cbd9b))
|
|
31
|
+
* make console, proxy, and endpoint hosts configurable ([#684](https://github.com/runpod/runpod-python/issues/684)) ([b9583f7](https://github.com/runpod/runpod-python/commit/b9583f7c0a02e6c0cbb9598538393fc239d88852))
|
|
32
|
+
* resumable network volume upload and download ([#655](https://github.com/runpod/runpod-python/issues/655)) ([a677ee9](https://github.com/runpod/runpod-python/commit/a677ee9f08f21da5a29cc126ed63032fc1063365))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### Bug Fixes
|
|
36
|
+
|
|
37
|
+
* **apps:** accept datacenter ids outside the built-in list ([#690](https://github.com/runpod/runpod-python/issues/690)) ([dbc251a](https://github.com/runpod/runpod-python/commit/dbc251ac8ba87ed4cc53f71010bc8c87dba1c77d))
|
|
38
|
+
* **serverless:** stop taking jobs after a per-job refresh_worker (SLS-900) ([#663](https://github.com/runpod/runpod-python/issues/663)) ([8ca9d9b](https://github.com/runpod/runpod-python/commit/8ca9d9bcfdc0ad1792e899cdef49a4fd172fbb13))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Documentation
|
|
42
|
+
|
|
43
|
+
* rename getting_Started.md to getting_started.md ([#697](https://github.com/runpod/runpod-python/issues/697)) ([fbcabcd](https://github.com/runpod/runpod-python/commit/fbcabcd73889783d8e03b6261154891f13b0d9d6))
|
|
44
|
+
|
|
45
|
+
## [2.1.0](https://github.com/runpod/runpod-python/compare/v2.0.0...v2.1.0) (2026-11-03)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### Features
|
|
49
|
+
|
|
50
|
+
* 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))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### Bug Fixes
|
|
54
|
+
|
|
55
|
+
* 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))
|
|
56
|
+
* require https for documented cli installation ([#674](https://github.com/runpod/runpod-python/issues/674)) ([946afb4](https://github.com/runpod/runpod-python/commit/946afb4ea45ac526c81106783faeac73caf17ee6))
|
|
57
|
+
|
|
17
58
|
## [2.1.0](https://github.com/runpod/runpod-python/compare/v2.0.0...v2.1.0) (2026-10-28)
|
|
18
59
|
|
|
19
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runpod
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: 🐍 | Python library for Runpod API and serverless worker SDK.
|
|
5
5
|
Home-page: https://runpod.io
|
|
6
6
|
Author: Runpod
|
|
@@ -34,7 +34,6 @@ Requires-Dist: backoff>=2.2.1
|
|
|
34
34
|
Requires-Dist: boto3>=1.43.118
|
|
35
35
|
Requires-Dist: certifi>=2026.10.19
|
|
36
36
|
Requires-Dist: click>=8.5.0
|
|
37
|
-
Requires-Dist: cloudpickle>=3.1.2
|
|
38
37
|
Requires-Dist: colorama<0.4.7,>=0.4.6
|
|
39
38
|
Requires-Dist: cryptography>=50.0.2
|
|
40
39
|
Requires-Dist: fastapi[all]>=0.162.0
|
|
@@ -143,6 +142,18 @@ make setup
|
|
|
143
142
|
make test
|
|
144
143
|
```
|
|
145
144
|
|
|
145
|
+
## Remote values
|
|
146
|
+
|
|
147
|
+
Queue and task calls use finite JSON-compatible arguments, return values,
|
|
148
|
+
and stream chunks. JSON conversion turns tuples into lists and supported
|
|
149
|
+
dictionary keys into strings, including in same-worker remote calls.
|
|
150
|
+
Bytes, custom objects, circular references, and non-finite numbers produce
|
|
151
|
+
explicit errors. `.local()` remains a direct Python call.
|
|
152
|
+
|
|
153
|
+
Successful function responses contain `json_result`, including for a null
|
|
154
|
+
return value. SDK and worker runtimes share the JSON protocol validators;
|
|
155
|
+
use compatible package releases when building worker images.
|
|
156
|
+
|
|
146
157
|
## ⚡ | Serverless Worker (SDK)
|
|
147
158
|
|
|
148
159
|
This python package can also be used to create a serverless worker that can be deployed to Runpod as a custom endpoint API.
|
|
@@ -84,6 +84,18 @@ make setup
|
|
|
84
84
|
make test
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
## Remote values
|
|
88
|
+
|
|
89
|
+
Queue and task calls use finite JSON-compatible arguments, return values,
|
|
90
|
+
and stream chunks. JSON conversion turns tuples into lists and supported
|
|
91
|
+
dictionary keys into strings, including in same-worker remote calls.
|
|
92
|
+
Bytes, custom objects, circular references, and non-finite numbers produce
|
|
93
|
+
explicit errors. `.local()` remains a direct Python call.
|
|
94
|
+
|
|
95
|
+
Successful function responses contain `json_result`, including for a null
|
|
96
|
+
return value. SDK and worker runtimes share the JSON protocol validators;
|
|
97
|
+
use compatible package releases when building worker images.
|
|
98
|
+
|
|
87
99
|
## ⚡ | Serverless Worker (SDK)
|
|
88
100
|
|
|
89
101
|
This python package can also be used to create a serverless worker that can be deployed to Runpod as a custom endpoint API.
|
|
@@ -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.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Runpod CLI
|
|
2
|
+
|
|
3
|
+
Note: This CLI is not the same as runpodctl and provides a different set of features.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# Auth
|
|
7
|
+
rp login
|
|
8
|
+
|
|
9
|
+
# Flash apps
|
|
10
|
+
rp flash init my-app
|
|
11
|
+
rp flash dev main.py
|
|
12
|
+
rp flash deploy
|
|
13
|
+
rp flash app list
|
|
14
|
+
rp flash env list --app my-app
|
|
15
|
+
rp flash undeploy --app my-app
|
|
16
|
+
|
|
17
|
+
# SSH
|
|
18
|
+
rp ssh add # add a key to your account
|
|
19
|
+
rp ssh list # list account keys
|
|
20
|
+
rp ssh POD_ID # open a terminal on a pod
|
|
21
|
+
|
|
22
|
+
# Pods
|
|
23
|
+
rp pod list
|
|
24
|
+
rp pod create
|
|
25
|
+
rp pod connect POD_ID
|
|
26
|
+
|
|
27
|
+
# Network volume files
|
|
28
|
+
rp volume upload VOLUME ./dataset datasets/dataset
|
|
29
|
+
rp volume download VOLUME outputs/ ./outputs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Overview
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
rp --help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Authenticate
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
rp login # browser approval, then paste the key
|
|
42
|
+
rp login --api-key YOUR_KEY # store a key directly
|
|
43
|
+
rp login --profile staging # save browser credentials in a named profile
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`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.
|
|
47
|
+
`rp login` needs an interactive terminal. In CI or scripts, set `RUNPOD_API_KEY` or use `rp login --api-key`.
|
|
48
|
+
|
|
49
|
+
Credentials are stored in `~/.runpod/config.toml`.
|
|
50
|
+
Use `--profile NAME` with either login method to update only that profile; the default is `default`.
|
|
51
|
+
Set `RUNPOD_PROFILE=NAME` to select the profile for all commands and for `runpod` in Python; `--profile` overrides it, and `RUNPOD_API_KEY` overrides any stored profile.
|
|
52
|
+
|
|
53
|
+
### Flash apps
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
rp flash init my-app # scaffold a project
|
|
57
|
+
rp flash dev main.py # run a live development session
|
|
58
|
+
rp flash deploy # deploy the current project
|
|
59
|
+
rp flash app list # list deployed apps
|
|
60
|
+
rp flash env list --app my-app # list an app's environments
|
|
61
|
+
rp flash undeploy --app my-app # delete an environment's endpoints
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
App discovery imports modules synchronously. Keep blocking work inside an
|
|
65
|
+
entrypoint or a resource function; interrupt discovery if an import blocks.
|
|
66
|
+
|
|
67
|
+
On Ctrl-C, a dev session waits for its running local entrypoint to finish before
|
|
68
|
+
deleting endpoints. Blocking entrypoint code can delay shutdown. Cleanup failures
|
|
69
|
+
exit with an error and report the endpoint IDs that still need deletion.
|
|
70
|
+
|
|
71
|
+
### Network volume files
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
rp volume upload VOLUME ./model.safetensors models/
|
|
75
|
+
rp volume upload VOLUME ./dataset datasets/dataset
|
|
76
|
+
rp volume download VOLUME models/model.safetensors
|
|
77
|
+
rp volume download VOLUME outputs/ ./outputs
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Transfers use the volume's S3-compatible API, so they need S3 API keys
|
|
81
|
+
(separate from the API key): set `RUNPOD_S3_ACCESS_KEY` and
|
|
82
|
+
`RUNPOD_S3_SECRET_KEY`, pass `--s3-access-key` / `--s3-secret-key`, or save
|
|
83
|
+
`s3_access_key` / `s3_secret_key` in `~/.runpod/config.toml`. The CLI prompts
|
|
84
|
+
for missing keys and offers to save them. You can omit `VOLUME` in an
|
|
85
|
+
interactive session to pick a volume from a list. The
|
|
86
|
+
volume must be in a [datacenter with the S3 API](https://docs.runpod.io/storage/s3-api).
|
|
87
|
+
`VOLUME` is the volume's ID or name, looked up with your API key.
|
|
88
|
+
|
|
89
|
+
To resume an interrupted transfer, run the same command again. Files that
|
|
90
|
+
are unchanged (same size, destination not older) are skipped, a large upload
|
|
91
|
+
reuses the parts it already sent, and a large download keeps its finished
|
|
92
|
+
chunks in `<name>.rpdownload`. `--force` transfers every file. Parts of an
|
|
93
|
+
upload that is never resumed stay in the volume's hidden `.s3compat_uploads`
|
|
94
|
+
folder and count toward its size.
|
|
95
|
+
|
|
96
|
+
The same transfers are available from Python:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from runpod import NetworkVolume
|
|
100
|
+
|
|
101
|
+
volume = NetworkVolume("my-volume")
|
|
102
|
+
volume.upload("./dataset", "datasets/dataset")
|
|
103
|
+
volume.download("outputs/", "./outputs")
|
|
104
|
+
```
|
|
@@ -8,10 +8,12 @@ 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
|
|
|
15
|
+
Set `RUNPOD_PROFILE=staging` to use that profile for every command, including `rp login`. `--profile` takes precedence over `RUNPOD_PROFILE`, and `RUNPOD_API_KEY` takes precedence over any stored profile.
|
|
16
|
+
|
|
15
17
|
### SSH
|
|
16
18
|
|
|
17
19
|
Add an SSH key to your account by running `rp ssh add`. To use an existing key pass `--key` or `--key-file`. Keys are stored in `~/.runpod/ssh/`. If no key is specified a new one is generated and stored.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
The Runpod python library is a powerful library providing SDK functions, API access, and CLI commands for interacting with the Runpod platform.
|
|
4
|
+
|
|
5
|
+
## Credentials File
|
|
6
|
+
|
|
7
|
+
`rp login` stores your API key in `~/.runpod/config.toml`, one table per profile:
|
|
8
|
+
|
|
9
|
+
```toml
|
|
10
|
+
[default]
|
|
11
|
+
api_key = "YOUR_RUNPOD_API_KEY"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Profile
|
|
15
|
+
|
|
16
|
+
By default, credentials are stored under and read from the `default` profile. To store a key under another profile, pass `--profile` to `rp login`:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
rp login --profile my_profile
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
To use that profile, set the `RUNPOD_PROFILE` environment variable before importing `runpod`. The library reads the profile and its API key once, at import time.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
export RUNPOD_PROFILE=my_profile
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`RUNPOD_API_KEY`, when set, takes precedence over any stored profile. Inside a script, you can also set the key directly:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import runpod
|
|
32
|
+
|
|
33
|
+
runpod.api_key = "YOUR_RUNPOD_API_KEY"
|
|
34
|
+
```
|
|
@@ -54,6 +54,7 @@ _LAZY_ATTRS = {
|
|
|
54
54
|
"Model": "runpod.apps",
|
|
55
55
|
"NetworkVolume": "runpod.apps",
|
|
56
56
|
"Queue": "runpod.apps",
|
|
57
|
+
"Task": "runpod.apps",
|
|
57
58
|
"Secret": "runpod.apps",
|
|
58
59
|
"Volume": "runpod.apps",
|
|
59
60
|
"is_local": "runpod.apps",
|
|
@@ -86,6 +87,18 @@ __all__ = [
|
|
|
86
87
|
]
|
|
87
88
|
|
|
88
89
|
|
|
90
|
+
DEFAULT_PROFILE = "default"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _selected_profile() -> str:
|
|
94
|
+
"""credential profile in ~/.runpod/config.toml to use.
|
|
95
|
+
|
|
96
|
+
set RUNPOD_PROFILE to pick a named profile (saved with
|
|
97
|
+
`rp login --profile NAME`); unset or blank means "default".
|
|
98
|
+
"""
|
|
99
|
+
return os.environ.get("RUNPOD_PROFILE", "").strip() or DEFAULT_PROFILE
|
|
100
|
+
|
|
101
|
+
|
|
89
102
|
def _load_api_key():
|
|
90
103
|
"""resolve the api key from the environment, then stored credentials.
|
|
91
104
|
|
|
@@ -102,7 +115,7 @@ def _load_api_key():
|
|
|
102
115
|
).get_credentials(profile)
|
|
103
116
|
except Exception: # noqa: BLE001 - no stored key is a valid state
|
|
104
117
|
return None
|
|
105
|
-
return creds
|
|
118
|
+
return creds.get("api_key") if creds else None
|
|
106
119
|
|
|
107
120
|
|
|
108
121
|
def __getattr__(name):
|
|
@@ -118,7 +131,7 @@ def __getattr__(name):
|
|
|
118
131
|
# ------------------------------- Config Paths ------------------------------- #
|
|
119
132
|
SSH_KEY_PATH = os.path.expanduser("~/.runpod/ssh")
|
|
120
133
|
|
|
121
|
-
profile =
|
|
134
|
+
profile = _selected_profile() # pylint: disable=invalid-name
|
|
122
135
|
|
|
123
136
|
api_key = _load_api_key() # pylint: disable=invalid-name
|
|
124
137
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Service hosts used to build console, proxy, and endpoint URLs.
|
|
2
|
+
|
|
3
|
+
Each host can be overridden with an environment variable, which is read on
|
|
4
|
+
every call so a change takes effect without re-importing ``runpod``. Unset or
|
|
5
|
+
blank values fall back to the public Runpod hosts.
|
|
6
|
+
|
|
7
|
+
RUNPOD_CONSOLE_URL web console base URL (default https://console.runpod.io)
|
|
8
|
+
RUNPOD_PROXY_DOMAIN pod HTTP proxy domain (default proxy.runpod.net)
|
|
9
|
+
RUNPOD_ENDPOINT_BASE_URL serverless base URL (default https://api.runpod.ai/v2)
|
|
10
|
+
|
|
11
|
+
The endpoint domain is the host of ``RUNPOD_ENDPOINT_BASE_URL``. When that is
|
|
12
|
+
unset or blank, a non-blank ``runpod.endpoint_url_base`` set programmatically
|
|
13
|
+
is used instead.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> proxy_url("abc123", 8080)
|
|
17
|
+
'https://abc123-8080.proxy.runpod.net'
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
from typing import Union
|
|
22
|
+
|
|
23
|
+
DEFAULT_CONSOLE_URL = "https://console.runpod.io"
|
|
24
|
+
DEFAULT_PROXY_DOMAIN = "proxy.runpod.net"
|
|
25
|
+
DEFAULT_ENDPOINT_BASE_URL = "https://api.runpod.ai/v2"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _env(name: str, default: str) -> str:
|
|
29
|
+
value = os.environ.get(name, "").strip()
|
|
30
|
+
return value or default
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def console_url() -> str:
|
|
34
|
+
"""Web console base URL, without a trailing slash."""
|
|
35
|
+
return _env("RUNPOD_CONSOLE_URL", DEFAULT_CONSOLE_URL).rstrip("/")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def proxy_url(pod_id: str, port: Union[int, str]) -> str:
|
|
39
|
+
"""Public HTTP proxy URL for ``port`` on ``pod_id``."""
|
|
40
|
+
domain = _env("RUNPOD_PROXY_DOMAIN", DEFAULT_PROXY_DOMAIN).strip("/")
|
|
41
|
+
return f"https://{pod_id}-{port}.{domain}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def endpoint_domain() -> str:
|
|
45
|
+
"""Host of the serverless data plane, e.g. ``api.runpod.ai``.
|
|
46
|
+
|
|
47
|
+
Load-balanced endpoints are served on ``<endpoint_id>.<endpoint_domain>``.
|
|
48
|
+
"""
|
|
49
|
+
import runpod
|
|
50
|
+
|
|
51
|
+
module_base = (runpod.endpoint_url_base or "").strip()
|
|
52
|
+
base = _env("RUNPOD_ENDPOINT_BASE_URL", module_base or DEFAULT_ENDPOINT_BASE_URL)
|
|
53
|
+
host = base.split("://", 1)[-1]
|
|
54
|
+
return host.split("/", 1)[0]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""finite json validation and wire-value conversion for callers and workers."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _json_text(value: Any, label: str) -> str:
|
|
8
|
+
try:
|
|
9
|
+
return json.dumps(value, allow_nan=False)
|
|
10
|
+
except (TypeError, ValueError, RecursionError) as exc:
|
|
11
|
+
raise TypeError(f"{label} must be json-serializable: {exc}") from exc
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def validate_json(value: Any, label: str = "value") -> None:
|
|
15
|
+
"""require values encodable as finite json."""
|
|
16
|
+
_json_text(value, label)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def normalize_json(value: Any, label: str = "value") -> Any:
|
|
20
|
+
"""apply json's container and key conversions without a network roundtrip."""
|
|
21
|
+
return json.loads(_json_text(value, label))
|
|
@@ -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.0
|
|
22
|
-
__version_tuple__ = version_tuple = (2,
|
|
21
|
+
__version__ = version = '2.2.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 2, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g89d72b8c4'
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Filesystem and checksum helpers for resumable transfers.
|
|
2
|
+
|
|
3
|
+
This private module keeps disk state and path safety details out of the S3
|
|
4
|
+
request orchestration. network_volume_transfer re-exports the error type.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import unicodedata
|
|
11
|
+
from pathlib import Path, PurePosixPath
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
MIB = 1024 * 1024
|
|
15
|
+
PARTIAL_SUFFIX = ".rpdownload"
|
|
16
|
+
SIDECAR_SUFFIX = ".rpdownload.json"
|
|
17
|
+
# every file a download writes next to its target
|
|
18
|
+
TRANSFER_SUFFIXES = (PARTIAL_SUFFIX, SIDECAR_SUFFIX, SIDECAR_SUFFIX + ".tmp")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NetworkVolumeTransferError(RuntimeError):
|
|
22
|
+
"""a transfer failed. `resumable` says whether re-running can help."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, message: str, resumable: bool = False):
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
self.resumable = resumable
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _read_json(path: Path) -> Optional[Dict[str, Any]]:
|
|
30
|
+
try:
|
|
31
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
32
|
+
except (OSError, ValueError):
|
|
33
|
+
return None
|
|
34
|
+
return data if isinstance(data, dict) else None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _write_json(path: Path, data: Dict[str, Any]) -> None:
|
|
38
|
+
"""replace path atomically, synced, so a crash leaves the old state or
|
|
39
|
+
the new one."""
|
|
40
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
tmp = path.with_name(path.name + ".tmp")
|
|
42
|
+
with open(tmp, "w", encoding="utf-8") as handle:
|
|
43
|
+
json.dump(data, handle)
|
|
44
|
+
handle.flush()
|
|
45
|
+
os.fsync(handle.fileno())
|
|
46
|
+
os.replace(tmp, path)
|
|
47
|
+
_sync_dir(path.parent)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _sync_dir(path: Path) -> None:
|
|
51
|
+
"""make a rename in path survive a power loss. best effort: windows
|
|
52
|
+
cannot open a directory."""
|
|
53
|
+
try:
|
|
54
|
+
fd = os.open(path, os.O_RDONLY)
|
|
55
|
+
except OSError:
|
|
56
|
+
return
|
|
57
|
+
try:
|
|
58
|
+
os.fsync(fd)
|
|
59
|
+
except OSError:
|
|
60
|
+
# some filesystems refuse fsync on a directory; the rename stands
|
|
61
|
+
pass
|
|
62
|
+
finally:
|
|
63
|
+
os.close(fd)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _md5_hex(path: Path, offset: int, length: int) -> str:
|
|
67
|
+
digest = hashlib.md5() # noqa: S324 - etag comparison, not security
|
|
68
|
+
with open(path, "rb") as handle:
|
|
69
|
+
handle.seek(offset)
|
|
70
|
+
while length > 0 and (block := handle.read(min(8 * MIB, length))):
|
|
71
|
+
digest.update(block)
|
|
72
|
+
length -= len(block)
|
|
73
|
+
return digest.hexdigest()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _etag(value: Any) -> str:
|
|
77
|
+
return str(value or "").strip('"')
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _is_md5(etag: str) -> bool:
|
|
81
|
+
return len(etag) == 32 and all(c in "0123456789abcdef" for c in etag.lower())
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _multipart_etag(parts: List[Dict[str, Any]]) -> str:
|
|
85
|
+
"""the etag s3 (and runpod, verified) gives an object assembled from
|
|
86
|
+
parts: md5 of the parts' binary md5s, then "-<count>". "" when a part
|
|
87
|
+
etag is not an md5."""
|
|
88
|
+
etags = [_etag(part["ETag"]) for part in parts]
|
|
89
|
+
if not all(_is_md5(etag) for etag in etags):
|
|
90
|
+
return ""
|
|
91
|
+
digest = hashlib.md5(b"".join(bytes.fromhex(etag) for etag in etags)) # noqa: S324
|
|
92
|
+
return f"{digest.hexdigest()}-{len(parts)}"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _same_version(a: os.stat_result, b: os.stat_result) -> bool:
|
|
96
|
+
return a.st_size == b.st_size and a.st_mtime_ns == b.st_mtime_ns
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _not_older(a: float, b: float) -> bool:
|
|
100
|
+
"""a >= b in whole seconds; s3 dates carry no finer precision."""
|
|
101
|
+
return int(a) >= int(b)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _check_remote_path(remote_path: Optional[str]) -> None:
|
|
105
|
+
if remote_path and ".." in remote_path.replace("\\", "/").split("/"):
|
|
106
|
+
raise ValueError(f"remote path {remote_path!r} must not contain '..'")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _check_local_names(targets: List[Path], root: Path) -> None:
|
|
110
|
+
"""refuse targets that would share a path on disk: two that are one name
|
|
111
|
+
on a case-insensitive or normalizing filesystem ("Model.bin" and
|
|
112
|
+
"model.bin"), a file whose name is also a folder another file lives in
|
|
113
|
+
("a" beside "A/b"), or a file whose partial, sidecar or sidecar temp name
|
|
114
|
+
is also a target or a folder one lives in ("x" beside "x.rpdownload/y").
|
|
115
|
+
names are compared nfc-normalized (apfs ignores normalization) and case
|
|
116
|
+
folded, which can refuse a listing a case-sensitive filesystem would
|
|
117
|
+
take: the safe direction."""
|
|
118
|
+
|
|
119
|
+
def fold(path: PurePosixPath) -> str:
|
|
120
|
+
return unicodedata.normalize("NFC", str(path)).casefold()
|
|
121
|
+
|
|
122
|
+
rels = [PurePosixPath(target.relative_to(root).as_posix()) for target in targets]
|
|
123
|
+
files: Dict[str, PurePosixPath] = {}
|
|
124
|
+
dirs: Dict[str, PurePosixPath] = {}
|
|
125
|
+
for rel in rels:
|
|
126
|
+
if fold(rel) in files:
|
|
127
|
+
raise NetworkVolumeTransferError(
|
|
128
|
+
f"cannot download both {files[fold(rel)]} and {rel}: they would be one "
|
|
129
|
+
"file on a case-insensitive or normalizing filesystem"
|
|
130
|
+
)
|
|
131
|
+
files[fold(rel)] = rel
|
|
132
|
+
dirs.update({fold(parent): rel for parent in rel.parents if str(parent) != "."})
|
|
133
|
+
for rel in rels:
|
|
134
|
+
if fold(rel) in dirs:
|
|
135
|
+
raise NetworkVolumeTransferError(
|
|
136
|
+
f"cannot download both {rel} and {dirs[fold(rel)]}: "
|
|
137
|
+
"the first would have to be a file and a folder"
|
|
138
|
+
)
|
|
139
|
+
for suffix in TRANSFER_SUFFIXES:
|
|
140
|
+
twin = fold(rel.with_name(rel.name + suffix))
|
|
141
|
+
other = files.get(twin) or dirs.get(twin)
|
|
142
|
+
if other is not None:
|
|
143
|
+
raise NetworkVolumeTransferError(
|
|
144
|
+
f"cannot download both {rel} and {other}: "
|
|
145
|
+
f"{rel.name + suffix} is used for the first's partial download"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _safe_target(root: Path, rel: str) -> Path:
|
|
150
|
+
"""local path for a remote relative key, refusing anything outside root.
|
|
151
|
+
"." and empty segments are refused too: "a/./b" would alias "a/b"."""
|
|
152
|
+
segments = rel.split("/")
|
|
153
|
+
if (
|
|
154
|
+
rel.startswith("/")
|
|
155
|
+
or "\\" in rel
|
|
156
|
+
or any(seg in ("..", ".", "") for seg in segments)
|
|
157
|
+
or ":" in segments[0]
|
|
158
|
+
):
|
|
159
|
+
raise NetworkVolumeTransferError(f"refusing unsafe remote path: {rel!r}")
|
|
160
|
+
target = root.joinpath(*segments)
|
|
161
|
+
if root.resolve() not in target.resolve().parents:
|
|
162
|
+
raise NetworkVolumeTransferError(f"refusing unsafe remote path: {rel!r}")
|
|
163
|
+
# a symlinked folder would let two remote paths share one local file,
|
|
164
|
+
# which the name collision check cannot see
|
|
165
|
+
for depth in range(1, len(segments)):
|
|
166
|
+
if root.joinpath(*segments[:depth]).is_symlink():
|
|
167
|
+
raise NetworkVolumeTransferError(
|
|
168
|
+
f"{root.joinpath(*segments[:depth])} is a symlink; "
|
|
169
|
+
"download into a folder without symlinked subfolders"
|
|
170
|
+
)
|
|
171
|
+
return target
|