tower 0.3.34__py3-none-win_arm64.whl

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 (289) hide show
  1. tower/__init__.py +37 -0
  2. tower/__main__.py +38 -0
  3. tower/_client.py +375 -0
  4. tower/_context.py +51 -0
  5. tower/_dbt.py +299 -0
  6. tower/_features.py +147 -0
  7. tower/_llms.py +670 -0
  8. tower/_tables.py +579 -0
  9. tower/exceptions.py +34 -0
  10. tower/polars.py +11 -0
  11. tower/py.typed +0 -0
  12. tower/pyarrow.py +7 -0
  13. tower/pyiceberg.py +19 -0
  14. tower/tower_api_client/__init__.py +8 -0
  15. tower/tower_api_client/api/__init__.py +1 -0
  16. tower/tower_api_client/api/default/__init__.py +1 -0
  17. tower/tower_api_client/api/default/acknowledge_alert.py +160 -0
  18. tower/tower_api_client/api/default/acknowledge_all_alerts.py +134 -0
  19. tower/tower_api_client/api/default/activate_schedules.py +168 -0
  20. tower/tower_api_client/api/default/cancel_run.py +174 -0
  21. tower/tower_api_client/api/default/claim_device_login_ticket.py +168 -0
  22. tower/tower_api_client/api/default/create_account.py +168 -0
  23. tower/tower_api_client/api/default/create_api_key.py +160 -0
  24. tower/tower_api_client/api/default/create_app.py +168 -0
  25. tower/tower_api_client/api/default/create_authenticator.py +172 -0
  26. tower/tower_api_client/api/default/create_catalog.py +168 -0
  27. tower/tower_api_client/api/default/create_device_login_ticket.py +136 -0
  28. tower/tower_api_client/api/default/create_environment.py +168 -0
  29. tower/tower_api_client/api/default/create_password_reset.py +172 -0
  30. tower/tower_api_client/api/default/create_schedule.py +168 -0
  31. tower/tower_api_client/api/default/create_secret.py +185 -0
  32. tower/tower_api_client/api/default/create_session.py +168 -0
  33. tower/tower_api_client/api/default/create_team.py +168 -0
  34. tower/tower_api_client/api/default/deactivate_schedules.py +168 -0
  35. tower/tower_api_client/api/default/delete_alert.py +103 -0
  36. tower/tower_api_client/api/default/delete_api_key.py +160 -0
  37. tower/tower_api_client/api/default/delete_app.py +160 -0
  38. tower/tower_api_client/api/default/delete_authenticator.py +168 -0
  39. tower/tower_api_client/api/default/delete_catalog.py +185 -0
  40. tower/tower_api_client/api/default/delete_schedule.py +168 -0
  41. tower/tower_api_client/api/default/delete_secret.py +181 -0
  42. tower/tower_api_client/api/default/delete_team.py +168 -0
  43. tower/tower_api_client/api/default/delete_team_invitation.py +183 -0
  44. tower/tower_api_client/api/default/deploy_app.py +216 -0
  45. tower/tower_api_client/api/default/describe_app.py +245 -0
  46. tower/tower_api_client/api/default/describe_app_version.py +174 -0
  47. tower/tower_api_client/api/default/describe_device_login_session.py +162 -0
  48. tower/tower_api_client/api/default/describe_email_preferences.py +134 -0
  49. tower/tower_api_client/api/default/describe_run.py +183 -0
  50. tower/tower_api_client/api/default/describe_run_logs.py +174 -0
  51. tower/tower_api_client/api/default/describe_secrets_key.py +170 -0
  52. tower/tower_api_client/api/default/describe_session.py +134 -0
  53. tower/tower_api_client/api/default/export_catalogs.py +168 -0
  54. tower/tower_api_client/api/default/export_secrets.py +168 -0
  55. tower/tower_api_client/api/default/generate_app_statistics.py +170 -0
  56. tower/tower_api_client/api/default/generate_authenticator.py +134 -0
  57. tower/tower_api_client/api/default/generate_run_statistics.py +251 -0
  58. tower/tower_api_client/api/default/generate_runner_credentials.py +140 -0
  59. tower/tower_api_client/api/default/invite_team_member.py +183 -0
  60. tower/tower_api_client/api/default/leave_team.py +164 -0
  61. tower/tower_api_client/api/default/list_alerts.py +271 -0
  62. tower/tower_api_client/api/default/list_api_keys.py +134 -0
  63. tower/tower_api_client/api/default/list_app_environments.py +160 -0
  64. tower/tower_api_client/api/default/list_app_versions.py +160 -0
  65. tower/tower_api_client/api/default/list_apps.py +278 -0
  66. tower/tower_api_client/api/default/list_authenticators.py +134 -0
  67. tower/tower_api_client/api/default/list_catalogs.py +215 -0
  68. tower/tower_api_client/api/default/list_environments.py +134 -0
  69. tower/tower_api_client/api/default/list_my_team_invitations.py +134 -0
  70. tower/tower_api_client/api/default/list_plans.py +181 -0
  71. tower/tower_api_client/api/default/list_runs.py +291 -0
  72. tower/tower_api_client/api/default/list_schedules.py +200 -0
  73. tower/tower_api_client/api/default/list_secret_environments.py +134 -0
  74. tower/tower_api_client/api/default/list_secrets.py +215 -0
  75. tower/tower_api_client/api/default/list_team_invitations.py +160 -0
  76. tower/tower_api_client/api/default/list_team_members.py +160 -0
  77. tower/tower_api_client/api/default/list_teams.py +134 -0
  78. tower/tower_api_client/api/default/refresh_session.py +172 -0
  79. tower/tower_api_client/api/default/remove_team_member.py +183 -0
  80. tower/tower_api_client/api/default/resend_email_verification.py +89 -0
  81. tower/tower_api_client/api/default/resend_team_invitation.py +183 -0
  82. tower/tower_api_client/api/default/run_app.py +188 -0
  83. tower/tower_api_client/api/default/search_runs.py +272 -0
  84. tower/tower_api_client/api/default/stream_alerts.py +174 -0
  85. tower/tower_api_client/api/default/stream_run_logs.py +198 -0
  86. tower/tower_api_client/api/default/update_account_name.py +183 -0
  87. tower/tower_api_client/api/default/update_app.py +183 -0
  88. tower/tower_api_client/api/default/update_catalog.py +183 -0
  89. tower/tower_api_client/api/default/update_email_preferences.py +171 -0
  90. tower/tower_api_client/api/default/update_environment.py +183 -0
  91. tower/tower_api_client/api/default/update_my_team_invitation.py +168 -0
  92. tower/tower_api_client/api/default/update_password_reset.py +183 -0
  93. tower/tower_api_client/api/default/update_plan.py +160 -0
  94. tower/tower_api_client/api/default/update_schedule.py +183 -0
  95. tower/tower_api_client/api/default/update_secret.py +183 -0
  96. tower/tower_api_client/api/default/update_team.py +187 -0
  97. tower/tower_api_client/api/default/update_user.py +168 -0
  98. tower/tower_api_client/api/default/verify_email.py +172 -0
  99. tower/tower_api_client/api/feature_flags/__init__.py +1 -0
  100. tower/tower_api_client/api/feature_flags/get_feature_flag_value.py +164 -0
  101. tower/tower_api_client/client.py +286 -0
  102. tower/tower_api_client/errors.py +16 -0
  103. tower/tower_api_client/models/__init__.py +363 -0
  104. tower/tower_api_client/models/account.py +50 -0
  105. tower/tower_api_client/models/acknowledge_alert_response.py +51 -0
  106. tower/tower_api_client/models/acknowledge_all_alerts_response.py +59 -0
  107. tower/tower_api_client/models/alert.py +95 -0
  108. tower/tower_api_client/models/api_key.py +82 -0
  109. tower/tower_api_client/models/app.py +196 -0
  110. tower/tower_api_client/models/app_health_status.py +9 -0
  111. tower/tower_api_client/models/app_statistics.py +63 -0
  112. tower/tower_api_client/models/app_status.py +9 -0
  113. tower/tower_api_client/models/app_summary.py +63 -0
  114. tower/tower_api_client/models/app_version.py +79 -0
  115. tower/tower_api_client/models/batch_schedule_params.py +51 -0
  116. tower/tower_api_client/models/batch_schedule_response.py +65 -0
  117. tower/tower_api_client/models/cancel_run_response.py +57 -0
  118. tower/tower_api_client/models/catalog.py +98 -0
  119. tower/tower_api_client/models/catalog_property.py +47 -0
  120. tower/tower_api_client/models/claim_device_login_ticket_params.py +51 -0
  121. tower/tower_api_client/models/claim_device_login_ticket_response.py +51 -0
  122. tower/tower_api_client/models/create_account_params.py +59 -0
  123. tower/tower_api_client/models/create_account_params_flags_struct.py +39 -0
  124. tower/tower_api_client/models/create_account_response.py +57 -0
  125. tower/tower_api_client/models/create_api_key_params.py +51 -0
  126. tower/tower_api_client/models/create_api_key_response.py +57 -0
  127. tower/tower_api_client/models/create_app_params.py +79 -0
  128. tower/tower_api_client/models/create_app_response.py +57 -0
  129. tower/tower_api_client/models/create_authenticator_params.py +59 -0
  130. tower/tower_api_client/models/create_authenticator_response.py +57 -0
  131. tower/tower_api_client/models/create_catalog_params.py +90 -0
  132. tower/tower_api_client/models/create_catalog_params_type.py +11 -0
  133. tower/tower_api_client/models/create_catalog_response.py +57 -0
  134. tower/tower_api_client/models/create_device_login_ticket_response.py +101 -0
  135. tower/tower_api_client/models/create_environment_params.py +51 -0
  136. tower/tower_api_client/models/create_environment_response.py +57 -0
  137. tower/tower_api_client/models/create_password_reset_params.py +51 -0
  138. tower/tower_api_client/models/create_password_reset_response.py +51 -0
  139. tower/tower_api_client/models/create_schedule_params.py +131 -0
  140. tower/tower_api_client/models/create_schedule_params_status.py +9 -0
  141. tower/tower_api_client/models/create_schedule_response.py +57 -0
  142. tower/tower_api_client/models/create_secret_params.py +75 -0
  143. tower/tower_api_client/models/create_secret_response.py +57 -0
  144. tower/tower_api_client/models/create_session_params.py +69 -0
  145. tower/tower_api_client/models/create_session_response.py +57 -0
  146. tower/tower_api_client/models/create_team_params.py +51 -0
  147. tower/tower_api_client/models/create_team_response.py +57 -0
  148. tower/tower_api_client/models/delete_api_key_params.py +51 -0
  149. tower/tower_api_client/models/delete_api_key_response.py +57 -0
  150. tower/tower_api_client/models/delete_app_response.py +57 -0
  151. tower/tower_api_client/models/delete_authenticator_params.py +51 -0
  152. tower/tower_api_client/models/delete_authenticator_response.py +57 -0
  153. tower/tower_api_client/models/delete_catalog_response.py +57 -0
  154. tower/tower_api_client/models/delete_schedule_params.py +51 -0
  155. tower/tower_api_client/models/delete_schedule_response.py +65 -0
  156. tower/tower_api_client/models/delete_secret_response.py +57 -0
  157. tower/tower_api_client/models/delete_team_invitation_params.py +51 -0
  158. tower/tower_api_client/models/delete_team_invitation_response.py +57 -0
  159. tower/tower_api_client/models/delete_team_params.py +51 -0
  160. tower/tower_api_client/models/delete_team_response.py +57 -0
  161. tower/tower_api_client/models/deploy_app_response.py +57 -0
  162. tower/tower_api_client/models/describe_app_response.py +75 -0
  163. tower/tower_api_client/models/describe_app_version_response.py +57 -0
  164. tower/tower_api_client/models/describe_device_login_session_response.py +57 -0
  165. tower/tower_api_client/models/describe_email_preferences_body.py +57 -0
  166. tower/tower_api_client/models/describe_run_logs_response.py +65 -0
  167. tower/tower_api_client/models/describe_run_response.py +57 -0
  168. tower/tower_api_client/models/describe_secrets_key_response.py +51 -0
  169. tower/tower_api_client/models/describe_session_response.py +57 -0
  170. tower/tower_api_client/models/email_subscriptions.py +57 -0
  171. tower/tower_api_client/models/encrypted_catalog_property.py +55 -0
  172. tower/tower_api_client/models/environment.py +39 -0
  173. tower/tower_api_client/models/error_detail.py +57 -0
  174. tower/tower_api_client/models/error_model.py +114 -0
  175. tower/tower_api_client/models/export_catalogs_params.py +83 -0
  176. tower/tower_api_client/models/export_catalogs_response.py +75 -0
  177. tower/tower_api_client/models/export_secrets_params.py +83 -0
  178. tower/tower_api_client/models/export_secrets_response.py +75 -0
  179. tower/tower_api_client/models/exported_catalog.py +98 -0
  180. tower/tower_api_client/models/exported_catalog_property.py +55 -0
  181. tower/tower_api_client/models/exported_secret.py +65 -0
  182. tower/tower_api_client/models/featurebase_identity.py +47 -0
  183. tower/tower_api_client/models/features.py +75 -0
  184. tower/tower_api_client/models/generate_app_statistics_response.py +57 -0
  185. tower/tower_api_client/models/generate_authenticator_response.py +57 -0
  186. tower/tower_api_client/models/generate_run_statistics_response.py +85 -0
  187. tower/tower_api_client/models/generate_run_statistics_status_item.py +14 -0
  188. tower/tower_api_client/models/generate_runner_credentials_response.py +57 -0
  189. tower/tower_api_client/models/get_feature_flag_response_body.py +78 -0
  190. tower/tower_api_client/models/get_feature_flag_response_body_value_type.py +11 -0
  191. tower/tower_api_client/models/invite_team_member_params.py +61 -0
  192. tower/tower_api_client/models/invite_team_member_response.py +65 -0
  193. tower/tower_api_client/models/leave_team_response.py +57 -0
  194. tower/tower_api_client/models/list_account_plans_response.py +75 -0
  195. tower/tower_api_client/models/list_alerts_response.py +75 -0
  196. tower/tower_api_client/models/list_api_keys_response.py +65 -0
  197. tower/tower_api_client/models/list_app_environments_response.py +51 -0
  198. tower/tower_api_client/models/list_app_versions_response.py +65 -0
  199. tower/tower_api_client/models/list_apps_filter.py +10 -0
  200. tower/tower_api_client/models/list_apps_response.py +75 -0
  201. tower/tower_api_client/models/list_apps_sort.py +11 -0
  202. tower/tower_api_client/models/list_authenticators_response.py +67 -0
  203. tower/tower_api_client/models/list_catalogs_response.py +75 -0
  204. tower/tower_api_client/models/list_environments_response.py +65 -0
  205. tower/tower_api_client/models/list_my_team_invitations_response.py +65 -0
  206. tower/tower_api_client/models/list_runs_response.py +75 -0
  207. tower/tower_api_client/models/list_runs_status_item.py +14 -0
  208. tower/tower_api_client/models/list_schedules_response.py +75 -0
  209. tower/tower_api_client/models/list_secret_environments_response.py +51 -0
  210. tower/tower_api_client/models/list_secrets_response.py +75 -0
  211. tower/tower_api_client/models/list_team_invitations_response.py +65 -0
  212. tower/tower_api_client/models/list_team_members_response.py +65 -0
  213. tower/tower_api_client/models/list_teams_response.py +65 -0
  214. tower/tower_api_client/models/pagination.py +63 -0
  215. tower/tower_api_client/models/parameter.py +55 -0
  216. tower/tower_api_client/models/plan.py +129 -0
  217. tower/tower_api_client/models/refresh_session_params.py +40 -0
  218. tower/tower_api_client/models/refresh_session_response.py +67 -0
  219. tower/tower_api_client/models/remove_team_member_params.py +51 -0
  220. tower/tower_api_client/models/remove_team_member_response.py +57 -0
  221. tower/tower_api_client/models/resend_team_invitation_params.py +60 -0
  222. tower/tower_api_client/models/resend_team_invitation_response.py +57 -0
  223. tower/tower_api_client/models/run.py +246 -0
  224. tower/tower_api_client/models/run_app_params.py +86 -0
  225. tower/tower_api_client/models/run_app_params_parameters.py +44 -0
  226. tower/tower_api_client/models/run_app_response.py +57 -0
  227. tower/tower_api_client/models/run_failure_alert.py +55 -0
  228. tower/tower_api_client/models/run_log_line.py +101 -0
  229. tower/tower_api_client/models/run_log_line_channel.py +9 -0
  230. tower/tower_api_client/models/run_parameter.py +47 -0
  231. tower/tower_api_client/models/run_results.py +79 -0
  232. tower/tower_api_client/models/run_statistics.py +79 -0
  233. tower/tower_api_client/models/run_status.py +14 -0
  234. tower/tower_api_client/models/run_status_group.py +10 -0
  235. tower/tower_api_client/models/run_timeseries_point.py +97 -0
  236. tower/tower_api_client/models/runner_credentials.py +71 -0
  237. tower/tower_api_client/models/schedule.py +126 -0
  238. tower/tower_api_client/models/schedule_status.py +9 -0
  239. tower/tower_api_client/models/search_runs_response.py +75 -0
  240. tower/tower_api_client/models/search_runs_status_item.py +14 -0
  241. tower/tower_api_client/models/secret.py +65 -0
  242. tower/tower_api_client/models/session.py +85 -0
  243. tower/tower_api_client/models/sse_warning.py +49 -0
  244. tower/tower_api_client/models/statistics_settings.py +75 -0
  245. tower/tower_api_client/models/statistics_settings_interval.py +9 -0
  246. tower/tower_api_client/models/stream_alerts_event_error.py +102 -0
  247. tower/tower_api_client/models/stream_alerts_event_run_failure_alert.py +104 -0
  248. tower/tower_api_client/models/stream_run_logs_event_log.py +102 -0
  249. tower/tower_api_client/models/stream_run_logs_event_warning.py +102 -0
  250. tower/tower_api_client/models/team.py +80 -0
  251. tower/tower_api_client/models/team_invitation.py +63 -0
  252. tower/tower_api_client/models/token.py +39 -0
  253. tower/tower_api_client/models/unverified_authenticator.py +63 -0
  254. tower/tower_api_client/models/update_account_name_params.py +51 -0
  255. tower/tower_api_client/models/update_account_name_response.py +57 -0
  256. tower/tower_api_client/models/update_app_params.py +82 -0
  257. tower/tower_api_client/models/update_app_response.py +57 -0
  258. tower/tower_api_client/models/update_catalog_params.py +73 -0
  259. tower/tower_api_client/models/update_catalog_response.py +57 -0
  260. tower/tower_api_client/models/update_email_preferences_body.py +57 -0
  261. tower/tower_api_client/models/update_environment_params.py +51 -0
  262. tower/tower_api_client/models/update_environment_response.py +57 -0
  263. tower/tower_api_client/models/update_my_team_invitation_params.py +59 -0
  264. tower/tower_api_client/models/update_my_team_invitation_response.py +40 -0
  265. tower/tower_api_client/models/update_password_reset_params.py +51 -0
  266. tower/tower_api_client/models/update_password_reset_response.py +51 -0
  267. tower/tower_api_client/models/update_plan_params.py +69 -0
  268. tower/tower_api_client/models/update_plan_response.py +57 -0
  269. tower/tower_api_client/models/update_schedule_params.py +121 -0
  270. tower/tower_api_client/models/update_schedule_params_status.py +9 -0
  271. tower/tower_api_client/models/update_schedule_response.py +57 -0
  272. tower/tower_api_client/models/update_secret_params.py +67 -0
  273. tower/tower_api_client/models/update_secret_response.py +57 -0
  274. tower/tower_api_client/models/update_team_params.py +59 -0
  275. tower/tower_api_client/models/update_team_response.py +57 -0
  276. tower/tower_api_client/models/update_user_params.py +234 -0
  277. tower/tower_api_client/models/update_user_response.py +57 -0
  278. tower/tower_api_client/models/user.py +124 -0
  279. tower/tower_api_client/models/verified_authenticator.py +65 -0
  280. tower/tower_api_client/models/verify_email_params.py +51 -0
  281. tower/tower_api_client/models/verify_email_response.py +57 -0
  282. tower/tower_api_client/types.py +46 -0
  283. tower/utils/pyarrow.py +328 -0
  284. tower/utils/tables.py +17 -0
  285. tower-0.3.34.data/scripts/tower.exe +0 -0
  286. tower-0.3.34.dist-info/METADATA +433 -0
  287. tower-0.3.34.dist-info/RECORD +289 -0
  288. tower-0.3.34.dist-info/WHEEL +4 -0
  289. tower-0.3.34.dist-info/licenses/LICENSE +21 -0
tower/__init__.py ADDED
@@ -0,0 +1,37 @@
1
+ """
2
+ Tower: A compute platform for modern data projects.
3
+
4
+ This package provides tools for interacting with the Tower platform,
5
+ including running apps locally or in the Tower cloud.
6
+
7
+ Optional features:
8
+ - AI/LLM support: Install with `pip install "tower[ai]"`
9
+ - Apache Iceberg support: Install with `pip install "tower[iceberg]"`
10
+ - dbt Core support: Install with `pip install "tower[dbt]"`
11
+ - All features: Install with `pip install "tower[all]"`
12
+ """
13
+
14
+ from typing import TYPE_CHECKING
15
+
16
+ from ._client import (
17
+ run_app,
18
+ wait_for_run,
19
+ wait_for_runs,
20
+ )
21
+
22
+ from ._features import override_get_attr, get_available_features, is_feature_enabled
23
+
24
+ if TYPE_CHECKING:
25
+ from ._tables import tables as tables
26
+ from ._llms import llms as llms
27
+ from ._dbt import dbt as dbt
28
+
29
+
30
+ def __getattr__(name: str):
31
+ """
32
+ Dynamic attribute lookup for optional features.
33
+
34
+ This allows Tower to lazily load optional dependencies only when
35
+ their features are actually used.
36
+ """
37
+ return override_get_attr(name)
tower/__main__.py ADDED
@@ -0,0 +1,38 @@
1
+ import io
2
+ import sys
3
+
4
+ _CLEAN_GLOBALS = globals().copy()
5
+
6
+
7
+ def run_program(filename):
8
+ ns = dict(
9
+ _CLEAN_GLOBALS,
10
+ # Make sure the __file__ variable is usable
11
+ # by the script we're profiling
12
+ __file__=filename,
13
+ )
14
+
15
+ with io.open(filename, encoding="utf-8") as f:
16
+ exec(compile(f.read(), filename, "exec"), ns, ns)
17
+
18
+
19
+ def parse_args():
20
+ from argparse import REMAINDER, ArgumentParser
21
+
22
+ parser = ArgumentParser()
23
+ parser.add_argument(
24
+ "program",
25
+ nargs=REMAINDER,
26
+ help="python script or module followed by command line arguments to run",
27
+ )
28
+ return parser.parse_args()
29
+
30
+
31
+ if __name__ == "__main__":
32
+ args = parse_args()
33
+
34
+ if len(args.program) == 0:
35
+ print("A program to run must be provided. Use -h for help")
36
+ sys.exit(1)
37
+
38
+ run_program(args.program[0])
tower/_client.py ADDED
@@ -0,0 +1,375 @@
1
+ import os
2
+ import time
3
+ import httpx
4
+ from typing import List, Dict, Optional
5
+
6
+ from ._context import TowerContext
7
+ from .exceptions import (
8
+ AppNotFoundError,
9
+ NotFoundException,
10
+ UnauthorizedException,
11
+ UnknownException,
12
+ UnhandledRunStateException,
13
+ RunFailedError,
14
+ TimeoutException,
15
+ )
16
+
17
+ from .tower_api_client import AuthenticatedClient
18
+ from .tower_api_client.api.default import describe_run as describe_run_api
19
+ from .tower_api_client.api.default import run_app as run_app_api
20
+ from .tower_api_client.models import (
21
+ DescribeRunResponse,
22
+ Run,
23
+ RunAppParams,
24
+ RunAppParamsParameters,
25
+ RunAppResponse,
26
+ )
27
+ from .tower_api_client.models.error_model import ErrorModel
28
+ from .tower_api_client.errors import UnexpectedStatus
29
+
30
+ # WAIT_TIMEOUT is the amount of time to wait between requests when polling the
31
+ # Tower API.
32
+ WAIT_TIMEOUT = 2
33
+
34
+ # DEFAULT_TOWER_URL is the default tower URL to use when connecting back to
35
+ # Tower.
36
+ DEFAULT_TOWER_URL = "https://api.tower.dev"
37
+
38
+ # DEFAULT_TOWER_ENVIRONMENT is the default environment to use when running an
39
+ # app somewhere.
40
+ DEFAULT_TOWER_ENVIRONMENT = "default"
41
+
42
+ # DEFAULT_NUM_TIMEOUT_RETRIES is the number of times to retry querying the Tower
43
+ # API before we just give up entirely.
44
+ DEFAULT_NUM_TIMEOUT_RETRIES = 5
45
+
46
+
47
+ def run_app(
48
+ name: str,
49
+ environment: Optional[str] = None,
50
+ parameters: Optional[Dict[str, str]] = None,
51
+ ) -> Run:
52
+ """
53
+ Run a Tower application with specified parameters and environment.
54
+
55
+ This function initiates a new run of a Tower application identified by its name.
56
+ The run can be configured with an optional environment override and runtime parameters.
57
+ If no environment is specified, the default environment from the Tower context is used.
58
+
59
+ Args:
60
+ name (str): The unique identifier of the application to run.
61
+ environment (Optional[str]): The environment to run the application in.
62
+ If not provided, uses the default environment from the Tower context.
63
+ parameters (Optional[Dict[str, str]]): A dictionary of key-value pairs
64
+ to pass as parameters to the application run.
65
+
66
+ Returns:
67
+ Run: A Run object containing information about the initiated application run,
68
+ including the app_name and run number.
69
+
70
+ Raises:
71
+ RuntimeError: If there is an error initiating the run or if the Tower API
72
+ returns an error response.
73
+ """
74
+ ctx = TowerContext.build()
75
+ client = _env_client(ctx)
76
+ run_params = RunAppParamsParameters()
77
+
78
+ if not environment:
79
+ environment = ctx.environment
80
+
81
+ if parameters:
82
+ run_params = RunAppParamsParameters.from_dict(parameters)
83
+
84
+ input_body = RunAppParams(
85
+ environment=environment,
86
+ parameters=run_params,
87
+ )
88
+
89
+ try:
90
+ output: Optional[Union[ErrorModel, RunAppResponse]] = run_app_api.sync(
91
+ name=name, client=client, body=input_body
92
+ )
93
+
94
+ if output is None:
95
+ raise RuntimeError("Error running app")
96
+ else:
97
+ if isinstance(output, ErrorModel):
98
+ raise RuntimeError(f"Error running app: {output.title}")
99
+ else:
100
+ return output.run
101
+ except UnexpectedStatus as e:
102
+ # Raise an AppNotFoundError here if the app was, indeed, not found.
103
+ if e.status_code == 404:
104
+ raise AppNotFoundError(name)
105
+ else:
106
+ raise UnknownException(
107
+ f"Unexpected status code {e.status_code} when running app {name}"
108
+ )
109
+
110
+
111
+ def wait_for_run(
112
+ run: Run,
113
+ timeout: Optional[float] = 86_400.0, # one day
114
+ raise_on_failure: bool = False,
115
+ ) -> Run:
116
+ """
117
+ Wait for a Tower app run to reach a terminal state by polling the Tower API.
118
+
119
+ This function continuously polls the Tower API every 2 seconds (defined by WAIT_TIMEOUT)
120
+ to check the status of the specified run. The function returns when the run reaches
121
+ a terminal state (exited, errored, cancelled, or crashed).
122
+
123
+ Args:
124
+ run (Run): The Run object containing the app_name and number of the run to monitor.
125
+ timeout (Optional[float]): Maximum time to wait in seconds before raising a
126
+ TimeoutException. Defaults to one day (86,400 seconds).
127
+ raise_on_failure (bool): If True, raises a RunFailedError when the run fails.
128
+ If False, returns the failed run object. Defaults to False.
129
+
130
+ Returns:
131
+ Run: The final state of the run after completion or failure.
132
+
133
+ Raises:
134
+ TimeoutException: If the specified timeout is reached before the run completes.
135
+ RunFailedError: If raise_on_failure is True and the run fails.
136
+ UnhandledRunStateException: If the run enters an unexpected state.
137
+ UnknownException: If there are persistent problems communicating with the Tower API.
138
+ NotFoundException: If the run cannot be found.
139
+ UnauthorizedException: If the API key is invalid or unauthorized.
140
+ """
141
+ ctx = TowerContext.build()
142
+ retries = 0
143
+
144
+ # We use this to track the timeout, if one is defined.
145
+ start_time = time.time()
146
+
147
+ while True:
148
+ # We check for a timeout at the top of the loop because we want to
149
+ # avoid waiting unnecessarily for the timeout hitting the Tower API if
150
+ # we've enounctered some sort of operational problem there.
151
+ if timeout is not None:
152
+ if _time_since(start_time) > timeout:
153
+ raise TimeoutException(_time_since(start_time))
154
+
155
+ # We time this out to avoid waiting forever on the API.
156
+ try:
157
+ desc = _check_run_status(ctx, run, timeout=2.0)
158
+ retries = 0
159
+
160
+ if _is_successful_run(desc):
161
+ return desc
162
+ elif _is_failed_run(desc):
163
+ if raise_on_failure:
164
+ raise RunFailedError(desc.app_name, desc.number, desc.status)
165
+ else:
166
+ return desc
167
+
168
+ elif _is_run_awaiting_completion(desc):
169
+ time.sleep(WAIT_TIMEOUT)
170
+ else:
171
+ raise UnhandledRunStateException(desc.status)
172
+ except TimeoutException:
173
+ # timed out in the API, we want to keep trying this for a while
174
+ # (assuming we didn't hit the global timeout limit) until we give
175
+ # up entirely.
176
+ retries += 1
177
+
178
+ if retries >= DEFAULT_NUM_TIMEOUT_RETRIES:
179
+ raise UnknownException("There was a problem with the Tower API.")
180
+
181
+
182
+ def wait_for_runs(
183
+ runs: List[Run],
184
+ timeout: Optional[float] = 86_400.0, # one day
185
+ raise_on_failure: bool = False,
186
+ ) -> tuple[List[Run], List[Run]]:
187
+ """
188
+ Wait for multiple Tower app runs to reach terminal states by polling the Tower API.
189
+
190
+ This function continuously polls the Tower API every 2 seconds (defined by WAIT_TIMEOUT)
191
+ to check the status of all specified runs. The function returns when all runs reach
192
+ terminal states (`exited`, `errored`, `cancelled`, or `crashed`).
193
+
194
+ Args:
195
+ runs (List[Run]): A list of Run objects to monitor.
196
+ timeout (Optional[float]): Maximum time to wait in seconds before raising a
197
+ TimeoutException. Defaults to one day (86,400 seconds).
198
+ raise_on_failure (bool): If True, raises a RunFailedError when any run fails.
199
+ If False, failed runs are returned in the failed_runs list. Defaults to False.
200
+
201
+ Returns:
202
+ tuple[List[Run], List[Run]]: A tuple containing two lists:
203
+ - successful_runs: List of runs that completed successfully (status: 'exited')
204
+ - failed_runs: List of runs that failed (status: 'crashed', 'cancelled', or 'errored')
205
+
206
+ Raises:
207
+ TimeoutException: If the specified timeout is reached before all runs complete.
208
+ RunFailedError: If raise_on_failure is True and any run fails.
209
+ UnhandledRunStateException: If a run enters an unexpected state.
210
+ UnknownException: If there are persistent problems communicating with the Tower API.
211
+ NotFoundException: If any run cannot be found.
212
+ UnauthorizedException: If the API key is invalid or unauthorized.
213
+ """
214
+ ctx = TowerContext.build()
215
+ retries = 0
216
+
217
+ # We use this to track the timeout, if one is defined.
218
+ start_time = time.time()
219
+
220
+ awaiting_runs = runs
221
+ successful_runs = []
222
+ failed_runs = []
223
+
224
+ while len(awaiting_runs) > 0:
225
+ run = awaiting_runs.pop(0)
226
+
227
+ # Check the overall timeout at the top of the loop in case we've
228
+ # spent a load of time deeper inside the loop on reties, etc.
229
+ if timeout is not None:
230
+ if _time_since(start_time) > timeout:
231
+ raise TimeoutException(_time_since(start_time))
232
+
233
+ try:
234
+ desc = _check_run_status(ctx, run, timeout=2.0)
235
+ retries = 0
236
+
237
+ if _is_successful_run(desc):
238
+ successful_runs.append(desc)
239
+ elif _is_failed_run(desc):
240
+ if raise_on_failure:
241
+ raise RunFailedError(desc.app_name, desc.number, desc.status)
242
+ else:
243
+ failed_runs.append(desc)
244
+
245
+ elif _is_run_awaiting_completion(desc):
246
+ time.sleep(WAIT_TIMEOUT)
247
+
248
+ # We need to re-add this run to the list so we check it again
249
+ # in the future. We add it to the back since we took it off the
250
+ # front, effectively moving to the next run.
251
+ awaiting_runs.append(desc)
252
+ else:
253
+ raise UnhandledRunStateException(desc.status)
254
+ except TimeoutException:
255
+ # timed out in the API, we want to keep trying this for a while
256
+ # (assuming we didn't hit the global timeout limit) until we give
257
+ # up entirely.
258
+ retries += 1
259
+
260
+ if retries >= DEFAULT_NUM_TIMEOUT_RETRIES:
261
+ raise UnknownException("There was a problem with the Tower API.")
262
+ else:
263
+ # Add the item back on the list for retry later on.
264
+ awaiting_runs.append(run)
265
+
266
+ return (successful_runs, failed_runs)
267
+
268
+
269
+ def _is_failed_run(run: Run) -> bool:
270
+ """
271
+ Check if the given run has failed.
272
+
273
+ Args:
274
+ run (Run): The Run object containing the status to check.
275
+
276
+ Returns:
277
+ bool: True if the run has failed, False otherwise.
278
+ """
279
+ return run.status in ["crashed", "cancelled", "errored"]
280
+
281
+
282
+ def _is_successful_run(run: Run) -> bool:
283
+ """
284
+ Check if a given run was successful.
285
+
286
+ Args:
287
+ run (Run): The Run object containing the status to check.
288
+
289
+ Returns:
290
+ bool: True if the run was successful, False otherwise.
291
+ """
292
+ return run.status in ["exited"]
293
+
294
+
295
+ def _is_run_awaiting_completion(run: Run) -> bool:
296
+ """
297
+ Check if a given run is either running or expected to run in the near future.
298
+
299
+ Args:
300
+ run (Run): The Run object containing the status to check.
301
+
302
+ Returns:
303
+ bool: True if the run is awaiting run or currently running, False otherwise.
304
+ """
305
+ return run.status in ["pending", "scheduled", "running"]
306
+
307
+
308
+ def _env_client(
309
+ ctx: TowerContext, timeout: Optional[float] = None
310
+ ) -> AuthenticatedClient:
311
+ tower_url = ctx.tower_url
312
+
313
+ if not tower_url.endswith("/v1"):
314
+ if tower_url.endswith("/"):
315
+ tower_url += "v1"
316
+ else:
317
+ tower_url += "/v1"
318
+
319
+ if ctx.jwt is not None:
320
+ token = ctx.jwt
321
+ auth_header_name = "Authorization"
322
+ prefix = "Bearer"
323
+ else:
324
+ token = ctx.api_key
325
+ auth_header_name = "X-API-Key"
326
+ prefix = ""
327
+
328
+ return AuthenticatedClient(
329
+ verify_ssl=False,
330
+ base_url=tower_url,
331
+ token=token,
332
+ auth_header_name=auth_header_name,
333
+ prefix=prefix,
334
+ timeout=timeout,
335
+ raise_on_unexpected_status=True,
336
+ )
337
+
338
+
339
+ def _time_since(start_time: float) -> float:
340
+ return time.time() - start_time
341
+
342
+
343
+ def _check_run_status(
344
+ ctx: TowerContext,
345
+ run: Run,
346
+ timeout: Optional[float] = 2.0, # one day
347
+ ) -> Run:
348
+ client = _env_client(ctx, timeout=timeout)
349
+
350
+ try:
351
+ output: Optional[Union[DescribeRunResponse, ErrorModel]] = (
352
+ describe_run_api.sync(name=run.app_name, seq=run.number, client=client)
353
+ )
354
+
355
+ if output is None:
356
+ raise UnknownException("Failed to fetch run")
357
+ elif isinstance(output, ErrorModel):
358
+ # If it was a 404 error, that means that we couldn't find this
359
+ # app for some reason. This is really only relevant on the
360
+ # first time that we check--if we could find the run, but then
361
+ # suddenly couldn't that's a really big problem I'd say.
362
+ if output.status == 404:
363
+ raise NotFoundException(output.detail)
364
+ elif output.status == 401:
365
+ # NOTE: Most of the time, this shouldn't happen?
366
+ raise UnauthorizedException(output.detail)
367
+ else:
368
+ raise UnknownException(output.detail)
369
+ else:
370
+ # There was a run object, so let's return that.
371
+ return output.run
372
+ except httpx.TimeoutException:
373
+ # If we received a timeout from the API then we should raise our own
374
+ # timeout type.
375
+ raise TimeoutException(timeout)
tower/_context.py ADDED
@@ -0,0 +1,51 @@
1
+ import os
2
+
3
+
4
+ class TowerContext:
5
+ def __init__(
6
+ self,
7
+ tower_url: str,
8
+ environment: str,
9
+ api_key: str = None,
10
+ inference_router: str = None,
11
+ inference_router_api_key: str = None,
12
+ inference_provider: str = None,
13
+ jwt: str = None,
14
+ ):
15
+ self.tower_url = tower_url
16
+ self.environment = environment
17
+ self.api_key = api_key
18
+ self.jwt = jwt
19
+ self.inference_router = inference_router
20
+ self.inference_router_api_key = inference_router_api_key
21
+ self.inference_provider = inference_provider
22
+
23
+ def is_local(self) -> bool:
24
+ if self.environment is None or self.environment == "":
25
+ return True
26
+ elif self.environment == "local":
27
+ return True
28
+ else:
29
+ return False
30
+
31
+ @classmethod
32
+ def build(cls):
33
+ tower_url = os.getenv("TOWER_URL", "https://api.tower.dev")
34
+ tower_environment = os.getenv("TOWER_ENVIRONMENT", "default")
35
+ tower_api_key = os.getenv("TOWER_API_KEY")
36
+ tower_jwt = os.getenv("TOWER_JWT")
37
+
38
+ # Replaces the deprecated hugging_face_provider and hugging_face_api_key
39
+ inference_router = os.getenv("TOWER_INFERENCE_ROUTER")
40
+ inference_router_api_key = os.getenv("TOWER_INFERENCE_ROUTER_API_KEY")
41
+ inference_provider = os.getenv("TOWER_INFERENCE_PROVIDER")
42
+
43
+ return cls(
44
+ tower_url=tower_url,
45
+ environment=tower_environment,
46
+ api_key=tower_api_key,
47
+ inference_router=inference_router,
48
+ inference_router_api_key=inference_router_api_key,
49
+ inference_provider=inference_provider,
50
+ jwt=tower_jwt,
51
+ )