frontier-runner 0.1.1__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.
- frontier/__init__.py +3 -0
- frontier/__main__.py +4 -0
- frontier/adapters/__init__.py +8 -0
- frontier/adapters/base.py +77 -0
- frontier/adapters/bigquery.py +118 -0
- frontier/adapters/databricks.py +90 -0
- frontier/adapters/postgres.py +93 -0
- frontier/adapters/redshift.py +89 -0
- frontier/adapters/snowflake.py +276 -0
- frontier/api.py +186 -0
- frontier/artifacts.py +49 -0
- frontier/cdc/__init__.py +1 -0
- frontier/cdc/config.py +197 -0
- frontier/cdc/consume.py +171 -0
- frontier/cdc/normalize.py +218 -0
- frontier/cdc/prove.py +586 -0
- frontier/cdc/route.py +96 -0
- frontier/cdc/store.py +881 -0
- frontier/cdc/streams.py +25 -0
- frontier/cdc/upload.py +416 -0
- frontier/cli.py +2024 -0
- frontier/comment.py +354 -0
- frontier/compare.py +498 -0
- frontier/config.py +305 -0
- frontier/credentials.py +239 -0
- frontier/dbt_artifacts.py +345 -0
- frontier/errors.py +43 -0
- frontier/execute.py +848 -0
- frontier/frontier.py +586 -0
- frontier/github.py +59 -0
- frontier/hashing.py +90 -0
- frontier/impact.py +999 -0
- frontier/local_config.py +78 -0
- frontier/onboard/__init__.py +1 -0
- frontier/onboard/commands.py +593 -0
- frontier/onboard/constants.py +20 -0
- frontier/onboard/demo.py +38 -0
- frontier/onboard/detect.py +130 -0
- frontier/onboard/discover.py +184 -0
- frontier/onboard/doctor.py +346 -0
- frontier/onboard/github.py +273 -0
- frontier/onboard/gitignore.py +21 -0
- frontier/onboard/hashkey.py +17 -0
- frontier/onboard/permissions.py +63 -0
- frontier/onboard/prompt.py +70 -0
- frontier/onboard/saas.py +226 -0
- frontier/onboard/versions.py +20 -0
- frontier/progress.py +68 -0
- frontier/proof.py +794 -0
- frontier/semantic.py +583 -0
- frontier/snowflake.py +24 -0
- frontier/snowflake_sql.py +442 -0
- frontier/sql_fingerprint.py +116 -0
- frontier/validation.py +211 -0
- frontier/warehouse.py +390 -0
- frontier_runner-0.1.1.dist-info/METADATA +214 -0
- frontier_runner-0.1.1.dist-info/RECORD +61 -0
- frontier_runner-0.1.1.dist-info/WHEEL +5 -0
- frontier_runner-0.1.1.dist-info/entry_points.txt +2 -0
- frontier_runner-0.1.1.dist-info/licenses/LICENSE +21 -0
- frontier_runner-0.1.1.dist-info/top_level.txt +1 -0
frontier/__init__.py
ADDED
frontier/__main__.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from frontier.config import ConfigError
|
|
6
|
+
from frontier.warehouse import (
|
|
7
|
+
sql_string,
|
|
8
|
+
split_relation_parts,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CursorAdapter:
|
|
13
|
+
"""Shared execute/close for DB-API style connections."""
|
|
14
|
+
|
|
15
|
+
warehouse_type: str
|
|
16
|
+
dialect: str
|
|
17
|
+
_connection: Any = None
|
|
18
|
+
last_query_id: str | None = None
|
|
19
|
+
|
|
20
|
+
def execute(self, sql: str) -> list[tuple[Any, ...]]:
|
|
21
|
+
connection = self._require_connection()
|
|
22
|
+
cursor = connection.cursor()
|
|
23
|
+
try:
|
|
24
|
+
cursor.execute(sql)
|
|
25
|
+
self.last_query_id = getattr(cursor, "sfqid", None) or getattr(cursor, "sfqId", None)
|
|
26
|
+
if self.last_query_id is not None:
|
|
27
|
+
self.last_query_id = str(self.last_query_id)
|
|
28
|
+
if cursor.description is None:
|
|
29
|
+
return []
|
|
30
|
+
rows = cursor.fetchall() or []
|
|
31
|
+
return [tuple(row) for row in rows]
|
|
32
|
+
finally:
|
|
33
|
+
cursor.close()
|
|
34
|
+
|
|
35
|
+
def get_query_profile(self, query_id: str) -> dict[str, Any]:
|
|
36
|
+
del query_id
|
|
37
|
+
return {}
|
|
38
|
+
|
|
39
|
+
def close(self) -> None:
|
|
40
|
+
connection = self._connection
|
|
41
|
+
if connection is not None:
|
|
42
|
+
connection.close()
|
|
43
|
+
self._connection = None
|
|
44
|
+
|
|
45
|
+
def _require_connection(self) -> Any:
|
|
46
|
+
if self._connection is None:
|
|
47
|
+
raise ConfigError(f"{self.warehouse_type} adapter is not connected")
|
|
48
|
+
return self._connection
|
|
49
|
+
|
|
50
|
+
def _table_lookup_sql(self, relation: str) -> str:
|
|
51
|
+
catalog, schema, table = split_relation_parts(relation)
|
|
52
|
+
sql = (
|
|
53
|
+
"select 1 as present from information_schema.tables "
|
|
54
|
+
f"where lower(table_name) = lower({sql_string(table)})"
|
|
55
|
+
)
|
|
56
|
+
if schema:
|
|
57
|
+
sql += f" and lower(table_schema) = lower({sql_string(schema)})"
|
|
58
|
+
if catalog:
|
|
59
|
+
sql += f" and lower(table_catalog) = lower({sql_string(catalog)})"
|
|
60
|
+
return sql + " limit 1"
|
|
61
|
+
|
|
62
|
+
def relation_exists(self, relation: str) -> bool:
|
|
63
|
+
return bool(self.execute(self._table_lookup_sql(relation)))
|
|
64
|
+
|
|
65
|
+
def estimate_query_cost(self, sql: str) -> dict[str, Any]:
|
|
66
|
+
try:
|
|
67
|
+
rows = self.execute(f"explain {sql}")
|
|
68
|
+
except Exception:
|
|
69
|
+
return {"estimated": False, "warehouse_type": self.warehouse_type}
|
|
70
|
+
return {
|
|
71
|
+
"estimated": True,
|
|
72
|
+
"warehouse_type": self.warehouse_type,
|
|
73
|
+
"plan_rows": len(rows),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
def get_query_history(self, run_id: str) -> list[dict[str, Any]]:
|
|
77
|
+
return []
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from frontier.config import ConfigError, redact
|
|
6
|
+
from frontier.warehouse import (
|
|
7
|
+
env_value,
|
|
8
|
+
quote_identifier,
|
|
9
|
+
split_relation_parts,
|
|
10
|
+
sql_string,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BigQueryAdapter:
|
|
15
|
+
warehouse_type = "bigquery"
|
|
16
|
+
dialect = "bigquery"
|
|
17
|
+
|
|
18
|
+
def __init__(self, client: Any | None = None, *, project: str | None = None, dataset: str | None = None):
|
|
19
|
+
self._client = client
|
|
20
|
+
self.project = project
|
|
21
|
+
self.dataset = dataset
|
|
22
|
+
|
|
23
|
+
def quote_identifier(self, value: str) -> str:
|
|
24
|
+
return quote_identifier(value, "`")
|
|
25
|
+
|
|
26
|
+
def execute(self, sql: str) -> list[tuple[Any, ...]]:
|
|
27
|
+
client = self._require_client()
|
|
28
|
+
rows = list(client.query(sql).result())
|
|
29
|
+
return [tuple(row.values()) for row in rows]
|
|
30
|
+
|
|
31
|
+
def relation_exists(self, relation: str) -> bool:
|
|
32
|
+
catalog, schema, table = split_relation_parts(relation)
|
|
33
|
+
project = catalog or self.project
|
|
34
|
+
dataset = schema or self.dataset
|
|
35
|
+
if not project or not dataset:
|
|
36
|
+
raise ConfigError("BigQuery relation_exists needs project and dataset")
|
|
37
|
+
sql = (
|
|
38
|
+
f"select 1 from `{project}.{dataset}.INFORMATION_SCHEMA.TABLES` "
|
|
39
|
+
f"where lower(table_name) = lower({sql_string(table)}) "
|
|
40
|
+
"limit 1"
|
|
41
|
+
)
|
|
42
|
+
return bool(self.execute(sql))
|
|
43
|
+
|
|
44
|
+
def estimate_query_cost(self, sql: str) -> dict[str, Any]:
|
|
45
|
+
client = self._require_client()
|
|
46
|
+
job_config = _dry_run_config()
|
|
47
|
+
job = client.query(sql, job_config=job_config)
|
|
48
|
+
bytes_processed = int(getattr(job, "total_bytes_processed", 0) or 0)
|
|
49
|
+
return {
|
|
50
|
+
"estimated": True,
|
|
51
|
+
"warehouse_type": self.warehouse_type,
|
|
52
|
+
"total_bytes_processed": bytes_processed,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def get_query_history(self, run_id: str) -> list[dict[str, Any]]:
|
|
56
|
+
project = self.project
|
|
57
|
+
if not project:
|
|
58
|
+
return []
|
|
59
|
+
tagged = sql_string(run_id)
|
|
60
|
+
sql = (
|
|
61
|
+
f"select job_id, state, total_bytes_processed "
|
|
62
|
+
f"from `{project}.region-us.INFORMATION_SCHEMA.JOBS_BY_PROJECT` "
|
|
63
|
+
f"where query like '%' || {tagged} || '%' "
|
|
64
|
+
"order by creation_time desc limit 50"
|
|
65
|
+
)
|
|
66
|
+
try:
|
|
67
|
+
rows = self.execute(sql)
|
|
68
|
+
except Exception:
|
|
69
|
+
return []
|
|
70
|
+
return [
|
|
71
|
+
{"query_id": row[0], "status": row[1], "bytes": row[2] if len(row) > 2 else None}
|
|
72
|
+
for row in rows
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
def close(self) -> None:
|
|
76
|
+
client = self._client
|
|
77
|
+
if client is not None and hasattr(client, "close"):
|
|
78
|
+
client.close()
|
|
79
|
+
self._client = None
|
|
80
|
+
|
|
81
|
+
def describe(self) -> dict[str, Any]:
|
|
82
|
+
return redact(
|
|
83
|
+
{
|
|
84
|
+
"warehouse_type": self.warehouse_type,
|
|
85
|
+
"project": self.project,
|
|
86
|
+
"dataset": self.dataset,
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def _require_client(self) -> Any:
|
|
91
|
+
if self._client is None:
|
|
92
|
+
raise ConfigError("bigquery adapter is not connected")
|
|
93
|
+
return self._client
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _dry_run_config() -> Any:
|
|
97
|
+
from google.cloud import bigquery
|
|
98
|
+
|
|
99
|
+
return bigquery.QueryJobConfig(dry_run=True, use_query_cache=False)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def open_bigquery_adapter(settings: dict[str, Any]) -> BigQueryAdapter:
|
|
103
|
+
try:
|
|
104
|
+
from google.cloud import bigquery
|
|
105
|
+
except ImportError as error:
|
|
106
|
+
raise ConfigError(
|
|
107
|
+
"Install frontier-runner[bigquery] to open a BigQuery session",
|
|
108
|
+
) from error
|
|
109
|
+
project = env_value("BIGQUERY_PROJECT", "GOOGLE_CLOUD_PROJECT") or settings.get("project")
|
|
110
|
+
dataset = (
|
|
111
|
+
env_value("BIGQUERY_DATASET")
|
|
112
|
+
or settings.get("dataset")
|
|
113
|
+
or settings.get("schema")
|
|
114
|
+
)
|
|
115
|
+
if not project:
|
|
116
|
+
raise ConfigError("BigQuery project is required (BIGQUERY_PROJECT or dbt profile)")
|
|
117
|
+
client = bigquery.Client(project=str(project))
|
|
118
|
+
return BigQueryAdapter(client, project=str(project), dataset=str(dataset) if dataset else None)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from frontier.adapters.base import CursorAdapter
|
|
6
|
+
from frontier.config import ConfigError, redact
|
|
7
|
+
from frontier.warehouse import env_value, quote_identifier, sql_string
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DatabricksAdapter(CursorAdapter):
|
|
11
|
+
warehouse_type = "databricks"
|
|
12
|
+
dialect = "databricks"
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
connection: Any | None = None,
|
|
17
|
+
*,
|
|
18
|
+
catalog: str | None = None,
|
|
19
|
+
schema: str | None = None,
|
|
20
|
+
host: str | None = None,
|
|
21
|
+
http_path: str | None = None,
|
|
22
|
+
):
|
|
23
|
+
self._connection = connection
|
|
24
|
+
self.catalog = catalog
|
|
25
|
+
self.schema = schema
|
|
26
|
+
self.host = host
|
|
27
|
+
self.http_path = http_path
|
|
28
|
+
|
|
29
|
+
def quote_identifier(self, value: str) -> str:
|
|
30
|
+
return quote_identifier(value, "`")
|
|
31
|
+
|
|
32
|
+
def get_query_history(self, run_id: str) -> list[dict[str, Any]]:
|
|
33
|
+
tagged = sql_string(run_id)
|
|
34
|
+
try:
|
|
35
|
+
rows = self.execute(
|
|
36
|
+
"select statement_id, status "
|
|
37
|
+
"from system.query.history "
|
|
38
|
+
f"where statement_text like '%' || {tagged} || '%' "
|
|
39
|
+
"order by start_time desc limit 50"
|
|
40
|
+
)
|
|
41
|
+
except Exception:
|
|
42
|
+
return []
|
|
43
|
+
return [{"query_id": row[0], "status": row[1]} for row in rows]
|
|
44
|
+
|
|
45
|
+
def describe(self) -> dict[str, Any]:
|
|
46
|
+
return redact(
|
|
47
|
+
{
|
|
48
|
+
"warehouse_type": self.warehouse_type,
|
|
49
|
+
"host": self.host,
|
|
50
|
+
"http_path": self.http_path,
|
|
51
|
+
"catalog": self.catalog,
|
|
52
|
+
"schema": self.schema,
|
|
53
|
+
"token": env_value("DATABRICKS_TOKEN"),
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def open_databricks_adapter(settings: dict[str, Any]) -> DatabricksAdapter:
|
|
59
|
+
try:
|
|
60
|
+
from databricks import sql as databricks_sql
|
|
61
|
+
except ImportError as error:
|
|
62
|
+
raise ConfigError(
|
|
63
|
+
"Install frontier-runner[databricks] to open a Databricks SQL session",
|
|
64
|
+
) from error
|
|
65
|
+
host = env_value("DATABRICKS_SERVER_HOSTNAME", "DATABRICKS_HOST") or settings.get(
|
|
66
|
+
"host",
|
|
67
|
+
) or settings.get("server_hostname")
|
|
68
|
+
http_path = env_value("DATABRICKS_HTTP_PATH") or settings.get("http_path")
|
|
69
|
+
token = env_value("DATABRICKS_TOKEN") or settings.get("token")
|
|
70
|
+
catalog = env_value("DATABRICKS_CATALOG") or settings.get("catalog")
|
|
71
|
+
schema = env_value("DATABRICKS_SCHEMA") or settings.get("schema")
|
|
72
|
+
if not host or not http_path or not token:
|
|
73
|
+
raise ConfigError(
|
|
74
|
+
"Databricks host, HTTP path, and token are required "
|
|
75
|
+
"(DATABRICKS_HOST / DATABRICKS_HTTP_PATH / DATABRICKS_TOKEN)",
|
|
76
|
+
)
|
|
77
|
+
connection = databricks_sql.connect(
|
|
78
|
+
server_hostname=str(host),
|
|
79
|
+
http_path=str(http_path),
|
|
80
|
+
access_token=str(token),
|
|
81
|
+
catalog=str(catalog) if catalog else None,
|
|
82
|
+
schema=str(schema) if schema else None,
|
|
83
|
+
)
|
|
84
|
+
return DatabricksAdapter(
|
|
85
|
+
connection,
|
|
86
|
+
catalog=str(catalog) if catalog else None,
|
|
87
|
+
schema=str(schema) if schema else None,
|
|
88
|
+
host=str(host),
|
|
89
|
+
http_path=str(http_path),
|
|
90
|
+
)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from frontier.adapters.base import CursorAdapter
|
|
6
|
+
from frontier.config import ConfigError, redact
|
|
7
|
+
from frontier.warehouse import env_value, quote_identifier, sql_string
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PostgresAdapter(CursorAdapter):
|
|
11
|
+
warehouse_type = "postgres"
|
|
12
|
+
dialect = "postgres"
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
connection: Any | None = None,
|
|
17
|
+
*,
|
|
18
|
+
host: str | None = None,
|
|
19
|
+
database: str | None = None,
|
|
20
|
+
schema: str | None = None,
|
|
21
|
+
user: str | None = None,
|
|
22
|
+
):
|
|
23
|
+
self._connection = connection
|
|
24
|
+
self.host = host
|
|
25
|
+
self.database = database
|
|
26
|
+
self.schema = schema
|
|
27
|
+
self.user = user
|
|
28
|
+
|
|
29
|
+
def quote_identifier(self, value: str) -> str:
|
|
30
|
+
return quote_identifier(value, '"')
|
|
31
|
+
|
|
32
|
+
def get_query_history(self, run_id: str) -> list[dict[str, Any]]:
|
|
33
|
+
tagged = sql_string(f"%{run_id}%")
|
|
34
|
+
try:
|
|
35
|
+
rows = self.execute(
|
|
36
|
+
"select queryid::text, calls "
|
|
37
|
+
"from pg_stat_statements "
|
|
38
|
+
f"where query like {tagged} "
|
|
39
|
+
"order by last_exec_at desc nulls last limit 50"
|
|
40
|
+
)
|
|
41
|
+
except Exception:
|
|
42
|
+
return []
|
|
43
|
+
return [{"query_id": row[0], "calls": row[1]} for row in rows]
|
|
44
|
+
|
|
45
|
+
def describe(self) -> dict[str, Any]:
|
|
46
|
+
return redact(
|
|
47
|
+
{
|
|
48
|
+
"warehouse_type": self.warehouse_type,
|
|
49
|
+
"host": self.host,
|
|
50
|
+
"database": self.database,
|
|
51
|
+
"schema": self.schema,
|
|
52
|
+
"user": self.user,
|
|
53
|
+
"password": env_value("FRONTIER_POSTGRES_PASSWORD", "PGPASSWORD"),
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def open_postgres_adapter(settings: dict[str, Any]) -> PostgresAdapter:
|
|
59
|
+
try:
|
|
60
|
+
import psycopg
|
|
61
|
+
except ImportError as error:
|
|
62
|
+
raise ConfigError(
|
|
63
|
+
"Install frontier-runner[postgres] to open a PostgreSQL session",
|
|
64
|
+
) from error
|
|
65
|
+
host = env_value("FRONTIER_POSTGRES_HOST", "PGHOST") or settings.get("host")
|
|
66
|
+
user = env_value("FRONTIER_POSTGRES_USER", "PGUSER") or settings.get("user")
|
|
67
|
+
database = (
|
|
68
|
+
env_value("FRONTIER_POSTGRES_DB", "PGDATABASE")
|
|
69
|
+
or settings.get("dbname")
|
|
70
|
+
or settings.get("database")
|
|
71
|
+
)
|
|
72
|
+
port = env_value("FRONTIER_POSTGRES_PORT", "PGPORT") or settings.get("port") or 5432
|
|
73
|
+
password = env_value("FRONTIER_POSTGRES_PASSWORD", "PGPASSWORD") or settings.get("password")
|
|
74
|
+
schema = env_value("FRONTIER_POSTGRES_SCHEMA") or settings.get("schema") or "public"
|
|
75
|
+
if not host or not user or not database:
|
|
76
|
+
raise ConfigError(
|
|
77
|
+
"PostgreSQL host, user, and database are required "
|
|
78
|
+
"(FRONTIER_POSTGRES_* or dbt profile)",
|
|
79
|
+
)
|
|
80
|
+
connection = psycopg.connect(
|
|
81
|
+
host=str(host),
|
|
82
|
+
user=str(user),
|
|
83
|
+
dbname=str(database),
|
|
84
|
+
port=int(port),
|
|
85
|
+
password=str(password) if password else None,
|
|
86
|
+
)
|
|
87
|
+
return PostgresAdapter(
|
|
88
|
+
connection,
|
|
89
|
+
host=str(host),
|
|
90
|
+
database=str(database),
|
|
91
|
+
schema=str(schema),
|
|
92
|
+
user=str(user),
|
|
93
|
+
)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from frontier.adapters.base import CursorAdapter
|
|
6
|
+
from frontier.config import ConfigError, redact
|
|
7
|
+
from frontier.warehouse import env_value, quote_identifier, sql_string
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RedshiftAdapter(CursorAdapter):
|
|
11
|
+
warehouse_type = "redshift"
|
|
12
|
+
dialect = "redshift"
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
connection: Any | None = None,
|
|
17
|
+
*,
|
|
18
|
+
host: str | None = None,
|
|
19
|
+
database: str | None = None,
|
|
20
|
+
schema: str | None = None,
|
|
21
|
+
user: str | None = None,
|
|
22
|
+
):
|
|
23
|
+
self._connection = connection
|
|
24
|
+
self.host = host
|
|
25
|
+
self.database = database
|
|
26
|
+
self.schema = schema
|
|
27
|
+
self.user = user
|
|
28
|
+
|
|
29
|
+
def quote_identifier(self, value: str) -> str:
|
|
30
|
+
return quote_identifier(value, '"')
|
|
31
|
+
|
|
32
|
+
def get_query_history(self, run_id: str) -> list[dict[str, Any]]:
|
|
33
|
+
tagged = sql_string(f"%{run_id}%")
|
|
34
|
+
try:
|
|
35
|
+
rows = self.execute(
|
|
36
|
+
"select query::text, status "
|
|
37
|
+
"from stl_query "
|
|
38
|
+
f"where querytxt like {tagged} "
|
|
39
|
+
"order by starttime desc limit 50"
|
|
40
|
+
)
|
|
41
|
+
except Exception:
|
|
42
|
+
return []
|
|
43
|
+
return [{"query_id": row[0], "status": row[1]} for row in rows]
|
|
44
|
+
|
|
45
|
+
def describe(self) -> dict[str, Any]:
|
|
46
|
+
return redact(
|
|
47
|
+
{
|
|
48
|
+
"warehouse_type": self.warehouse_type,
|
|
49
|
+
"host": self.host,
|
|
50
|
+
"database": self.database,
|
|
51
|
+
"schema": self.schema,
|
|
52
|
+
"user": self.user,
|
|
53
|
+
"password": env_value("REDSHIFT_PASSWORD"),
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def open_redshift_adapter(settings: dict[str, Any]) -> RedshiftAdapter:
|
|
59
|
+
try:
|
|
60
|
+
import redshift_connector
|
|
61
|
+
except ImportError as error:
|
|
62
|
+
raise ConfigError(
|
|
63
|
+
"Install frontier-runner[redshift] to open a Redshift session",
|
|
64
|
+
) from error
|
|
65
|
+
host = env_value("REDSHIFT_HOST") or settings.get("host")
|
|
66
|
+
user = env_value("REDSHIFT_USER") or settings.get("user")
|
|
67
|
+
database = env_value("REDSHIFT_DATABASE") or settings.get("dbname") or settings.get("database")
|
|
68
|
+
port = env_value("REDSHIFT_PORT") or settings.get("port") or 5439
|
|
69
|
+
password = env_value("REDSHIFT_PASSWORD") or settings.get("password")
|
|
70
|
+
schema = env_value("REDSHIFT_SCHEMA") or settings.get("schema") or "public"
|
|
71
|
+
if not host or not user or not database or not password:
|
|
72
|
+
raise ConfigError(
|
|
73
|
+
"Redshift host, user, database, and password are required "
|
|
74
|
+
"(REDSHIFT_* or dbt profile)",
|
|
75
|
+
)
|
|
76
|
+
connection = redshift_connector.connect(
|
|
77
|
+
host=str(host),
|
|
78
|
+
database=str(database),
|
|
79
|
+
user=str(user),
|
|
80
|
+
password=str(password),
|
|
81
|
+
port=int(port),
|
|
82
|
+
)
|
|
83
|
+
return RedshiftAdapter(
|
|
84
|
+
connection,
|
|
85
|
+
host=str(host),
|
|
86
|
+
database=str(database),
|
|
87
|
+
schema=str(schema),
|
|
88
|
+
user=str(user),
|
|
89
|
+
)
|