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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: data-factory-utils
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Summary: Utility functions for interacting with data factories.
5
5
  Requires-Dist: boto3>=1.42.8
6
6
  Requires-Dist: boto3-stubs>=1.42.89
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "data-factory-utils"
3
- version = "2.0.0"
3
+ version = "2.2.0"
4
4
  description = "Utility functions for interacting with data factories."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -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 GetQueryResultsOutputTypeDef, QueryExecutionContextTypeDef
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
- self.await_query_completion()
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
- max_iterations: int = 20,
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
- return
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)