databay-cli 0.1.0__py3-none-any.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.
- databay/__init__.py +5 -0
- databay/__main__.py +7 -0
- databay/agent_skill.py +184 -0
- databay/api.py +626 -0
- databay/cli/__init__.py +0 -0
- databay/cli/auth.py +377 -0
- databay/cli/build.py +89 -0
- databay/cli/commands/__init__.py +0 -0
- databay/cli/commands/alerting_webhook.py +19 -0
- databay/cli/commands/alerting_webhook_tag.py +14 -0
- databay/cli/commands/alerting_webhook_type.py +14 -0
- databay/cli/commands/alerting_webhook_undelivered.py +15 -0
- databay/cli/commands/announcement.py +17 -0
- databay/cli/commands/api_key.py +18 -0
- databay/cli/commands/api_key_project.py +16 -0
- databay/cli/commands/backfill_reason_code.py +14 -0
- databay/cli/commands/backfill_run.py +17 -0
- databay/cli/commands/canvas.py +16 -0
- databay/cli/commands/canvas_graph.py +15 -0
- databay/cli/commands/canvas_table_check.py +15 -0
- databay/cli/commands/dashboards.py +21 -0
- databay/cli/commands/dashboards_card.py +20 -0
- databay/cli/commands/dashboards_dataset.py +14 -0
- databay/cli/commands/dashboards_folder.py +16 -0
- databay/cli/commands/dashboards_home.py +17 -0
- databay/cli/commands/dashboards_placement.py +16 -0
- databay/cli/commands/db.py +19 -0
- databay/cli/commands/db_column.py +14 -0
- databay/cli/commands/docs_article.py +16 -0
- databay/cli/commands/dq_result.py +14 -0
- databay/cli/commands/dq_rule.py +16 -0
- databay/cli/commands/feature.py +14 -0
- databay/cli/commands/google_ads_source.py +14 -0
- databay/cli/commands/google_analytics_source.py +14 -0
- databay/cli/commands/instagram_account.py +14 -0
- databay/cli/commands/instagram_source.py +14 -0
- databay/cli/commands/linkedin_account.py +14 -0
- databay/cli/commands/linkedin_source.py +14 -0
- databay/cli/commands/me.py +14 -0
- databay/cli/commands/meta_ads_source.py +14 -0
- databay/cli/commands/metric_models.py +26 -0
- databay/cli/commands/metrics.py +14 -0
- databay/cli/commands/models.py +16 -0
- databay/cli/commands/nlq.py +15 -0
- databay/cli/commands/notebook_credential.py +16 -0
- databay/cli/commands/org.py +14 -0
- databay/cli/commands/org_member.py +17 -0
- databay/cli/commands/org_offboarding.py +14 -0
- databay/cli/commands/pipe.py +26 -0
- databay/cli/commands/pipe_backfill_run.py +14 -0
- databay/cli/commands/pipe_config_option.py +14 -0
- databay/cli/commands/pipe_destination.py +16 -0
- databay/cli/commands/pipe_leftover.py +16 -0
- databay/cli/commands/pipe_sink_type.py +14 -0
- databay/cli/commands/pipe_source_type.py +14 -0
- databay/cli/commands/pipe_status_bucket.py +14 -0
- databay/cli/commands/pipe_stuck_delete.py +14 -0
- databay/cli/commands/plan.py +14 -0
- databay/cli/commands/project.py +21 -0
- databay/cli/commands/project_budget.py +15 -0
- databay/cli/commands/project_member.py +18 -0
- databay/cli/commands/project_privilege.py +16 -0
- databay/cli/commands/project_share.py +16 -0
- databay/cli/commands/provision.py +14 -0
- databay/cli/commands/provisioning.py +14 -0
- databay/cli/commands/registry.py +14 -0
- databay/cli/commands/resource_home.py +14 -0
- databay/cli/commands/schema_change.py +18 -0
- databay/cli/commands/schema_history.py +17 -0
- databay/cli/commands/schema_version.py +16 -0
- databay/cli/commands/shopify_store.py +14 -0
- databay/cli/commands/sink_data_type.py +14 -0
- databay/cli/commands/source.py +24 -0
- databay/cli/commands/source_catalog.py +15 -0
- databay/cli/commands/source_check_run.py +17 -0
- databay/cli/commands/source_hidden_type.py +14 -0
- databay/cli/commands/source_name.py +14 -0
- databay/cli/commands/source_pipe.py +14 -0
- databay/cli/commands/source_publication.py +16 -0
- databay/cli/commands/source_readiness_override.py +15 -0
- databay/cli/commands/source_setup_choice.py +16 -0
- databay/cli/commands/source_stream.py +15 -0
- databay/cli/commands/source_table.py +14 -0
- databay/cli/commands/ssbi_job.py +18 -0
- databay/cli/commands/transform_type.py +14 -0
- databay/cli/commands/usage.py +14 -0
- databay/cli/commands/warehouse.py +21 -0
- databay/cli/commands/warehouse_access_assignment.py +16 -0
- databay/cli/commands/warehouse_access_role.py +22 -0
- databay/cli/commands/warehouse_access_settings.py +17 -0
- databay/cli/commands/warehouse_login.py +20 -0
- databay/cli/commands/warehouse_principal.py +17 -0
- databay/cli/commands/warehouse_service_account.py +17 -0
- databay/cli/commands/warehouse_service_account_credential.py +18 -0
- databay/cli/common.py +93 -0
- databay/cli/console.py +121 -0
- databay/cli/flags.py +183 -0
- databay/cli/handwritten.py +22 -0
- databay/cli/main.py +198 -0
- databay/cli/metrics.py +171 -0
- databay/cli/org.py +193 -0
- databay/cli/output.py +134 -0
- databay/cli/pipe_wait.py +357 -0
- databay/cli/profile.py +230 -0
- databay/cli/runner.py +432 -0
- databay/cli/skill.py +64 -0
- databay/cli/sql.py +84 -0
- databay/compat.py +161 -0
- databay/credentials.py +217 -0
- databay/error_catalog.py +226 -0
- databay/error_codes.py +3265 -0
- databay/errors.py +222 -0
- databay/feature_header.py +40 -0
- databay/invoke.py +225 -0
- databay/locks.py +41 -0
- databay/login_flow.py +245 -0
- databay/model_base.py +40 -0
- databay/models/__init__.py +0 -0
- databay/models/alerting_webhook.py +46 -0
- databay/models/alerting_webhook_tag.py +11 -0
- databay/models/alerting_webhook_type.py +11 -0
- databay/models/alerting_webhook_undelivered.py +23 -0
- databay/models/announcement.py +37 -0
- databay/models/api_key.py +81 -0
- databay/models/api_key_project.py +29 -0
- databay/models/backfill_reason_code.py +11 -0
- databay/models/backfill_run.py +41 -0
- databay/models/canvas.py +68 -0
- databay/models/canvas_graph.py +86 -0
- databay/models/canvas_table_check.py +181 -0
- databay/models/dashboards.py +79 -0
- databay/models/dashboards_card.py +37 -0
- databay/models/dashboards_dataset.py +27 -0
- databay/models/dashboards_folder.py +18 -0
- databay/models/dashboards_home.py +28 -0
- databay/models/db.py +34 -0
- databay/models/db_column.py +11 -0
- databay/models/docs_article.py +59 -0
- databay/models/dq_result.py +27 -0
- databay/models/dq_rule.py +43 -0
- databay/models/feature.py +15 -0
- databay/models/google_ads_source.py +11 -0
- databay/models/google_analytics_source.py +11 -0
- databay/models/instagram_source.py +11 -0
- databay/models/linkedin_source.py +11 -0
- databay/models/me.py +71 -0
- databay/models/meta_ads_source.py +11 -0
- databay/models/metric_models.py +274 -0
- databay/models/metrics.py +158 -0
- databay/models/models.py +73 -0
- databay/models/nlq.py +92 -0
- databay/models/notebook_credential.py +51 -0
- databay/models/org.py +44 -0
- databay/models/org_member.py +123 -0
- databay/models/org_offboarding.py +36 -0
- databay/models/pipe.py +340 -0
- databay/models/pipe_config_option.py +25 -0
- databay/models/pipe_destination.py +128 -0
- databay/models/pipe_leftover.py +51 -0
- databay/models/pipe_sink_type.py +11 -0
- databay/models/pipe_source_type.py +11 -0
- databay/models/pipe_status_bucket.py +13 -0
- databay/models/pipe_stuck_delete.py +25 -0
- databay/models/plan.py +37 -0
- databay/models/project.py +154 -0
- databay/models/project_budget.py +22 -0
- databay/models/project_member.py +67 -0
- databay/models/project_privilege.py +21 -0
- databay/models/project_share.py +76 -0
- databay/models/provision.py +12 -0
- databay/models/provisioning.py +20 -0
- databay/models/registry.py +18 -0
- databay/models/resource_home.py +35 -0
- databay/models/schema_change.py +96 -0
- databay/models/schema_history.py +32 -0
- databay/models/schema_version.py +23 -0
- databay/models/shared.py +875 -0
- databay/models/shopify_store.py +19 -0
- databay/models/sink_data_type.py +11 -0
- databay/models/source.py +263 -0
- databay/models/source_catalog.py +82 -0
- databay/models/source_check_run.py +63 -0
- databay/models/source_hidden_type.py +13 -0
- databay/models/source_name.py +18 -0
- databay/models/source_pipe.py +16 -0
- databay/models/source_publication.py +30 -0
- databay/models/source_readiness_override.py +13 -0
- databay/models/source_setup_choice.py +33 -0
- databay/models/source_stream.py +65 -0
- databay/models/source_table.py +27 -0
- databay/models/sql.py +31 -0
- databay/models/ssbi_job.py +97 -0
- databay/models/transform_type.py +40 -0
- databay/models/usage.py +112 -0
- databay/models/warehouse.py +38 -0
- databay/models/warehouse_access_assignment.py +70 -0
- databay/models/warehouse_access_role.py +148 -0
- databay/models/warehouse_access_settings.py +35 -0
- databay/models/warehouse_login.py +20 -0
- databay/models/warehouse_principal.py +49 -0
- databay/models/warehouse_service_account.py +35 -0
- databay/models/warehouse_service_account_credential.py +11 -0
- databay/oidc.py +205 -0
- databay/operation.py +191 -0
- databay/operations/__init__.py +0 -0
- databay/operations/alerting_webhook.py +317 -0
- databay/operations/alerting_webhook_tag.py +35 -0
- databay/operations/alerting_webhook_type.py +35 -0
- databay/operations/alerting_webhook_undelivered.py +74 -0
- databay/operations/announcement.py +135 -0
- databay/operations/api_key.py +198 -0
- databay/operations/api_key_project.py +130 -0
- databay/operations/backfill_reason_code.py +35 -0
- databay/operations/backfill_run.py +245 -0
- databay/operations/canvas.py +136 -0
- databay/operations/canvas_graph.py +102 -0
- databay/operations/canvas_table_check.py +101 -0
- databay/operations/dashboards.py +441 -0
- databay/operations/dashboards_card.py +406 -0
- databay/operations/dashboards_dataset.py +50 -0
- databay/operations/dashboards_folder.py +146 -0
- databay/operations/dashboards_home.py +176 -0
- databay/operations/dashboards_placement.py +152 -0
- databay/operations/db.py +337 -0
- databay/operations/db_column.py +57 -0
- databay/operations/docs_article.py +101 -0
- databay/operations/dq_result.py +43 -0
- databay/operations/dq_rule.py +155 -0
- databay/operations/feature.py +35 -0
- databay/operations/google_ads_source.py +40 -0
- databay/operations/google_analytics_source.py +40 -0
- databay/operations/instagram_account.py +47 -0
- databay/operations/instagram_source.py +40 -0
- databay/operations/linkedin_account.py +47 -0
- databay/operations/linkedin_source.py +40 -0
- databay/operations/me.py +57 -0
- databay/operations/meta_ads_source.py +40 -0
- databay/operations/metric_models.py +612 -0
- databay/operations/metrics.py +218 -0
- databay/operations/models.py +123 -0
- databay/operations/nlq.py +100 -0
- databay/operations/notebook_credential.py +95 -0
- databay/operations/org.py +79 -0
- databay/operations/org_member.py +192 -0
- databay/operations/org_offboarding.py +43 -0
- databay/operations/pipe.py +882 -0
- databay/operations/pipe_backfill_run.py +72 -0
- databay/operations/pipe_config_option.py +50 -0
- databay/operations/pipe_destination.py +178 -0
- databay/operations/pipe_leftover.py +110 -0
- databay/operations/pipe_sink_type.py +35 -0
- databay/operations/pipe_source_type.py +35 -0
- databay/operations/pipe_status_bucket.py +35 -0
- databay/operations/pipe_stuck_delete.py +52 -0
- databay/operations/plan.py +35 -0
- databay/operations/project.py +346 -0
- databay/operations/project_budget.py +135 -0
- databay/operations/project_member.py +226 -0
- databay/operations/project_privilege.py +143 -0
- databay/operations/project_share.py +160 -0
- databay/operations/provision.py +35 -0
- databay/operations/provisioning.py +35 -0
- databay/operations/registry.py +35 -0
- databay/operations/resource_home.py +80 -0
- databay/operations/schema_change.py +222 -0
- databay/operations/schema_history.py +136 -0
- databay/operations/schema_version.py +115 -0
- databay/operations/shopify_store.py +40 -0
- databay/operations/sink_data_type.py +43 -0
- databay/operations/source.py +1207 -0
- databay/operations/source_catalog.py +135 -0
- databay/operations/source_check_run.py +242 -0
- databay/operations/source_hidden_type.py +59 -0
- databay/operations/source_name.py +69 -0
- databay/operations/source_pipe.py +57 -0
- databay/operations/source_publication.py +139 -0
- databay/operations/source_readiness_override.py +96 -0
- databay/operations/source_setup_choice.py +161 -0
- databay/operations/source_stream.py +99 -0
- databay/operations/source_table.py +59 -0
- databay/operations/sql.py +54 -0
- databay/operations/ssbi_job.py +242 -0
- databay/operations/transform_type.py +35 -0
- databay/operations/usage.py +35 -0
- databay/operations/warehouse.py +343 -0
- databay/operations/warehouse_access_assignment.py +179 -0
- databay/operations/warehouse_access_role.py +473 -0
- databay/operations/warehouse_access_settings.py +172 -0
- databay/operations/warehouse_login.py +308 -0
- databay/operations/warehouse_principal.py +174 -0
- databay/operations/warehouse_service_account.py +206 -0
- databay/operations/warehouse_service_account_credential.py +285 -0
- databay/paths.py +32 -0
- databay/platform_check.py +20 -0
- databay/profiles.py +181 -0
- databay/redact.py +22 -0
- databay/reference.py +218 -0
- databay/resource.py +167 -0
- databay/resources/__init__.py +0 -0
- databay/resources/alerting_webhook.py +84 -0
- databay/resources/alerting_webhook_tag.py +21 -0
- databay/resources/alerting_webhook_type.py +21 -0
- databay/resources/alerting_webhook_undelivered.py +35 -0
- databay/resources/announcement.py +45 -0
- databay/resources/api_key.py +56 -0
- databay/resources/api_key_project.py +47 -0
- databay/resources/backfill_reason_code.py +21 -0
- databay/resources/backfill_run.py +78 -0
- databay/resources/canvas.py +43 -0
- databay/resources/canvas_graph.py +38 -0
- databay/resources/canvas_table_check.py +38 -0
- databay/resources/dashboards.py +105 -0
- databay/resources/dashboards_card.py +97 -0
- databay/resources/dashboards_dataset.py +27 -0
- databay/resources/dashboards_folder.py +47 -0
- databay/resources/dashboards_home.py +59 -0
- databay/resources/dashboards_placement.py +47 -0
- databay/resources/db.py +87 -0
- databay/resources/db_column.py +28 -0
- databay/resources/docs_article.py +37 -0
- databay/resources/dq_result.py +25 -0
- databay/resources/dq_rule.py +48 -0
- databay/resources/feature.py +21 -0
- databay/resources/google_ads_source.py +25 -0
- databay/resources/google_analytics_source.py +25 -0
- databay/resources/instagram_account.py +27 -0
- databay/resources/instagram_source.py +25 -0
- databay/resources/linkedin_account.py +27 -0
- databay/resources/linkedin_source.py +25 -0
- databay/resources/me.py +27 -0
- databay/resources/meta_ads_source.py +25 -0
- databay/resources/metric_models.py +154 -0
- databay/resources/metrics.py +57 -0
- databay/resources/models.py +42 -0
- databay/resources/nlq.py +34 -0
- databay/resources/notebook_credential.py +41 -0
- databay/resources/org.py +29 -0
- databay/resources/org_member.py +53 -0
- databay/resources/org_offboarding.py +25 -0
- databay/resources/pipe.py +208 -0
- databay/resources/pipe_backfill_run.py +28 -0
- databay/resources/pipe_config_option.py +26 -0
- databay/resources/pipe_destination.py +49 -0
- databay/resources/pipe_leftover.py +42 -0
- databay/resources/pipe_sink_type.py +21 -0
- databay/resources/pipe_source_type.py +21 -0
- databay/resources/pipe_status_bucket.py +21 -0
- databay/resources/pipe_stuck_delete.py +27 -0
- databay/resources/plan.py +21 -0
- databay/resources/project.py +92 -0
- databay/resources/project_budget.py +38 -0
- databay/resources/project_member.py +68 -0
- databay/resources/project_privilege.py +48 -0
- databay/resources/project_share.py +47 -0
- databay/resources/provision.py +21 -0
- databay/resources/provisioning.py +21 -0
- databay/resources/registry.py +21 -0
- databay/resources/resource_home.py +29 -0
- databay/resources/schema_change.py +62 -0
- databay/resources/schema_history.py +52 -0
- databay/resources/schema_version.py +43 -0
- databay/resources/shopify_store.py +25 -0
- databay/resources/sink_data_type.py +25 -0
- databay/resources/source.py +132 -0
- databay/resources/source_catalog.py +34 -0
- databay/resources/source_check_run.py +67 -0
- databay/resources/source_hidden_type.py +25 -0
- databay/resources/source_name.py +27 -0
- databay/resources/source_pipe.py +28 -0
- databay/resources/source_publication.py +47 -0
- databay/resources/source_readiness_override.py +37 -0
- databay/resources/source_setup_choice.py +50 -0
- databay/resources/source_stream.py +38 -0
- databay/resources/source_table.py +27 -0
- databay/resources/sql.py +27 -0
- databay/resources/ssbi_job.py +69 -0
- databay/resources/transform_type.py +21 -0
- databay/resources/usage.py +21 -0
- databay/resources/warehouse.py +136 -0
- databay/resources/warehouse_access_assignment.py +51 -0
- databay/resources/warehouse_access_role.py +118 -0
- databay/resources/warehouse_access_settings.py +56 -0
- databay/resources/warehouse_login.py +81 -0
- databay/resources/warehouse_principal.py +51 -0
- databay/resources/warehouse_service_account.py +61 -0
- databay/resources/warehouse_service_account_credential.py +78 -0
- databay/sdk.py +77 -0
- databay/settings.py +90 -0
- databay/skill/SKILL.md +357 -0
- databay/skill/references/commands.md +7025 -0
- databay/skill/references/errors.md +727 -0
- databay/spec.py +362 -0
- databay/stored_token.py +134 -0
- databay/tokens.py +108 -0
- databay/transport.py +357 -0
- databay/update_check.py +151 -0
- databay/version.py +6 -0
- databay/waits.py +330 -0
- databay_cli-0.1.0.dist-info/METADATA +42 -0
- databay_cli-0.1.0.dist-info/RECORD +403 -0
- databay_cli-0.1.0.dist-info/WHEEL +4 -0
- databay_cli-0.1.0.dist-info/entry_points.txt +2 -0
- databay_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
databay/waits.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Waiting on what an operation started, by polling its declared status operation.
|
|
3
|
+
|
|
4
|
+
Each status value is pending, success or failure, as the ``Wait`` declares; a value it does not
|
|
5
|
+
name (a newer server's) stops the wait with ``unexpected_wait_state``. A 404 is
|
|
6
|
+
``wait_target_gone``, or ``wait_feature_disabled`` when it says the feature is off. 5xx, 408, 429 and
|
|
7
|
+
network failures are retried within the timeout until ``max_transient_in_a_row`` in a row; any other
|
|
8
|
+
error answer stops the wait with its own error.
|
|
9
|
+
Running out of time is ``wait_pending``: the same wait can be resumed.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import importlib
|
|
15
|
+
import time
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
19
|
+
|
|
20
|
+
from databay.errors import (
|
|
21
|
+
DatabayError,
|
|
22
|
+
DatabayFeatureDisabled,
|
|
23
|
+
DatabayNetworkError,
|
|
24
|
+
DatabayResponseInvalid,
|
|
25
|
+
DatabayWaitError,
|
|
26
|
+
)
|
|
27
|
+
from databay.invoke import CallValues, build_request, decode, json_text, pointer
|
|
28
|
+
from databay.transport import error_from, is_transient, retry_after_seconds
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from databay.invoke import Session
|
|
32
|
+
from databay.operation import Operation, Wait
|
|
33
|
+
from databay.transport import Request
|
|
34
|
+
|
|
35
|
+
#: ``not_started``: the trigger's answer names nothing to wait on.
|
|
36
|
+
Outcome = Literal["success", "pending", "failure", "not_started"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(slots=True)
|
|
40
|
+
class WaitOutcome:
|
|
41
|
+
outcome: Outcome
|
|
42
|
+
#: The status value last seen, as the declaration spells it; ``None`` before the first answer.
|
|
43
|
+
state: str | None
|
|
44
|
+
#: The last status answer, as JSON.
|
|
45
|
+
status: Any
|
|
46
|
+
elapsed_s: float
|
|
47
|
+
#: The status operation's parameters, by name: what resuming the wait polls again.
|
|
48
|
+
bound: dict[str, str]
|
|
49
|
+
status_operation: str
|
|
50
|
+
#: Why a successful wait ended, when it can end more than one way.
|
|
51
|
+
reason: str | None = None
|
|
52
|
+
|
|
53
|
+
def to_dict(self) -> dict[str, Any]:
|
|
54
|
+
data = {
|
|
55
|
+
"outcome": self.outcome,
|
|
56
|
+
"state": self.state,
|
|
57
|
+
"elapsed_s": round(self.elapsed_s, 1),
|
|
58
|
+
"status_operation": self.status_operation,
|
|
59
|
+
"status": self.status,
|
|
60
|
+
}
|
|
61
|
+
return data if self.reason is None else {**data, "reason": self.reason}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def operation(operation_id: str) -> Operation:
|
|
65
|
+
"""The generated ``Operation`` with this id."""
|
|
66
|
+
from databay.spec import OPERATIONS # noqa: PLC0415
|
|
67
|
+
|
|
68
|
+
module, attribute = OPERATIONS[operation_id]
|
|
69
|
+
return getattr(importlib.import_module(f"databay.operations.{module}"), attribute)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def not_started(wait: Wait) -> WaitOutcome:
|
|
73
|
+
"""The outcome of a wait whose trigger started nothing: the answer has no value to bind."""
|
|
74
|
+
return WaitOutcome("not_started", None, None, 0.0, {}, wait.status_operation)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def bind(wait: Wait, trigger: Any, trigger_path: dict[str, str]) -> dict[str, str] | None: # noqa: ANN401
|
|
78
|
+
"""
|
|
79
|
+
The status operation's parameters, from the trigger's answer or its own path parameters.
|
|
80
|
+
|
|
81
|
+
``None`` when the answer, already validated against its model, leaves a bound field out or
|
|
82
|
+
null: the trigger started nothing to wait on.
|
|
83
|
+
"""
|
|
84
|
+
bound: dict[str, str] = {}
|
|
85
|
+
for parameter, source in wait.bind:
|
|
86
|
+
if source.startswith("path:"):
|
|
87
|
+
bound[parameter] = trigger_path[source.removeprefix("path:")]
|
|
88
|
+
continue
|
|
89
|
+
try:
|
|
90
|
+
value = pointer(trigger, source)
|
|
91
|
+
except (KeyError, IndexError, ValueError):
|
|
92
|
+
value = None
|
|
93
|
+
if value is None:
|
|
94
|
+
return None
|
|
95
|
+
bound[parameter] = json_text(value)
|
|
96
|
+
return bound
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def classify(wait: Wait, status: Any) -> tuple[Outcome | None, str]: # noqa: ANN401
|
|
100
|
+
"""The outcome a status answer means, and the value it read; ``None`` for a value the wait does not name."""
|
|
101
|
+
value = json_text(pointer(status, wait.field))
|
|
102
|
+
for outcome, values in (("pending", wait.pending), ("success", wait.success), ("failure", wait.failure)):
|
|
103
|
+
if value in values:
|
|
104
|
+
return outcome, value
|
|
105
|
+
for conditional_value, condition in wait.conditional:
|
|
106
|
+
if conditional_value != value:
|
|
107
|
+
continue
|
|
108
|
+
second = json_text(pointer(status, condition.field))
|
|
109
|
+
for outcome, values in (
|
|
110
|
+
("pending", condition.pending),
|
|
111
|
+
("success", condition.success),
|
|
112
|
+
("failure", condition.failure),
|
|
113
|
+
):
|
|
114
|
+
if second in values:
|
|
115
|
+
return outcome, f"{value} ({condition.field.lstrip('/')}={second})"
|
|
116
|
+
return None, f"{value} ({condition.field.lstrip('/')}={second})"
|
|
117
|
+
return None, value
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _status_values(status_op: Operation, bound: dict[str, str]) -> CallValues:
|
|
121
|
+
values = CallValues()
|
|
122
|
+
for param in status_op.params:
|
|
123
|
+
if param.name not in bound:
|
|
124
|
+
continue
|
|
125
|
+
target = values.path if param.location == "path" else values.query
|
|
126
|
+
target[param.name] = bound[param.name]
|
|
127
|
+
return values
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(frozen=True, slots=True)
|
|
131
|
+
class Reading:
|
|
132
|
+
"""What one tick of a wait read, and whether it ends the wait."""
|
|
133
|
+
|
|
134
|
+
outcome: Outcome
|
|
135
|
+
state: str
|
|
136
|
+
status: Any
|
|
137
|
+
status_operation: str
|
|
138
|
+
#: Raised to end the wait, with the wait's last state as its ``outcome``.
|
|
139
|
+
stop: DatabayWaitError | None = None
|
|
140
|
+
#: Why a successful wait ended, when it can end more than one way.
|
|
141
|
+
reason: str | None = None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class _TransientFailureError(Exception):
|
|
145
|
+
"""A tick's request got an answer a later tick can get past: 5xx, 408, 429 or a network failure."""
|
|
146
|
+
|
|
147
|
+
def __init__(self, failure: DatabayError, retry_after: float | None) -> None:
|
|
148
|
+
super().__init__(failure.message)
|
|
149
|
+
self.failure = failure
|
|
150
|
+
self.retry_after = retry_after
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
#: Sends a tick's request and answers its JSON. It raises what stops the wait, and a transient failure ends the tick.
|
|
154
|
+
Fetch = Callable[["Operation", "Request"], Any]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def read_status(wait: Wait, raw: Any, command: str) -> Reading: # noqa: ANN401
|
|
158
|
+
"""A status answer as ``wait`` classifies it; a failure or a value it does not name stops the wait."""
|
|
159
|
+
try:
|
|
160
|
+
outcome, state = classify(wait, raw)
|
|
161
|
+
except (KeyError, IndexError, ValueError):
|
|
162
|
+
raise DatabayResponseInvalid(
|
|
163
|
+
f"{command}: the answer has no {wait.field}", details={"field": wait.field}
|
|
164
|
+
) from None
|
|
165
|
+
if outcome is None:
|
|
166
|
+
stop = DatabayWaitError(
|
|
167
|
+
f"{command}: {wait.field.lstrip('/')} is {state!r}, a state this CLI does not know",
|
|
168
|
+
code="unexpected_wait_state",
|
|
169
|
+
details={"state": state},
|
|
170
|
+
)
|
|
171
|
+
return Reading("failure", state, raw, wait.status_operation, stop)
|
|
172
|
+
if outcome == "failure":
|
|
173
|
+
reason = _error_text(wait, raw)
|
|
174
|
+
stop = DatabayWaitError(
|
|
175
|
+
f"{command}: ended {state}" + (f": {reason}" if reason else ""),
|
|
176
|
+
details={"state": state, "error": reason},
|
|
177
|
+
)
|
|
178
|
+
return Reading("failure", state, raw, wait.status_operation, stop)
|
|
179
|
+
return Reading(outcome, state, raw, wait.status_operation)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def poll(
|
|
183
|
+
session: Session,
|
|
184
|
+
step: Callable[[Fetch], Reading],
|
|
185
|
+
*,
|
|
186
|
+
command: str,
|
|
187
|
+
status_operation: str,
|
|
188
|
+
bound: dict[str, str],
|
|
189
|
+
limit: float,
|
|
190
|
+
on_state: Callable[[WaitOutcome], None] | None = None,
|
|
191
|
+
clock: Callable[[], float] = time.monotonic,
|
|
192
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
193
|
+
) -> WaitOutcome:
|
|
194
|
+
"""
|
|
195
|
+
Run ``step`` once per tick until a reading ends the wait or ``limit`` seconds pass.
|
|
196
|
+
|
|
197
|
+
``step`` sends its requests through the ``Fetch`` it is given, which applies the rules every
|
|
198
|
+
wait shares. A request after a tick's first is not sent once ``limit`` plus the policy's
|
|
199
|
+
``tick_grace_s`` has passed, so a slow tick cannot pile its requests' timeouts on top of the
|
|
200
|
+
limit. ``command`` names the wait in the timeout message.
|
|
201
|
+
"""
|
|
202
|
+
policy = session.transport.settings.wait
|
|
203
|
+
started = clock()
|
|
204
|
+
interval = policy.first_interval_s
|
|
205
|
+
transient = 0
|
|
206
|
+
last = WaitOutcome("pending", None, None, 0.0, bound, status_operation)
|
|
207
|
+
tick_has_sent = False
|
|
208
|
+
|
|
209
|
+
def out_of_time(elapsed: float) -> DatabayWaitError:
|
|
210
|
+
last.outcome = "pending"
|
|
211
|
+
last.elapsed_s = elapsed
|
|
212
|
+
return DatabayWaitError(
|
|
213
|
+
f"{command}: still {last.state or 'unknown'} after {round(elapsed)}s",
|
|
214
|
+
code="wait_pending",
|
|
215
|
+
outcome=last,
|
|
216
|
+
details={"state": last.state, "timeout_s": limit},
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def fetch(op: Operation, request: Request) -> Any: # noqa: ANN401
|
|
220
|
+
nonlocal tick_has_sent
|
|
221
|
+
if tick_has_sent and clock() - started >= limit + policy.tick_grace_s:
|
|
222
|
+
raise out_of_time(clock() - started)
|
|
223
|
+
tick_has_sent = True
|
|
224
|
+
try:
|
|
225
|
+
answer = session.transport.exchange(request, retries=False)
|
|
226
|
+
except DatabayNetworkError as exc:
|
|
227
|
+
raise _TransientFailureError(exc, None) from None
|
|
228
|
+
if answer.status == 404: # noqa: PLR2004
|
|
229
|
+
last.outcome = "failure"
|
|
230
|
+
last.elapsed_s = clock() - started
|
|
231
|
+
refusal = error_from(answer, request)
|
|
232
|
+
if isinstance(refusal, DatabayFeatureDisabled):
|
|
233
|
+
raise DatabayWaitError(
|
|
234
|
+
f"{op.command}: the feature {refusal.feature} is off (source: {refusal.source})",
|
|
235
|
+
code="wait_feature_disabled",
|
|
236
|
+
hint=refusal.hint,
|
|
237
|
+
outcome=last,
|
|
238
|
+
details={"bound": bound, **refusal.details},
|
|
239
|
+
)
|
|
240
|
+
raise DatabayWaitError(
|
|
241
|
+
f"{op.command}: what the wait polled is gone (HTTP 404)",
|
|
242
|
+
code="wait_target_gone",
|
|
243
|
+
outcome=last,
|
|
244
|
+
details={"bound": bound},
|
|
245
|
+
)
|
|
246
|
+
if is_transient(answer.status):
|
|
247
|
+
retry_after = retry_after_seconds(answer.headers.get("retry-after"))
|
|
248
|
+
raise _TransientFailureError(error_from(answer, request), retry_after)
|
|
249
|
+
if answer.status >= 400: # noqa: PLR2004
|
|
250
|
+
raise error_from(answer, request)
|
|
251
|
+
raw, _ = decode(op, answer)
|
|
252
|
+
return raw
|
|
253
|
+
|
|
254
|
+
while True:
|
|
255
|
+
retry_after: float | None = None
|
|
256
|
+
tick_has_sent = False
|
|
257
|
+
try:
|
|
258
|
+
reading = step(fetch)
|
|
259
|
+
except _TransientFailureError as exc:
|
|
260
|
+
retry_after = exc.retry_after
|
|
261
|
+
transient += 1
|
|
262
|
+
if transient >= policy.max_transient_in_a_row:
|
|
263
|
+
exc.failure.details = {**exc.failure.details, "wait": last.to_dict()}
|
|
264
|
+
raise exc.failure from None
|
|
265
|
+
else:
|
|
266
|
+
transient = 0
|
|
267
|
+
changed = reading.state != last.state
|
|
268
|
+
last = WaitOutcome(
|
|
269
|
+
reading.outcome,
|
|
270
|
+
reading.state,
|
|
271
|
+
reading.status,
|
|
272
|
+
clock() - started,
|
|
273
|
+
bound,
|
|
274
|
+
reading.status_operation,
|
|
275
|
+
reading.reason,
|
|
276
|
+
)
|
|
277
|
+
if changed and on_state is not None:
|
|
278
|
+
on_state(last)
|
|
279
|
+
if reading.stop is not None:
|
|
280
|
+
reading.stop.outcome = last
|
|
281
|
+
raise reading.stop
|
|
282
|
+
if reading.outcome == "success":
|
|
283
|
+
return last
|
|
284
|
+
elapsed = clock() - started
|
|
285
|
+
if elapsed >= limit:
|
|
286
|
+
raise out_of_time(elapsed)
|
|
287
|
+
pause = interval
|
|
288
|
+
if retry_after is not None:
|
|
289
|
+
pause = min(max(interval, retry_after), session.transport.settings.retry.retry_after_cap_s)
|
|
290
|
+
sleep(min(pause, limit - elapsed))
|
|
291
|
+
interval = min(interval * policy.backoff, policy.max_interval_s)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def run(
|
|
295
|
+
session: Session,
|
|
296
|
+
wait: Wait,
|
|
297
|
+
bound: dict[str, str],
|
|
298
|
+
*,
|
|
299
|
+
timeout_s: float | None,
|
|
300
|
+
project: str | None = None,
|
|
301
|
+
on_state: Callable[[WaitOutcome], None] | None = None,
|
|
302
|
+
clock: Callable[[], float] = time.monotonic,
|
|
303
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
304
|
+
) -> WaitOutcome:
|
|
305
|
+
"""Poll until success; raise ``DatabayWaitError`` (or the error that stopped it) otherwise."""
|
|
306
|
+
policy = session.transport.settings.wait
|
|
307
|
+
limit = timeout_s if timeout_s is not None else (wait.timeout_s or policy.default_timeout_s)
|
|
308
|
+
status_op = operation(wait.status_operation)
|
|
309
|
+
request = build_request(status_op, _status_values(status_op, bound), project if status_op.project else None)
|
|
310
|
+
return poll(
|
|
311
|
+
session,
|
|
312
|
+
lambda fetch: read_status(wait, fetch(status_op, request), status_op.command),
|
|
313
|
+
command=status_op.command,
|
|
314
|
+
status_operation=wait.status_operation,
|
|
315
|
+
bound=bound,
|
|
316
|
+
limit=limit,
|
|
317
|
+
on_state=on_state,
|
|
318
|
+
clock=clock,
|
|
319
|
+
sleep=sleep,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _error_text(wait: Wait, raw: Any) -> str | None: # noqa: ANN401
|
|
324
|
+
if wait.error_field is None:
|
|
325
|
+
return None
|
|
326
|
+
try:
|
|
327
|
+
value = pointer(raw, wait.error_field)
|
|
328
|
+
except (KeyError, IndexError, ValueError):
|
|
329
|
+
return None
|
|
330
|
+
return None if value is None else json_text(value)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: databay-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Databay SDK and the databay CLI, generated from the Databay client contract
|
|
5
|
+
Project-URL: Homepage, https://databurst.tech
|
|
6
|
+
Project-URL: Repository, https://github.com/data-burst/databay-clients
|
|
7
|
+
Project-URL: Changelog, https://github.com/data-burst/databay-clients/blob/main/python/CHANGELOG.md
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Operating System :: MacOS
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
|
+
Requires-Dist: click<9,>=8.1.7
|
|
16
|
+
Requires-Dist: httpx<1,>=0.27.2
|
|
17
|
+
Requires-Dist: keyring<26,>=25.5.0
|
|
18
|
+
Requires-Dist: platformdirs<5,>=4.3.6
|
|
19
|
+
Requires-Dist: pydantic<3,>=2.9.2
|
|
20
|
+
Requires-Dist: rich>=13.9.4
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# databay-cli
|
|
24
|
+
|
|
25
|
+
The SDK and the `databay` command for a [Databay](https://databurst.tech) server. The agent skill that
|
|
26
|
+
ships with it lets an agent such as Claude Code drive the server through the command.
|
|
27
|
+
|
|
28
|
+
Needs Python 3.12 or newer, on Linux or macOS. The package is `databay-cli`; the `databay` project on
|
|
29
|
+
PyPI is unrelated.
|
|
30
|
+
|
|
31
|
+
Until the first PyPI release, install it from the repository:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv tool install "git+https://github.com/data-burst/databay-clients#subdirectory=python"
|
|
35
|
+
databay login --server https://<host>
|
|
36
|
+
databay skill install --target claude
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Install, sign in and releases: [docs/cli.md](https://github.com/data-burst/databay-clients/blob/main/docs/cli.md).
|
|
40
|
+
Changes: [CHANGELOG.md](https://github.com/data-burst/databay-clients/blob/main/python/CHANGELOG.md).
|
|
41
|
+
|
|
42
|
+
Licensed under Apache-2.0: see the `LICENSE` file in the package.
|