data-factory-utils 2.0.0__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {data_factory_utils-2.0.0 → data_factory_utils-2.2.0}/PKG-INFO +1 -1
- {data_factory_utils-2.0.0 → data_factory_utils-2.2.0}/pyproject.toml +1 -1
- {data_factory_utils-2.0.0 → data_factory_utils-2.2.0}/src/data_factory_utils/athena.py +14 -8
- {data_factory_utils-2.0.0 → data_factory_utils-2.2.0}/src/data_factory_utils/query.py +5 -1
- {data_factory_utils-2.0.0 → data_factory_utils-2.2.0}/README.md +0 -0
- {data_factory_utils-2.0.0 → data_factory_utils-2.2.0}/src/data_factory_utils/__init__.py +0 -0
- {data_factory_utils-2.0.0 → data_factory_utils-2.2.0}/src/data_factory_utils/environment.py +0 -0
|
@@ -8,7 +8,11 @@ from collections.abc import Generator
|
|
|
8
8
|
import boto3
|
|
9
9
|
import polars as pl
|
|
10
10
|
from mypy_boto3_athena.client import AthenaClient
|
|
11
|
-
from mypy_boto3_athena.type_defs import
|
|
11
|
+
from mypy_boto3_athena.type_defs import (
|
|
12
|
+
GetQueryResultsOutputTypeDef,
|
|
13
|
+
QueryExecutionContextTypeDef,
|
|
14
|
+
QueryExecutionStatisticsTypeDef,
|
|
15
|
+
)
|
|
12
16
|
|
|
13
17
|
from data_factory_utils.environment import Environment
|
|
14
18
|
from data_factory_utils.query import Query
|
|
@@ -76,10 +80,12 @@ class AthenaQuery:
|
|
|
76
80
|
self.config = config
|
|
77
81
|
self.has_succeeded = False
|
|
78
82
|
|
|
79
|
-
def run_and_await(self) -> None:
|
|
83
|
+
def run_and_await(self, *, stats: bool = False) -> QueryExecutionStatisticsTypeDef | None:
|
|
80
84
|
"""Run query, wait for it to complete."""
|
|
81
85
|
self.execution_id = self.start_execution()
|
|
82
|
-
|
|
86
|
+
if stats:
|
|
87
|
+
return self.await_query_completion(stats=True)
|
|
88
|
+
return self.await_query_completion()
|
|
83
89
|
|
|
84
90
|
def start_execution(self) -> str:
|
|
85
91
|
"""Begin an Athena query with standard settings. Return the query ID."""
|
|
@@ -117,10 +123,8 @@ class AthenaQuery:
|
|
|
117
123
|
self.config.athena_client.stop_query_execution(QueryExecutionId=self.execution_id)
|
|
118
124
|
|
|
119
125
|
def await_query_completion(
|
|
120
|
-
self,
|
|
121
|
-
|
|
122
|
-
max_interval_seconds: int = 120,
|
|
123
|
-
) -> None:
|
|
126
|
+
self, max_iterations: int = 20, max_interval_seconds: int = 120, *, stats: bool = False
|
|
127
|
+
) -> QueryExecutionStatisticsTypeDef | None:
|
|
124
128
|
"""Wait for a managed query with the given ID to finish and return the result."""
|
|
125
129
|
if self.execution_id is None:
|
|
126
130
|
msg = "No query id available. Make sure that the query has been startedbefore querying the result."
|
|
@@ -145,7 +149,9 @@ class AthenaQuery:
|
|
|
145
149
|
case "SUCCEEDED":
|
|
146
150
|
self.has_succeeded = True
|
|
147
151
|
logger.info("Query succeeded")
|
|
148
|
-
|
|
152
|
+
if stats:
|
|
153
|
+
return response["QueryExecution"]["Statistics"]
|
|
154
|
+
return None
|
|
149
155
|
case "FAILED" | "CANCELLED":
|
|
150
156
|
msg = f"Query execution failed: {execution_status}"
|
|
151
157
|
raise AthenaQueryError(msg)
|
|
@@ -17,6 +17,7 @@ DISALLOWED_CHARACTERS = re.compile(r"[;\n\r]")
|
|
|
17
17
|
MAX_LIST_LENGTH = 1000
|
|
18
18
|
S3_PATH_FORMAT = re.compile(r"^s3://[a-z0-9.-]{3,63}/.+$")
|
|
19
19
|
PERMITTED_IDENTIFIERS = ("NULL", "True", "False")
|
|
20
|
+
PERMITTED_TYPE_LITERALS = ("TIMESTAMP",)
|
|
20
21
|
|
|
21
22
|
Column = str
|
|
22
23
|
OrderByClause = tuple[Column, Literal["ASC", "DESC"]]
|
|
@@ -229,7 +230,7 @@ def validate_qualified_names(full_name: str) -> str:
|
|
|
229
230
|
|
|
230
231
|
def validate_expression(expression: str) -> str:
|
|
231
232
|
"""Ensure that an expression is valid."""
|
|
232
|
-
if expression.startswith("COALESCE"):
|
|
233
|
+
if expression.upper().startswith(("COALESCE", "CAST")):
|
|
233
234
|
return expression
|
|
234
235
|
|
|
235
236
|
match = EXPRESSION_IDENTIFIER.fullmatch(expression)
|
|
@@ -265,6 +266,9 @@ def validate_identifier(identifier: str | Subquery | datetime) -> str | Subquery
|
|
|
265
266
|
if identifier in PERMITTED_IDENTIFIERS:
|
|
266
267
|
return identifier
|
|
267
268
|
|
|
269
|
+
if isinstance(identifier, str) and identifier.upper().startswith(PERMITTED_TYPE_LITERALS):
|
|
270
|
+
return identifier
|
|
271
|
+
|
|
268
272
|
try:
|
|
269
273
|
# allow numeric values
|
|
270
274
|
float(identifier)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|