fastapi-cloud-cli 0.24.0__tar.gz → 0.25.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 (163) hide show
  1. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/PKG-INFO +2 -2
  2. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/pyproject.toml +7 -3
  3. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/__init__.py +1 -0
  4. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/_app.py +35 -0
  5. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/api/__init__.py +15 -0
  6. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/api/_errors.py +136 -0
  7. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/api/_models.py +140 -0
  8. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/api/_retry.py +91 -0
  9. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/api/client.py +308 -0
  10. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/cli.py +78 -0
  11. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/_auth.py +41 -0
  12. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/_flow.py +1 -1
  13. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/apps/__init__.py +3 -0
  14. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/apps/_app.py +6 -0
  15. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/apps/create.py +191 -0
  16. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/apps/get.py +42 -46
  17. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/apps/link.py +6 -2
  18. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/apps/list.py +48 -53
  19. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/apps/unlink.py +4 -0
  20. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/apps/update.py +117 -0
  21. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/auth/__init__.py +3 -0
  22. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/auth/_app.py +6 -0
  23. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/auth/wait.py +3 -1
  24. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/ci/__init__.py +3 -0
  25. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/ci/_app.py +6 -0
  26. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/ci/print_workflow.py +2 -0
  27. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/deploy/cloud.py +1 -1
  28. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/deploy/command.py +4 -1
  29. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/deploy/configure.py +1 -1
  30. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/deploy/upload.py +1 -1
  31. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/deploy/wait.py +3 -3
  32. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/deployments.py +91 -110
  33. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/domains/__init__.py +3 -0
  34. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/domains/_app.py +6 -0
  35. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/domains/_setup.py +211 -0
  36. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/domains/_shared.py +40 -0
  37. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/domains/add.py +186 -0
  38. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/domains/get.py +120 -0
  39. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/domains/list.py +77 -0
  40. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/domains/remove.py +174 -0
  41. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/domains/rendering.py +364 -0
  42. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/domains/restart.py +150 -0
  43. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/env/__init__.py +3 -0
  44. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/env/_app.py +6 -0
  45. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/env/_shared.py +1 -1
  46. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/env/delete.py +188 -0
  47. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/env/get.py +133 -0
  48. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/env/list.py +25 -31
  49. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/env/set.py +49 -54
  50. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/integrations/providers/__init__.py +5 -0
  51. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/integrations/providers/_app.py +6 -0
  52. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/integrations/providers/list.py +34 -39
  53. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/integrations/resources/__init__.py +5 -0
  54. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/integrations/resources/_app.py +6 -0
  55. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/integrations/resources/connect.py +55 -60
  56. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/integrations/resources/disconnect.py +244 -0
  57. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/integrations/resources/get.py +34 -38
  58. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/integrations/resources/list.py +30 -35
  59. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/login.py +6 -1
  60. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/logout.py +2 -0
  61. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/logs.py +34 -36
  62. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/setup_ci.py +209 -220
  63. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/teams/__init__.py +3 -0
  64. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/teams/_app.py +6 -0
  65. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/teams/get.py +24 -28
  66. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/teams/__init__.py → fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/teams/list.py +22 -41
  67. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/tokens/__init__.py +3 -0
  68. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/tokens/_app.py +6 -0
  69. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/tokens/create.py +44 -48
  70. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/tokens/delete.py +97 -0
  71. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/tokens/list.py +27 -32
  72. fastapi_cloud_cli-0.25.0/src/fastapi_cloud_cli/commands/whoami.py +66 -0
  73. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/teams.py +1 -1
  74. fastapi_cloud_cli-0.25.0/tests/domains/test_cli.py +1381 -0
  75. fastapi_cloud_cli-0.25.0/tests/domains/test_setup.py +190 -0
  76. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/integrations/providers/test_list.py +30 -15
  77. fastapi_cloud_cli-0.25.0/tests/integrations/resources/__init__.py +0 -0
  78. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/integrations/resources/test_connect.py +22 -17
  79. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/integrations/resources/test_disconnect.py +58 -28
  80. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/integrations/resources/test_get.py +36 -20
  81. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/integrations/resources/test_list.py +19 -14
  82. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_api_client.py +7 -7
  83. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli_apps.py +70 -31
  84. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli_deploy.py +5 -5
  85. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli_deployments.py +36 -30
  86. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli_link.py +1 -1
  87. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli_login.py +5 -5
  88. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli_teams.py +33 -19
  89. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli_tokens.py +18 -18
  90. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli_whoami.py +3 -3
  91. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_deploy_utils.py +1 -1
  92. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_env_delete.py +40 -23
  93. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_env_get.py +32 -14
  94. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_env_list.py +39 -26
  95. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_env_set.py +96 -16
  96. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_logs.py +2 -2
  97. fastapi_cloud_cli-0.25.0/tests/utils.py +115 -0
  98. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/__init__.py +0 -1
  99. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/cli.py +0 -90
  100. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/apps/__init__.py +0 -23
  101. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/apps/create.py +0 -196
  102. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/apps/update.py +0 -122
  103. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/auth/__init__.py +0 -14
  104. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/ci/__init__.py +0 -15
  105. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/env/__init__.py +0 -17
  106. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/env/delete.py +0 -194
  107. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/env/get.py +0 -139
  108. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/providers/__init__.py +0 -11
  109. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/__init__.py +0 -19
  110. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/disconnect.py +0 -250
  111. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/tokens/__init__.py +0 -15
  112. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/tokens/delete.py +0 -102
  113. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/whoami.py +0 -68
  114. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/utils/api.py +0 -541
  115. fastapi_cloud_cli-0.24.0/tests/utils.py +0 -62
  116. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/LICENSE +0 -0
  117. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/README.md +0 -0
  118. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/scripts/add_latest_release_date.py +0 -0
  119. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/scripts/format.sh +0 -0
  120. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/scripts/lint.sh +0 -0
  121. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/scripts/prepare_release.py +0 -0
  122. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/scripts/test-cov-html.sh +0 -0
  123. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/scripts/test.sh +0 -0
  124. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/__main__.py +0 -0
  125. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/__init__.py +0 -0
  126. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/deploy/__init__.py +0 -0
  127. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/deploy/archive.py +0 -0
  128. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/integrations/__init__.py +0 -0
  129. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/commands/integrations/resources/providers.py +0 -0
  130. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/config.py +0 -0
  131. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/logging.py +0 -0
  132. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/py.typed +0 -0
  133. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/__init__.py +0 -0
  134. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/apps.py +0 -0
  135. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/auth.py +0 -0
  136. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/cli.py +0 -0
  137. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/config.py +0 -0
  138. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/dates.py +0 -0
  139. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/env.py +0 -0
  140. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/errors.py +0 -0
  141. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/execution.py +0 -0
  142. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/progress_file.py +0 -0
  143. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/sentry.py +0 -0
  144. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/src/fastapi_cloud_cli/utils/version_check.py +0 -0
  145. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/__init__.py +0 -0
  146. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/conftest.py +0 -0
  147. {fastapi_cloud_cli-0.24.0/tests/integrations → fastapi_cloud_cli-0.25.0/tests/domains}/__init__.py +0 -0
  148. {fastapi_cloud_cli-0.24.0/tests/integrations/providers → fastapi_cloud_cli-0.25.0/tests/integrations}/__init__.py +0 -0
  149. {fastapi_cloud_cli-0.24.0/tests/integrations/resources → fastapi_cloud_cli-0.25.0/tests/integrations/providers}/__init__.py +0 -0
  150. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_archive.py +0 -0
  151. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_auth.py +0 -0
  152. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli.py +0 -0
  153. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli_ci.py +0 -0
  154. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli_logout.py +0 -0
  155. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli_setup_ci.py +0 -0
  156. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_cli_unlink.py +0 -0
  157. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_config.py +0 -0
  158. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_dates.py +0 -0
  159. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_prepare_release.py +0 -0
  160. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_progress_file.py +0 -0
  161. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_sentry.py +0 -0
  162. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_utils_apps.py +0 -0
  163. {fastapi_cloud_cli-0.24.0 → fastapi_cloud_cli-0.25.0}/tests/test_version_check.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi-cloud-cli
3
- Version: 0.24.0
3
+ Version: 0.25.0
4
4
  Summary: Deploy and manage FastAPI Cloud apps from the command line 🚀
5
5
  Author-Email: Patrick Arminio <patrick@fastapilabs.com>
6
6
  License: MIT
@@ -29,7 +29,7 @@ Project-URL: Repository, https://github.com/fastapilabs/fastapi-cloud-cli
29
29
  Project-URL: Issues, https://github.com/fastapilabs/fastapi-cloud-cli/issues
30
30
  Project-URL: Changelog, https://github.com/fastapilabs/fastapi-cloud-cli/blob/main/release-notes.md
31
31
  Requires-Python: >=3.10
32
- Requires-Dist: typer>=0.16.0
32
+ Requires-Dist: typer>=0.26.1
33
33
  Requires-Dist: uvicorn[standard]>=0.17.6
34
34
  Requires-Dist: rignore>=0.5.1
35
35
  Requires-Dist: httpx>=0.27.0
@@ -29,7 +29,7 @@ classifiers = [
29
29
  "Programming Language :: Python :: 3.13",
30
30
  ]
31
31
  dependencies = [
32
- "typer >= 0.16.0",
32
+ "typer >= 0.26.1",
33
33
  "uvicorn[standard] >= 0.17.6",
34
34
  "rignore >= 0.5.1",
35
35
  "httpx >= 0.27.0",
@@ -42,7 +42,7 @@ dependencies = [
42
42
  "detect-installer>=0.1.0",
43
43
  "agent-detector>=1.1.0",
44
44
  ]
45
- version = "0.24.0"
45
+ version = "0.25.0"
46
46
 
47
47
  [project.license]
48
48
  text = "MIT"
@@ -64,12 +64,13 @@ dev = [
64
64
  "prek>=0.2.24,<1.0.0",
65
65
  "pytest>=7.0.0,<10.0.0",
66
66
  "coverage[toml]>=7.2,<8.0",
67
- "mypy==2.3.0",
67
+ "mypy==2.3.1",
68
68
  "ruff>=0.15.16",
69
69
  "respx==0.23.1",
70
70
  "time-machine>=2.19.0",
71
71
  "ty>=0.0.25",
72
72
  "zizmor>=1.24.1",
73
+ "inline-snapshot>=0.35.4",
73
74
  ]
74
75
  github-actions = [
75
76
  "smokeshow>=0.5.0",
@@ -102,6 +103,9 @@ addopts = [
102
103
  xfail_strict = true
103
104
  junit_family = "xunit2"
104
105
 
106
+ [tool.inline-snapshot]
107
+ format-command = "ruff format --stdin-filename {filename}"
108
+
105
109
  [tool.coverage.run]
106
110
  parallel = true
107
111
  data_file = "coverage/.coverage"
@@ -0,0 +1 @@
1
+ __version__ = "0.25.0"
@@ -0,0 +1,35 @@
1
+ from typing import Annotated
2
+
3
+ import typer
4
+ from rich import print
5
+
6
+ from fastapi_cloud_cli import __version__
7
+
8
+ app = typer.Typer(rich_markup_mode="rich")
9
+
10
+
11
+ def version_callback(value: bool) -> None:
12
+ if value:
13
+ print(f"FastAPI Cloud CLI version: [green]{__version__}[/green]")
14
+ raise typer.Exit()
15
+
16
+
17
+ cloud_app = typer.Typer(
18
+ rich_markup_mode="rich",
19
+ help="Manage [bold]FastAPI[/bold] Cloud deployments.",
20
+ no_args_is_help=True,
21
+ )
22
+
23
+
24
+ @cloud_app.callback()
25
+ def cloud_main(
26
+ version: Annotated[
27
+ bool,
28
+ typer.Option(
29
+ "--version",
30
+ callback=version_callback,
31
+ is_eager=True,
32
+ help="Show the version and exit.",
33
+ ),
34
+ ] = False,
35
+ ) -> None: ...
@@ -0,0 +1,15 @@
1
+ from ._errors import StreamLogError as StreamLogError
2
+ from ._errors import TooManyRetriesError as TooManyRetriesError
3
+ from ._errors import _get_response_error_message as _get_response_error_message
4
+ from ._errors import get_http_error_code as get_http_error_code
5
+ from ._errors import get_http_error_hint as get_http_error_hint
6
+ from ._errors import handle_http_error as handle_http_error
7
+ from ._models import SUCCESSFUL_STATUSES as SUCCESSFUL_STATUSES
8
+ from ._models import AppLogEntry as AppLogEntry
9
+ from ._models import BuildLogLineMessage as BuildLogLineMessage
10
+ from ._models import CustomDomain as CustomDomain
11
+ from ._models import CustomDomainRecord as CustomDomainRecord
12
+ from ._models import CustomDomainStatus as CustomDomainStatus
13
+ from ._models import DeploymentStatus as DeploymentStatus
14
+ from ._retry import STREAM_LOGS_MAX_RETRIES as STREAM_LOGS_MAX_RETRIES
15
+ from .client import APIClient as APIClient
@@ -0,0 +1,136 @@
1
+ import json
2
+ import logging
3
+
4
+ import httpx
5
+
6
+ from fastapi_cloud_cli.utils.auth import AuthMode, delete_auth_config
7
+ from fastapi_cloud_cli.utils.errors import ErrorCode
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class StreamLogError(Exception):
13
+ """Raised when there's an error streaming logs (build or app logs)."""
14
+
15
+ def __init__(self, message: str, *, status_code: int | None = None) -> None:
16
+ super().__init__(message)
17
+ self.status_code = status_code
18
+
19
+
20
+ class TooManyRetriesError(Exception):
21
+ pass
22
+
23
+
24
+ def _handle_unauthorized(auth_mode: AuthMode) -> str:
25
+ message = "The specified token is not valid. "
26
+
27
+ if auth_mode == "user":
28
+ delete_auth_config()
29
+
30
+ message += "Use `fastapi login` to generate a new token."
31
+ else:
32
+ message += "Make sure to use a valid token."
33
+
34
+ return message
35
+
36
+
37
+ def _get_response_error_message(response: httpx.Response) -> str | None:
38
+ try:
39
+ data = response.json()
40
+ except (json.JSONDecodeError, httpx.ResponseNotRead):
41
+ return None
42
+
43
+ if not isinstance(data, dict):
44
+ return None # pragma: no cover
45
+
46
+ detail = data.get("detail")
47
+ if isinstance(detail, str):
48
+ return detail
49
+
50
+ if (
51
+ isinstance(detail, list)
52
+ and detail
53
+ and isinstance(detail[0], dict)
54
+ and isinstance(message := detail[0].get("msg"), str)
55
+ ):
56
+ return message.removeprefix("Value error, ")
57
+
58
+ return None # pragma: no cover
59
+
60
+
61
+ def handle_http_error(
62
+ error: httpx.HTTPError,
63
+ default_message: str | None = None,
64
+ not_found_message: str | None = None,
65
+ auth_mode: AuthMode = "user",
66
+ ) -> str:
67
+ message: str | None = None
68
+
69
+ if isinstance(error, httpx.HTTPStatusError):
70
+ status_code = error.response.status_code
71
+
72
+ if status_code == 422:
73
+ logger.debug(error.response.json())
74
+ message = _get_response_error_message(error.response)
75
+
76
+ elif status_code == 400:
77
+ message = _get_response_error_message(error.response)
78
+
79
+ elif status_code == 409:
80
+ message = _get_response_error_message(error.response)
81
+
82
+ elif status_code == 401:
83
+ message = _handle_unauthorized(auth_mode=auth_mode)
84
+
85
+ elif status_code == 403:
86
+ message = (
87
+ _get_response_error_message(error.response)
88
+ or "You don't have permissions for this resource"
89
+ )
90
+
91
+ elif status_code == 404:
92
+ message = (
93
+ _get_response_error_message(error.response)
94
+ or not_found_message
95
+ or "Resource not found."
96
+ )
97
+
98
+ if not message:
99
+ message = (
100
+ default_message
101
+ or f"Something went wrong while contacting the FastAPI Cloud server. Please try again later. \n\n{error}"
102
+ )
103
+
104
+ return message
105
+
106
+
107
+ def get_http_error_code(error: httpx.HTTPError) -> ErrorCode:
108
+ if isinstance(error, httpx.TimeoutException | httpx.NetworkError):
109
+ return "network_error"
110
+
111
+ if isinstance(error, httpx.HTTPStatusError):
112
+ status_code = error.response.status_code
113
+
114
+ if status_code in {400, 409, 422}:
115
+ return "invalid_input"
116
+
117
+ if status_code == 401:
118
+ return "invalid_token"
119
+
120
+ if status_code == 403:
121
+ return "permission_denied"
122
+
123
+ if status_code == 404:
124
+ return "not_found"
125
+
126
+ return "api_error"
127
+
128
+
129
+ def get_http_error_hint(code: ErrorCode, *, auth_mode: AuthMode = "user") -> str | None:
130
+ if code == "invalid_token":
131
+ if auth_mode == "user":
132
+ return "Run `fastapi cloud login` to generate a new token."
133
+
134
+ return "Make sure FASTAPI_CLOUD_TOKEN contains a valid token."
135
+
136
+ return None
@@ -0,0 +1,140 @@
1
+ from enum import Enum
2
+ from typing import Annotated, Literal
3
+
4
+ from pydantic import BaseModel, Field, TypeAdapter
5
+
6
+
7
+ class AppLogEntry(BaseModel):
8
+ timestamp: str
9
+ message: str
10
+ level: str
11
+
12
+
13
+ class BuildLogLineGeneric(BaseModel):
14
+ type: Literal["complete", "failed", "timeout", "heartbeat"]
15
+ id: str | None = None
16
+
17
+
18
+ class BuildLogLineMessage(BaseModel):
19
+ type: Literal["message"] = "message"
20
+ message: str
21
+ id: str | None = None
22
+
23
+
24
+ BuildLogLine = BuildLogLineMessage | BuildLogLineGeneric
25
+ BuildLogAdapter: TypeAdapter[BuildLogLine] = TypeAdapter(
26
+ Annotated[BuildLogLine, Field(discriminator="type")]
27
+ )
28
+
29
+
30
+ class CustomDomainStatus(str, Enum):
31
+ internal_dcv_pending = "internal_dcv_pending"
32
+ internal_dcv_missing = "internal_dcv_missing"
33
+ internal_dcv_invalid = "internal_dcv_invalid"
34
+ internal_dcv_timeout = "internal_dcv_timeout"
35
+ internal_dcv_revoked = "internal_dcv_revoked"
36
+ external_dcv_pending = "external_dcv_pending"
37
+ external_dcv_proxied = "external_dcv_proxied"
38
+ external_dcv_secured = "external_dcv_secured"
39
+ external_dcv_blocked = "external_dcv_blocked"
40
+ external_dcv_timeout = "external_dcv_timeout"
41
+ origin_setup_pending = "origin_setup_pending"
42
+ origin_setup_missing = "origin_setup_missing"
43
+ origin_setup_invalid = "origin_setup_invalid"
44
+ origin_setup_timeout = "origin_setup_timeout"
45
+ origin_setup_success = "origin_setup_success"
46
+ origin_setup_removed = "origin_setup_removed"
47
+
48
+
49
+ class CustomDomainRecord(BaseModel):
50
+ type: Literal["TXT", "CNAME", "A"]
51
+ name: str | None
52
+ value: str | None
53
+
54
+
55
+ class CustomDomain(BaseModel):
56
+ id: str
57
+ name: str
58
+ status: CustomDomainStatus
59
+ setup_in_progress: bool
60
+ setup_failed: bool
61
+ setup_successful: bool
62
+ is_using_pre_validation: bool
63
+ dns_records: list[CustomDomainRecord]
64
+ created_at: str
65
+ updated_at: str
66
+ setup_started_at: str | None
67
+ setup_checked_at: str | None
68
+ app_id: str
69
+
70
+
71
+ class CustomDomainsAPIResponse(BaseModel):
72
+ data: list[CustomDomain]
73
+ count: int
74
+
75
+
76
+ class DeploymentStatus(str, Enum):
77
+ waiting_upload = "waiting_upload"
78
+ upload_cancelled = "upload_cancelled"
79
+ ready_for_build = "ready_for_build"
80
+ building = "building"
81
+ extracting = "extracting"
82
+ extracting_failed = "extracting_failed"
83
+ extracting_failed_archive_too_large = "extracting_failed_archive_too_large"
84
+ building_image = "building_image"
85
+ building_image_failed = "building_image_failed"
86
+ building_image_failed_timeout = "building_image_failed_timeout"
87
+ deploying = "deploying"
88
+ deploying_skipped = "deploying_skipped"
89
+ deploying_failed = "deploying_failed"
90
+ verifying = "verifying"
91
+ verifying_failed = "verifying_failed"
92
+ verification_failed_oom = "verification_failed_oom"
93
+ verifying_skipped = "verifying_skipped"
94
+ success = "success"
95
+ degraded_oom = "degraded_oom"
96
+ degraded = "degraded"
97
+ expired = "expired"
98
+ failed = "failed"
99
+
100
+ @classmethod
101
+ def to_human_readable(cls, status: "DeploymentStatus") -> str:
102
+ return {
103
+ cls.waiting_upload: "Awaiting Upload",
104
+ cls.upload_cancelled: "Upload Cancelled",
105
+ cls.ready_for_build: "Build Queued",
106
+ cls.building: "Building",
107
+ cls.extracting: "Extracting Upload",
108
+ cls.extracting_failed: "Extraction Failed",
109
+ cls.extracting_failed_archive_too_large: "Archive Too Large",
110
+ cls.building_image: "Building Image",
111
+ cls.building_image_failed: "Build Failed",
112
+ cls.building_image_failed_timeout: "Build Timed Out",
113
+ cls.deploying: "Deploying Image",
114
+ cls.deploying_skipped: "Deployment Skipped",
115
+ cls.deploying_failed: "Deployment Failed",
116
+ cls.verifying: "Verifying Readiness",
117
+ cls.verifying_failed: "Verification Failed",
118
+ cls.verification_failed_oom: "Verification Failed (OOM)",
119
+ cls.verifying_skipped: "Verification Skipped",
120
+ cls.success: "Ready",
121
+ cls.degraded_oom: "Degraded (OOM)",
122
+ cls.degraded: "Degraded",
123
+ cls.expired: "Expired",
124
+ cls.failed: "Failed",
125
+ }[status]
126
+
127
+
128
+ SUCCESSFUL_STATUSES = {DeploymentStatus.success, DeploymentStatus.verifying_skipped}
129
+ FAILED_STATUSES = {
130
+ DeploymentStatus.failed,
131
+ DeploymentStatus.verifying_failed,
132
+ DeploymentStatus.verification_failed_oom,
133
+ DeploymentStatus.deploying_failed,
134
+ DeploymentStatus.deploying_skipped,
135
+ DeploymentStatus.building_image_failed,
136
+ DeploymentStatus.building_image_failed_timeout,
137
+ DeploymentStatus.extracting_failed,
138
+ DeploymentStatus.extracting_failed_archive_too_large,
139
+ }
140
+ TERMINAL_STATUSES = SUCCESSFUL_STATUSES | FAILED_STATUSES
@@ -0,0 +1,91 @@
1
+ import logging
2
+ import time
3
+ from collections.abc import Callable, Generator
4
+ from contextlib import contextmanager
5
+ from datetime import timedelta
6
+ from functools import wraps
7
+ from typing import TypeVar
8
+
9
+ import httpx
10
+ from typing_extensions import ParamSpec
11
+
12
+ from ._errors import StreamLogError, TooManyRetriesError
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ STREAM_LOGS_MAX_RETRIES = 3
17
+ STREAM_LOGS_TIMEOUT = timedelta(minutes=5)
18
+
19
+
20
+ @contextmanager
21
+ def attempt(attempt_number: int) -> Generator[None, None, None]:
22
+ def _backoff() -> None:
23
+ backoff_seconds = min(2**attempt_number, 30)
24
+ logger.debug(
25
+ "Retrying in %ds (attempt %d)",
26
+ backoff_seconds,
27
+ attempt_number,
28
+ )
29
+ time.sleep(backoff_seconds)
30
+
31
+ try:
32
+ yield
33
+
34
+ except (
35
+ httpx.TimeoutException,
36
+ httpx.NetworkError,
37
+ httpx.RemoteProtocolError,
38
+ ) as error:
39
+ logger.debug("Network error (will retry): %s", error)
40
+
41
+ _backoff()
42
+
43
+ except httpx.HTTPStatusError as error:
44
+ if error.response.status_code >= 500:
45
+ logger.debug(
46
+ "Server error %d (will retry): %s",
47
+ error.response.status_code,
48
+ error,
49
+ )
50
+ _backoff()
51
+ else:
52
+ # The streaming callers read the body before raising, so the
53
+ # server's error detail is available here.
54
+ raise StreamLogError(
55
+ f"HTTP {error.response.status_code}: {error.response.text}",
56
+ status_code=error.response.status_code,
57
+ ) from error
58
+
59
+
60
+ P = ParamSpec("P")
61
+ T = TypeVar("T")
62
+
63
+
64
+ def attempts(
65
+ total_attempts: int = 3, timeout: timedelta = timedelta(minutes=5)
66
+ ) -> Callable[
67
+ [Callable[P, Generator[T, None, None]]], Callable[P, Generator[T, None, None]]
68
+ ]:
69
+ def decorator(
70
+ func: Callable[P, Generator[T, None, None]],
71
+ ) -> Callable[P, Generator[T, None, None]]:
72
+ @wraps(func)
73
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> Generator[T, None, None]:
74
+ start = time.monotonic()
75
+
76
+ for attempt_number in range(total_attempts):
77
+ if time.monotonic() - start > timeout.total_seconds():
78
+ raise TimeoutError(
79
+ f"Log streaming timed out after {timeout.total_seconds():.0f}s"
80
+ )
81
+
82
+ with attempt(attempt_number):
83
+ yield from func(*args, **kwargs)
84
+ # If we get here without exception, the generator completed successfully
85
+ return
86
+
87
+ raise TooManyRetriesError(f"Failed after {total_attempts} attempts")
88
+
89
+ return wrapper
90
+
91
+ return decorator