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.
- tower/__init__.py +37 -0
- tower/__main__.py +38 -0
- tower/_client.py +375 -0
- tower/_context.py +51 -0
- tower/_dbt.py +299 -0
- tower/_features.py +147 -0
- tower/_llms.py +670 -0
- tower/_tables.py +579 -0
- tower/exceptions.py +34 -0
- tower/polars.py +11 -0
- tower/py.typed +0 -0
- tower/pyarrow.py +7 -0
- tower/pyiceberg.py +19 -0
- tower/tower_api_client/__init__.py +8 -0
- tower/tower_api_client/api/__init__.py +1 -0
- tower/tower_api_client/api/default/__init__.py +1 -0
- tower/tower_api_client/api/default/acknowledge_alert.py +160 -0
- tower/tower_api_client/api/default/acknowledge_all_alerts.py +134 -0
- tower/tower_api_client/api/default/activate_schedules.py +168 -0
- tower/tower_api_client/api/default/cancel_run.py +174 -0
- tower/tower_api_client/api/default/claim_device_login_ticket.py +168 -0
- tower/tower_api_client/api/default/create_account.py +168 -0
- tower/tower_api_client/api/default/create_api_key.py +160 -0
- tower/tower_api_client/api/default/create_app.py +168 -0
- tower/tower_api_client/api/default/create_authenticator.py +172 -0
- tower/tower_api_client/api/default/create_catalog.py +168 -0
- tower/tower_api_client/api/default/create_device_login_ticket.py +136 -0
- tower/tower_api_client/api/default/create_environment.py +168 -0
- tower/tower_api_client/api/default/create_password_reset.py +172 -0
- tower/tower_api_client/api/default/create_schedule.py +168 -0
- tower/tower_api_client/api/default/create_secret.py +185 -0
- tower/tower_api_client/api/default/create_session.py +168 -0
- tower/tower_api_client/api/default/create_team.py +168 -0
- tower/tower_api_client/api/default/deactivate_schedules.py +168 -0
- tower/tower_api_client/api/default/delete_alert.py +103 -0
- tower/tower_api_client/api/default/delete_api_key.py +160 -0
- tower/tower_api_client/api/default/delete_app.py +160 -0
- tower/tower_api_client/api/default/delete_authenticator.py +168 -0
- tower/tower_api_client/api/default/delete_catalog.py +185 -0
- tower/tower_api_client/api/default/delete_schedule.py +168 -0
- tower/tower_api_client/api/default/delete_secret.py +181 -0
- tower/tower_api_client/api/default/delete_team.py +168 -0
- tower/tower_api_client/api/default/delete_team_invitation.py +183 -0
- tower/tower_api_client/api/default/deploy_app.py +216 -0
- tower/tower_api_client/api/default/describe_app.py +245 -0
- tower/tower_api_client/api/default/describe_app_version.py +174 -0
- tower/tower_api_client/api/default/describe_device_login_session.py +162 -0
- tower/tower_api_client/api/default/describe_email_preferences.py +134 -0
- tower/tower_api_client/api/default/describe_run.py +183 -0
- tower/tower_api_client/api/default/describe_run_logs.py +174 -0
- tower/tower_api_client/api/default/describe_secrets_key.py +170 -0
- tower/tower_api_client/api/default/describe_session.py +134 -0
- tower/tower_api_client/api/default/export_catalogs.py +168 -0
- tower/tower_api_client/api/default/export_secrets.py +168 -0
- tower/tower_api_client/api/default/generate_app_statistics.py +170 -0
- tower/tower_api_client/api/default/generate_authenticator.py +134 -0
- tower/tower_api_client/api/default/generate_run_statistics.py +251 -0
- tower/tower_api_client/api/default/generate_runner_credentials.py +140 -0
- tower/tower_api_client/api/default/invite_team_member.py +183 -0
- tower/tower_api_client/api/default/leave_team.py +164 -0
- tower/tower_api_client/api/default/list_alerts.py +271 -0
- tower/tower_api_client/api/default/list_api_keys.py +134 -0
- tower/tower_api_client/api/default/list_app_environments.py +160 -0
- tower/tower_api_client/api/default/list_app_versions.py +160 -0
- tower/tower_api_client/api/default/list_apps.py +278 -0
- tower/tower_api_client/api/default/list_authenticators.py +134 -0
- tower/tower_api_client/api/default/list_catalogs.py +215 -0
- tower/tower_api_client/api/default/list_environments.py +134 -0
- tower/tower_api_client/api/default/list_my_team_invitations.py +134 -0
- tower/tower_api_client/api/default/list_plans.py +181 -0
- tower/tower_api_client/api/default/list_runs.py +291 -0
- tower/tower_api_client/api/default/list_schedules.py +200 -0
- tower/tower_api_client/api/default/list_secret_environments.py +134 -0
- tower/tower_api_client/api/default/list_secrets.py +215 -0
- tower/tower_api_client/api/default/list_team_invitations.py +160 -0
- tower/tower_api_client/api/default/list_team_members.py +160 -0
- tower/tower_api_client/api/default/list_teams.py +134 -0
- tower/tower_api_client/api/default/refresh_session.py +172 -0
- tower/tower_api_client/api/default/remove_team_member.py +183 -0
- tower/tower_api_client/api/default/resend_email_verification.py +89 -0
- tower/tower_api_client/api/default/resend_team_invitation.py +183 -0
- tower/tower_api_client/api/default/run_app.py +188 -0
- tower/tower_api_client/api/default/search_runs.py +272 -0
- tower/tower_api_client/api/default/stream_alerts.py +174 -0
- tower/tower_api_client/api/default/stream_run_logs.py +198 -0
- tower/tower_api_client/api/default/update_account_name.py +183 -0
- tower/tower_api_client/api/default/update_app.py +183 -0
- tower/tower_api_client/api/default/update_catalog.py +183 -0
- tower/tower_api_client/api/default/update_email_preferences.py +171 -0
- tower/tower_api_client/api/default/update_environment.py +183 -0
- tower/tower_api_client/api/default/update_my_team_invitation.py +168 -0
- tower/tower_api_client/api/default/update_password_reset.py +183 -0
- tower/tower_api_client/api/default/update_plan.py +160 -0
- tower/tower_api_client/api/default/update_schedule.py +183 -0
- tower/tower_api_client/api/default/update_secret.py +183 -0
- tower/tower_api_client/api/default/update_team.py +187 -0
- tower/tower_api_client/api/default/update_user.py +168 -0
- tower/tower_api_client/api/default/verify_email.py +172 -0
- tower/tower_api_client/api/feature_flags/__init__.py +1 -0
- tower/tower_api_client/api/feature_flags/get_feature_flag_value.py +164 -0
- tower/tower_api_client/client.py +286 -0
- tower/tower_api_client/errors.py +16 -0
- tower/tower_api_client/models/__init__.py +363 -0
- tower/tower_api_client/models/account.py +50 -0
- tower/tower_api_client/models/acknowledge_alert_response.py +51 -0
- tower/tower_api_client/models/acknowledge_all_alerts_response.py +59 -0
- tower/tower_api_client/models/alert.py +95 -0
- tower/tower_api_client/models/api_key.py +82 -0
- tower/tower_api_client/models/app.py +196 -0
- tower/tower_api_client/models/app_health_status.py +9 -0
- tower/tower_api_client/models/app_statistics.py +63 -0
- tower/tower_api_client/models/app_status.py +9 -0
- tower/tower_api_client/models/app_summary.py +63 -0
- tower/tower_api_client/models/app_version.py +79 -0
- tower/tower_api_client/models/batch_schedule_params.py +51 -0
- tower/tower_api_client/models/batch_schedule_response.py +65 -0
- tower/tower_api_client/models/cancel_run_response.py +57 -0
- tower/tower_api_client/models/catalog.py +98 -0
- tower/tower_api_client/models/catalog_property.py +47 -0
- tower/tower_api_client/models/claim_device_login_ticket_params.py +51 -0
- tower/tower_api_client/models/claim_device_login_ticket_response.py +51 -0
- tower/tower_api_client/models/create_account_params.py +59 -0
- tower/tower_api_client/models/create_account_params_flags_struct.py +39 -0
- tower/tower_api_client/models/create_account_response.py +57 -0
- tower/tower_api_client/models/create_api_key_params.py +51 -0
- tower/tower_api_client/models/create_api_key_response.py +57 -0
- tower/tower_api_client/models/create_app_params.py +79 -0
- tower/tower_api_client/models/create_app_response.py +57 -0
- tower/tower_api_client/models/create_authenticator_params.py +59 -0
- tower/tower_api_client/models/create_authenticator_response.py +57 -0
- tower/tower_api_client/models/create_catalog_params.py +90 -0
- tower/tower_api_client/models/create_catalog_params_type.py +11 -0
- tower/tower_api_client/models/create_catalog_response.py +57 -0
- tower/tower_api_client/models/create_device_login_ticket_response.py +101 -0
- tower/tower_api_client/models/create_environment_params.py +51 -0
- tower/tower_api_client/models/create_environment_response.py +57 -0
- tower/tower_api_client/models/create_password_reset_params.py +51 -0
- tower/tower_api_client/models/create_password_reset_response.py +51 -0
- tower/tower_api_client/models/create_schedule_params.py +131 -0
- tower/tower_api_client/models/create_schedule_params_status.py +9 -0
- tower/tower_api_client/models/create_schedule_response.py +57 -0
- tower/tower_api_client/models/create_secret_params.py +75 -0
- tower/tower_api_client/models/create_secret_response.py +57 -0
- tower/tower_api_client/models/create_session_params.py +69 -0
- tower/tower_api_client/models/create_session_response.py +57 -0
- tower/tower_api_client/models/create_team_params.py +51 -0
- tower/tower_api_client/models/create_team_response.py +57 -0
- tower/tower_api_client/models/delete_api_key_params.py +51 -0
- tower/tower_api_client/models/delete_api_key_response.py +57 -0
- tower/tower_api_client/models/delete_app_response.py +57 -0
- tower/tower_api_client/models/delete_authenticator_params.py +51 -0
- tower/tower_api_client/models/delete_authenticator_response.py +57 -0
- tower/tower_api_client/models/delete_catalog_response.py +57 -0
- tower/tower_api_client/models/delete_schedule_params.py +51 -0
- tower/tower_api_client/models/delete_schedule_response.py +65 -0
- tower/tower_api_client/models/delete_secret_response.py +57 -0
- tower/tower_api_client/models/delete_team_invitation_params.py +51 -0
- tower/tower_api_client/models/delete_team_invitation_response.py +57 -0
- tower/tower_api_client/models/delete_team_params.py +51 -0
- tower/tower_api_client/models/delete_team_response.py +57 -0
- tower/tower_api_client/models/deploy_app_response.py +57 -0
- tower/tower_api_client/models/describe_app_response.py +75 -0
- tower/tower_api_client/models/describe_app_version_response.py +57 -0
- tower/tower_api_client/models/describe_device_login_session_response.py +57 -0
- tower/tower_api_client/models/describe_email_preferences_body.py +57 -0
- tower/tower_api_client/models/describe_run_logs_response.py +65 -0
- tower/tower_api_client/models/describe_run_response.py +57 -0
- tower/tower_api_client/models/describe_secrets_key_response.py +51 -0
- tower/tower_api_client/models/describe_session_response.py +57 -0
- tower/tower_api_client/models/email_subscriptions.py +57 -0
- tower/tower_api_client/models/encrypted_catalog_property.py +55 -0
- tower/tower_api_client/models/environment.py +39 -0
- tower/tower_api_client/models/error_detail.py +57 -0
- tower/tower_api_client/models/error_model.py +114 -0
- tower/tower_api_client/models/export_catalogs_params.py +83 -0
- tower/tower_api_client/models/export_catalogs_response.py +75 -0
- tower/tower_api_client/models/export_secrets_params.py +83 -0
- tower/tower_api_client/models/export_secrets_response.py +75 -0
- tower/tower_api_client/models/exported_catalog.py +98 -0
- tower/tower_api_client/models/exported_catalog_property.py +55 -0
- tower/tower_api_client/models/exported_secret.py +65 -0
- tower/tower_api_client/models/featurebase_identity.py +47 -0
- tower/tower_api_client/models/features.py +75 -0
- tower/tower_api_client/models/generate_app_statistics_response.py +57 -0
- tower/tower_api_client/models/generate_authenticator_response.py +57 -0
- tower/tower_api_client/models/generate_run_statistics_response.py +85 -0
- tower/tower_api_client/models/generate_run_statistics_status_item.py +14 -0
- tower/tower_api_client/models/generate_runner_credentials_response.py +57 -0
- tower/tower_api_client/models/get_feature_flag_response_body.py +78 -0
- tower/tower_api_client/models/get_feature_flag_response_body_value_type.py +11 -0
- tower/tower_api_client/models/invite_team_member_params.py +61 -0
- tower/tower_api_client/models/invite_team_member_response.py +65 -0
- tower/tower_api_client/models/leave_team_response.py +57 -0
- tower/tower_api_client/models/list_account_plans_response.py +75 -0
- tower/tower_api_client/models/list_alerts_response.py +75 -0
- tower/tower_api_client/models/list_api_keys_response.py +65 -0
- tower/tower_api_client/models/list_app_environments_response.py +51 -0
- tower/tower_api_client/models/list_app_versions_response.py +65 -0
- tower/tower_api_client/models/list_apps_filter.py +10 -0
- tower/tower_api_client/models/list_apps_response.py +75 -0
- tower/tower_api_client/models/list_apps_sort.py +11 -0
- tower/tower_api_client/models/list_authenticators_response.py +67 -0
- tower/tower_api_client/models/list_catalogs_response.py +75 -0
- tower/tower_api_client/models/list_environments_response.py +65 -0
- tower/tower_api_client/models/list_my_team_invitations_response.py +65 -0
- tower/tower_api_client/models/list_runs_response.py +75 -0
- tower/tower_api_client/models/list_runs_status_item.py +14 -0
- tower/tower_api_client/models/list_schedules_response.py +75 -0
- tower/tower_api_client/models/list_secret_environments_response.py +51 -0
- tower/tower_api_client/models/list_secrets_response.py +75 -0
- tower/tower_api_client/models/list_team_invitations_response.py +65 -0
- tower/tower_api_client/models/list_team_members_response.py +65 -0
- tower/tower_api_client/models/list_teams_response.py +65 -0
- tower/tower_api_client/models/pagination.py +63 -0
- tower/tower_api_client/models/parameter.py +55 -0
- tower/tower_api_client/models/plan.py +129 -0
- tower/tower_api_client/models/refresh_session_params.py +40 -0
- tower/tower_api_client/models/refresh_session_response.py +67 -0
- tower/tower_api_client/models/remove_team_member_params.py +51 -0
- tower/tower_api_client/models/remove_team_member_response.py +57 -0
- tower/tower_api_client/models/resend_team_invitation_params.py +60 -0
- tower/tower_api_client/models/resend_team_invitation_response.py +57 -0
- tower/tower_api_client/models/run.py +246 -0
- tower/tower_api_client/models/run_app_params.py +86 -0
- tower/tower_api_client/models/run_app_params_parameters.py +44 -0
- tower/tower_api_client/models/run_app_response.py +57 -0
- tower/tower_api_client/models/run_failure_alert.py +55 -0
- tower/tower_api_client/models/run_log_line.py +101 -0
- tower/tower_api_client/models/run_log_line_channel.py +9 -0
- tower/tower_api_client/models/run_parameter.py +47 -0
- tower/tower_api_client/models/run_results.py +79 -0
- tower/tower_api_client/models/run_statistics.py +79 -0
- tower/tower_api_client/models/run_status.py +14 -0
- tower/tower_api_client/models/run_status_group.py +10 -0
- tower/tower_api_client/models/run_timeseries_point.py +97 -0
- tower/tower_api_client/models/runner_credentials.py +71 -0
- tower/tower_api_client/models/schedule.py +126 -0
- tower/tower_api_client/models/schedule_status.py +9 -0
- tower/tower_api_client/models/search_runs_response.py +75 -0
- tower/tower_api_client/models/search_runs_status_item.py +14 -0
- tower/tower_api_client/models/secret.py +65 -0
- tower/tower_api_client/models/session.py +85 -0
- tower/tower_api_client/models/sse_warning.py +49 -0
- tower/tower_api_client/models/statistics_settings.py +75 -0
- tower/tower_api_client/models/statistics_settings_interval.py +9 -0
- tower/tower_api_client/models/stream_alerts_event_error.py +102 -0
- tower/tower_api_client/models/stream_alerts_event_run_failure_alert.py +104 -0
- tower/tower_api_client/models/stream_run_logs_event_log.py +102 -0
- tower/tower_api_client/models/stream_run_logs_event_warning.py +102 -0
- tower/tower_api_client/models/team.py +80 -0
- tower/tower_api_client/models/team_invitation.py +63 -0
- tower/tower_api_client/models/token.py +39 -0
- tower/tower_api_client/models/unverified_authenticator.py +63 -0
- tower/tower_api_client/models/update_account_name_params.py +51 -0
- tower/tower_api_client/models/update_account_name_response.py +57 -0
- tower/tower_api_client/models/update_app_params.py +82 -0
- tower/tower_api_client/models/update_app_response.py +57 -0
- tower/tower_api_client/models/update_catalog_params.py +73 -0
- tower/tower_api_client/models/update_catalog_response.py +57 -0
- tower/tower_api_client/models/update_email_preferences_body.py +57 -0
- tower/tower_api_client/models/update_environment_params.py +51 -0
- tower/tower_api_client/models/update_environment_response.py +57 -0
- tower/tower_api_client/models/update_my_team_invitation_params.py +59 -0
- tower/tower_api_client/models/update_my_team_invitation_response.py +40 -0
- tower/tower_api_client/models/update_password_reset_params.py +51 -0
- tower/tower_api_client/models/update_password_reset_response.py +51 -0
- tower/tower_api_client/models/update_plan_params.py +69 -0
- tower/tower_api_client/models/update_plan_response.py +57 -0
- tower/tower_api_client/models/update_schedule_params.py +121 -0
- tower/tower_api_client/models/update_schedule_params_status.py +9 -0
- tower/tower_api_client/models/update_schedule_response.py +57 -0
- tower/tower_api_client/models/update_secret_params.py +67 -0
- tower/tower_api_client/models/update_secret_response.py +57 -0
- tower/tower_api_client/models/update_team_params.py +59 -0
- tower/tower_api_client/models/update_team_response.py +57 -0
- tower/tower_api_client/models/update_user_params.py +234 -0
- tower/tower_api_client/models/update_user_response.py +57 -0
- tower/tower_api_client/models/user.py +124 -0
- tower/tower_api_client/models/verified_authenticator.py +65 -0
- tower/tower_api_client/models/verify_email_params.py +51 -0
- tower/tower_api_client/models/verify_email_response.py +57 -0
- tower/tower_api_client/types.py +46 -0
- tower/utils/pyarrow.py +328 -0
- tower/utils/tables.py +17 -0
- tower-0.3.34.data/scripts/tower.exe +0 -0
- tower-0.3.34.dist-info/METADATA +433 -0
- tower-0.3.34.dist-info/RECORD +289 -0
- tower-0.3.34.dist-info/WHEEL +4 -0
- 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
|
+
)
|