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.
Files changed (392) hide show
  1. {runpod-2.0.1.dev1 → runpod-2.2.0}/CHANGELOG.md +41 -0
  2. {runpod-2.0.1.dev1/runpod.egg-info → runpod-2.2.0}/PKG-INFO +13 -2
  3. {runpod-2.0.1.dev1 → runpod-2.2.0}/README.md +12 -0
  4. runpod-2.2.0/docs/api/handling_errors.md +69 -0
  5. runpod-2.2.0/docs/cli/references/command_line_interface.md +104 -0
  6. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/cli/start_here.md +3 -1
  7. runpod-2.2.0/docs/getting_started.md +34 -0
  8. {runpod-2.0.1.dev1 → runpod-2.2.0}/requirements.txt +0 -1
  9. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/__init__.py +15 -2
  10. runpod-2.2.0/runpod/_hosts.py +54 -0
  11. runpod-2.2.0/runpod/_serialization.py +21 -0
  12. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_version.py +3 -3
  13. runpod-2.2.0/runpod/_volume_transfer_utils.py +171 -0
  14. runpod-2.2.0/runpod/api/_rate_limit.py +259 -0
  15. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/graphql.py +4 -4
  16. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/mutations/apps.py +6 -0
  17. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/queries/apps.py +1 -2
  18. runpod-2.2.0/runpod/api/rest.py +279 -0
  19. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/__init__.py +2 -1
  20. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/api.py +56 -5
  21. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/app.py +11 -1
  22. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/auth.py +41 -29
  23. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/build.py +0 -1
  24. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/context.py +22 -1
  25. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/datacenter.py +16 -0
  26. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/deploy.py +127 -31
  27. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/handles.py +27 -5
  28. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/images.py +3 -5
  29. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/placement.py +8 -4
  30. runpod-2.2.0/runpod/apps/protocol.py +86 -0
  31. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/serialization.py +28 -35
  32. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/spec.py +32 -4
  33. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/stubs.py +31 -2
  34. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/targets.py +121 -59
  35. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/tasks.py +130 -82
  36. runpod-2.2.0/runpod/apps/utils/network.py +65 -0
  37. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/volume.py +96 -2
  38. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/config/functions.py +46 -18
  39. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/error.py +14 -0
  40. runpod-2.2.0/runpod/network_volume_transfer.py +981 -0
  41. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/console.py +9 -9
  42. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/endpoint.py +2 -1
  43. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/main.py +53 -14
  44. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/pod.py +2 -1
  45. runpod-2.2.0/runpod/rp_cli/volume.py +610 -0
  46. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_fastapi.py +14 -5
  47. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_http.py +15 -2
  48. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_job.py +10 -1
  49. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_scale.py +7 -2
  50. {runpod-2.0.1.dev1 → runpod-2.2.0/runpod.egg-info}/PKG-INFO +13 -2
  51. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod.egg-info/SOURCES.txt +17 -1
  52. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod.egg-info/requires.txt +0 -1
  53. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod.egg-info/scm_file_list.json +17 -1
  54. runpod-2.2.0/runpod.egg-info/scm_version.json +8 -0
  55. runpod-2.2.0/tests/e2e/test_network_volume_transfer_live.py +74 -0
  56. runpod-2.2.0/tests/test_api/test_rate_limit.py +811 -0
  57. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_api_client.py +37 -4
  58. runpod-2.2.0/tests/test_apps/test_auth.py +127 -0
  59. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_config_params.py +54 -0
  60. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_deploy.py +35 -4
  61. runpod-2.2.0/tests/test_apps/test_deployed_tasks.py +471 -0
  62. runpod-2.2.0/tests/test_apps/test_deployment_validation.py +345 -0
  63. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_dispatch.py +25 -9
  64. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_placement.py +44 -0
  65. runpod-2.2.0/tests/test_apps/test_runtime_call_validation.py +172 -0
  66. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_tasks.py +15 -13
  67. runpod-2.2.0/tests/test_apps/test_transport_parity.py +170 -0
  68. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_volume.py +16 -0
  69. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_console.py +7 -0
  70. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_deploy_command.py +28 -0
  71. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_endpoint_command.py +18 -0
  72. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_management_commands.py +67 -4
  73. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_pod_command.py +8 -0
  74. runpod-2.2.0/tests/test_hosts.py +67 -0
  75. runpod-2.2.0/tests/test_network_volume_transfer.py +2004 -0
  76. runpod-2.2.0/tests/test_profile.py +79 -0
  77. runpod-2.2.0/tests/test_serialization.py +231 -0
  78. runpod-2.2.0/tests/test_serverless/test_refresh_worker.py +640 -0
  79. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_rp_volume_cache.py +3 -0
  80. runpod-2.0.1.dev1/docs/api/handling_errors.md +0 -28
  81. runpod-2.0.1.dev1/docs/cli/references/command_line_interface.md +0 -61
  82. runpod-2.0.1.dev1/docs/getting_Started.md +0 -21
  83. runpod-2.0.1.dev1/runpod/api/rest.py +0 -185
  84. runpod-2.0.1.dev1/runpod/apps/protocol.py +0 -60
  85. runpod-2.0.1.dev1/runpod/apps/utils/network.py +0 -33
  86. runpod-2.0.1.dev1/runpod/rp_cli/volume.py +0 -298
  87. runpod-2.0.1.dev1/runpod.egg-info/scm_version.json +0 -8
  88. runpod-2.0.1.dev1/tests/test_apps/test_auth.py +0 -60
  89. {runpod-2.0.1.dev1 → runpod-2.2.0}/.dockerignore +0 -0
  90. {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  91. {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  92. {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/dependabot.yml +0 -0
  93. {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/tape_collection/cli_config.tape +0 -0
  94. {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/tape_collection/cli_help.tape +0 -0
  95. {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/tape_collection/cli_ssh.tape +0 -0
  96. {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/tests.json +0 -0
  97. {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/workflows/cd.yml +0 -0
  98. {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/workflows/ci.yml +0 -0
  99. {runpod-2.0.1.dev1 → runpod-2.2.0}/.github/workflows/vhs.yml +0 -0
  100. {runpod-2.0.1.dev1 → runpod-2.2.0}/.gitignore +0 -0
  101. {runpod-2.0.1.dev1 → runpod-2.2.0}/ARCHITECTURE.md +0 -0
  102. {runpod-2.0.1.dev1 → runpod-2.2.0}/CONTRIBUTING.md +0 -0
  103. {runpod-2.0.1.dev1 → runpod-2.2.0}/LICENSE +0 -0
  104. {runpod-2.0.1.dev1 → runpod-2.2.0}/MANIFEST.in +0 -0
  105. {runpod-2.0.1.dev1 → runpod-2.2.0}/Makefile +0 -0
  106. {runpod-2.0.1.dev1 → runpod-2.2.0}/SECURITY.md +0 -0
  107. {runpod-2.0.1.dev1 → runpod-2.2.0}/build_tools/compile_gpu_test.sh +0 -0
  108. {runpod-2.0.1.dev1 → runpod-2.2.0}/build_tools/gpu_test.c +0 -0
  109. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/api/queries.md +0 -0
  110. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/cli/demos/config.gif +0 -0
  111. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/cli/demos/help.gif +0 -0
  112. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/cli/demos/ssh.gif +0 -0
  113. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/cli/references/projects.md +0 -0
  114. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/cli/references/runpod.toml.md +0 -0
  115. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/images/env_var_location.png +0 -0
  116. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/functions/error_handling.md +0 -0
  117. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/functions/logging.md +0 -0
  118. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/gpu_binary_compilation.md +0 -0
  119. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/local_testing.md +0 -0
  120. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/utils/rp_cleanup.md +0 -0
  121. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/utils/rp_debugger.md +0 -0
  122. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/utils/rp_download.md +0 -0
  123. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/utils/rp_upload.md +0 -0
  124. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/utils/rp_validator.md +0 -0
  125. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/volume_cache.md +0 -0
  126. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/worker.md +0 -0
  127. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/worker_fitness_checks.md +0 -0
  128. {runpod-2.0.1.dev1 → runpod-2.2.0}/docs/serverless/worker_realtime.md +0 -0
  129. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/api/create_container_registry_auth.py +0 -0
  130. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/api/create_endpoint.py +0 -0
  131. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/api/create_template.py +0 -0
  132. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/api/get_endpoints.py +0 -0
  133. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/apps/README.md +0 -0
  134. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/apps/gpu_inference.py +0 -0
  135. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/apps/hello_world.py +0 -0
  136. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/apps/streaming.py +0 -0
  137. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/apps/train_and_eval.py +0 -0
  138. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/apps/web_service.py +0 -0
  139. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/endpoints/asyncio_job_request.py +0 -0
  140. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/endpoints/health.py +0 -0
  141. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/endpoints/run.py +0 -0
  142. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/endpoints/run_sync.py +0 -0
  143. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/endpoints/streaming.py +0 -0
  144. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/rest_wrapper.py +0 -0
  145. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/serverless/concurrent_handler.py +0 -0
  146. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/serverless/logger.py +0 -0
  147. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/serverless/simple_handler.py +0 -0
  148. {runpod-2.0.1.dev1 → runpod-2.2.0}/examples/serverless/volume_cache_handler.py +0 -0
  149. {runpod-2.0.1.dev1 → runpod-2.2.0}/pyproject.toml +0 -0
  150. {runpod-2.0.1.dev1 → runpod-2.2.0}/pytest.ini +0 -0
  151. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_binary_helpers.py +0 -0
  152. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_health/__init__.py +0 -0
  153. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_health/cuda.py +0 -0
  154. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_health/fitness.py +0 -0
  155. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_health/gpu.py +0 -0
  156. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_health/system.py +0 -0
  157. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_logger.py +0 -0
  158. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/_startup.py +0 -0
  159. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/agent.py +0 -0
  160. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/__init__.py +0 -0
  161. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/ctl_commands.py +0 -0
  162. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/mutations/__init__.py +0 -0
  163. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/mutations/container_register_auth.py +0 -0
  164. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/queries/__init__.py +0 -0
  165. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/api/queries/user.py +0 -0
  166. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/dev.py +0 -0
  167. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/discovery.py +0 -0
  168. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/discovery_state.py +0 -0
  169. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/entrypoint.py +0 -0
  170. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/errors.py +0 -0
  171. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/gpu.py +0 -0
  172. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/init.py +0 -0
  173. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/invoker.py +0 -0
  174. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/job.py +0 -0
  175. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/logs.py +0 -0
  176. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/manage.py +0 -0
  177. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/markers.py +0 -0
  178. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/model.py +0 -0
  179. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/monitor.py +0 -0
  180. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/registry.py +0 -0
  181. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/schedule.py +0 -0
  182. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/secret.py +0 -0
  183. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/shim.py +0 -0
  184. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/utils/__init__.py +0 -0
  185. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/utils/client.py +0 -0
  186. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/utils/events.py +0 -0
  187. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/utils/lookup.py +0 -0
  188. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/utils/names.py +0 -0
  189. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/apps/watch.py +0 -0
  190. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/__init__.py +0 -0
  191. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/entry.py +0 -0
  192. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/__init__.py +0 -0
  193. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/config/__init__.py +0 -0
  194. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/pod/__init__.py +0 -0
  195. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/pod/commands.py +0 -0
  196. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/ssh/__init__.py +0 -0
  197. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/ssh/commands.py +0 -0
  198. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/groups/ssh/functions.py +0 -0
  199. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/utils/__init__.py +0 -0
  200. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/utils/rp_info.py +0 -0
  201. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/utils/rp_runpodignore.py +0 -0
  202. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/utils/rp_userspace.py +0 -0
  203. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/cli/utils/ssh_cmd.py +0 -0
  204. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/endpoint/__init__.py +0 -0
  205. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/endpoint/asyncio/__init__.py +0 -0
  206. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/endpoint/asyncio/asyncio_runner.py +0 -0
  207. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/endpoint/helpers.py +0 -0
  208. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/endpoint/runner.py +0 -0
  209. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/http_client.py +0 -0
  210. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/__init__.py +0 -0
  211. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/api.py +0 -0
  212. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/catalog.py +0 -0
  213. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/compute.py +0 -0
  214. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/endpoint_config.py +0 -0
  215. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/logs.py +0 -0
  216. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/options.py +0 -0
  217. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/output.py +0 -0
  218. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/rp_cli/update.py +0 -0
  219. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/__init__.py +0 -0
  220. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/binaries/README.md +0 -0
  221. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/binaries/__init__.py +0 -0
  222. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/binaries/gpu_test +0 -0
  223. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/__init__.py +0 -0
  224. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_capture.py +0 -0
  225. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_fitness.py +0 -0
  226. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_gpu_fitness.py +0 -0
  227. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_handler.py +0 -0
  228. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_local.py +0 -0
  229. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_logger.py +0 -0
  230. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_ping.py +0 -0
  231. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_prestart.py +0 -0
  232. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_progress.py +0 -0
  233. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_system_fitness.py +0 -0
  234. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/rp_tips.py +0 -0
  235. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/modules/worker_state.py +0 -0
  236. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/__init__.py +0 -0
  237. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_cleanup.py +0 -0
  238. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_cuda.py +0 -0
  239. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_debugger.py +0 -0
  240. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_download.py +0 -0
  241. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_model_cache.py +0 -0
  242. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_ssrf.py +0 -0
  243. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_upload.py +0 -0
  244. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_validator.py +0 -0
  245. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/utils/rp_volume_cache.py +0 -0
  246. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/serverless/worker.py +0 -0
  247. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/user_agent.py +0 -0
  248. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod/version.py +0 -0
  249. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod.egg-info/dependency_links.txt +0 -0
  250. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod.egg-info/entry_points.txt +0 -0
  251. {runpod-2.0.1.dev1 → runpod-2.2.0}/runpod.egg-info/top_level.txt +0 -0
  252. {runpod-2.0.1.dev1 → runpod-2.2.0}/scripts/README.md +0 -0
  253. {runpod-2.0.1.dev1 → runpod-2.2.0}/scripts/benchmark_cold_start.sh +0 -0
  254. {runpod-2.0.1.dev1 → runpod-2.2.0}/scripts/compare_benchmarks.py +0 -0
  255. {runpod-2.0.1.dev1 → runpod-2.2.0}/setup.cfg +0 -0
  256. {runpod-2.0.1.dev1 → runpod-2.2.0}/setup.py +0 -0
  257. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/__init__.py +0 -0
  258. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/__init__.py +0 -0
  259. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/apps_live_smoke.py +0 -0
  260. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/conftest.py +0 -0
  261. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/e2e_provisioner.py +0 -0
  262. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/01_hello_world.py +0 -0
  263. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/02_invocation_styles.py +0 -0
  264. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/03_gpu.py +0 -0
  265. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/04_dependencies.py +0 -0
  266. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/05_api_service.py +0 -0
  267. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/06_tasks.py +0 -0
  268. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/07_pipelines.py +0 -0
  269. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/08_volumes.py +0 -0
  270. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/09_secrets.py +0 -0
  271. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/10_cached_models.py +0 -0
  272. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/11_train_eval.py +0 -0
  273. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/12_custom_image.py +0 -0
  274. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/13_streaming.py +0 -0
  275. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/README.md +0 -0
  276. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/examples/run_all.sh +0 -0
  277. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/fixtures/cold_start/handler.py +0 -0
  278. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/fixtures/cold_start/pyproject.toml +0 -0
  279. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/matrix/main.py +0 -0
  280. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/matrix/run.py +0 -0
  281. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/task_pod_smoke.py +0 -0
  282. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/test_cold_start.py +0 -0
  283. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/test_mock_worker.py +0 -0
  284. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/e2e/tests.json +0 -0
  285. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/fixtures/mock_gpu_test +0 -0
  286. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_agent.py +0 -0
  287. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_api/__init__.py +0 -0
  288. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_api/test_ctl_commands.py +0 -0
  289. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_api/test_rest.py +0 -0
  290. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/__init__.py +0 -0
  291. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/conftest.py +0 -0
  292. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_agent_auth.py +0 -0
  293. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_app.py +0 -0
  294. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_build.py +0 -0
  295. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_compat_lock.py +0 -0
  296. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_dev.py +0 -0
  297. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_discovery.py +0 -0
  298. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_gpu_catalog.py +0 -0
  299. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_images.py +0 -0
  300. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_job.py +0 -0
  301. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_logs.py +0 -0
  302. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_manage.py +0 -0
  303. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_markers.py +0 -0
  304. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_model.py +0 -0
  305. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_monitor.py +0 -0
  306. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_registry.py +0 -0
  307. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_retry.py +0 -0
  308. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_secret.py +0 -0
  309. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_shim.py +0 -0
  310. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_targets.py +0 -0
  311. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_apps/test_watch.py +0 -0
  312. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/__init__.py +0 -0
  313. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_agent_login.py +0 -0
  314. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_catalog_command.py +0 -0
  315. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_api.py +0 -0
  316. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_groups/__init__.py +0 -0
  317. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_groups/test_config_functions.py +0 -0
  318. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_groups/test_pod_commands.py +0 -0
  319. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_groups/test_ssh_commands.py +0 -0
  320. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_groups/test_ssh_dispatch.py +0 -0
  321. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_groups/test_ssh_functions.py +0 -0
  322. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_logs.py +0 -0
  323. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_options.py +0 -0
  324. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_output.py +0 -0
  325. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_utils/__init__.py +0 -0
  326. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_utils/test_info.py +0 -0
  327. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_utils/test_runpodignore.py +0 -0
  328. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_utils/test_ssh_cmd.py +0 -0
  329. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_cli_utils/test_userspace.py +0 -0
  330. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_compute.py +0 -0
  331. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_init_command.py +0 -0
  332. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_update_command.py +0 -0
  333. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_cli/test_volume_command.py +0 -0
  334. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_endpoint/__init__.py +0 -0
  335. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_endpoint/test_asyncio_init.py +0 -0
  336. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_endpoint/test_asyncio_runner.py +0 -0
  337. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_endpoint/test_init.py +0 -0
  338. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_endpoint/test_runner.py +0 -0
  339. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_error.py +0 -0
  340. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_init.py +0 -0
  341. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_performance/__init__.py +0 -0
  342. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_performance/test_cold_start.py +0 -0
  343. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/__init__.py +0 -0
  344. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/local_sim/.env_example +0 -0
  345. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/local_sim/Makefile +0 -0
  346. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/local_sim/localhost.py +0 -0
  347. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/local_sim/worker.py +0 -0
  348. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_capture.py +0 -0
  349. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_init.py +0 -0
  350. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_integration_worker_state.py +0 -0
  351. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/__init__.py +0 -0
  352. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fastapi.py +0 -0
  353. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/__init__.py +0 -0
  354. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/conftest.py +0 -0
  355. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_force_kill.py +0 -0
  356. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_gpu_checks.py +0 -0
  357. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_gpu_integration.py +0 -0
  358. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_registration.py +0 -0
  359. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_safe_startup.py +0 -0
  360. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_startup.py +0 -0
  361. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_fitness/test_system_checks.py +0 -0
  362. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_handler.py +0 -0
  363. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_http.py +0 -0
  364. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_job.py +0 -0
  365. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_local.py +0 -0
  366. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_logger.py +0 -0
  367. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_ping.py +0 -0
  368. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_progress.py +0 -0
  369. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_scale.py +0 -0
  370. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_state.py +0 -0
  371. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_modules/test_tips.py +0 -0
  372. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_prestart.py +0 -0
  373. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_prestart_lifecycle.py +0 -0
  374. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_rp_scale.py +0 -0
  375. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_scale_mirror.py +0 -0
  376. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/__init__.py +0 -0
  377. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_cleanup.py +0 -0
  378. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_cuda.py +0 -0
  379. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_debugger.py +0 -0
  380. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_download.py +0 -0
  381. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_model_cache.py +0 -0
  382. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_ssrf.py +0 -0
  383. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_upload.py +0 -0
  384. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils/test_validate.py +0 -0
  385. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_utils_init.py +0 -0
  386. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_serverless/test_worker.py +0 -0
  387. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_shared/__init__.py +0 -0
  388. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_shared/test_auth.py +0 -0
  389. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_user_agent.py +0 -0
  390. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_version.py +0 -0
  391. {runpod-2.0.1.dev1 → runpod-2.2.0}/tests/test_whatever.py +0 -0
  392. {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.1.dev1
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 approval) or `rp login --api-key YOUR_KEY`. Credentials are stored in `~/.runpod/config.toml`.
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
+ ```
@@ -5,7 +5,6 @@ backoff >= 2.2.1
5
5
  boto3 >= 1.43.118
6
6
  certifi >= 2026.10.19
7
7
  click >= 8.5.0
8
- cloudpickle >= 3.1.2
9
8
  colorama >= 0.4.6, < 0.4.7
10
9
  cryptography >= 50.0.2
11
10
  fastapi[all] >= 0.162.0
@@ -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["api_key"] if creds else None
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 = "default" # pylint: disable=invalid-name
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.1.dev1'
22
- __version_tuple__ = version_tuple = (2, 0, 1, 'dev1')
21
+ __version__ = version = '2.2.0'
22
+ __version_tuple__ = version_tuple = (2, 2, 0)
23
23
 
24
- __commit_id__ = commit_id = 'ga4ba07898'
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