Source code for operators.iceberg.base_iceberg_table
from airflow.models import BaseOperator
from airflow.utils.decorators import apply_defaults
from airflow.exceptions import AirflowException
from config import Config
from typing import Dict, Any
[docs]
class BaseIcebergTableOperator(BaseOperator):
"""
Base operator for working with Iceberg tables in AWS Athena.
This abstract class provides common functionality for all Iceberg table operators
including connection handling, validation, and utility methods.
:param database: The database containing the Iceberg table
:type database: str
:param table_name: The name of the Iceberg table to operate on
:type table_name: str
:param verify_exists: Whether to verify the table exists before operations
:type verify_exists: bool
"""
template_fields = ["database", "table_name"]
ui_color = "#E8DAEF" # Light Purple base color for all Iceberg operators
@apply_defaults
def __init__(
self,
table_name: str,
database: str = None,
verify_exists: bool = True,
*args,
**kwargs,
):
super().__init__(*args, **kwargs)
self.table_name = table_name
self.database = database or Config.DATA_LAKE_DB
self.verify_exists = verify_exists
[docs]
def pre_execute(self, context: Dict) -> None:
"""
Hook called before execution. https://airflow.apache.org/docs/apache-airflow/1.10.12/_api/airflow/operators/index.html#airflow.operators.BaseOperator.pre_execute
Verifies table exists if verify_exists is True.
"""
if self.verify_exists:
self._verify_table_exists()
def _verify_table_exists(self) -> bool:
"""
Verify the specified Iceberg table exists.
:return: True if table exists, raises exception if not
:rtype: bool
:raises: AirflowException if table does not exist
"""
import awswrangler as wr
if not wr.catalog.does_table_exist(
database=self.database,
table=self.table_name,
):
raise AirflowException(
f"Table {self.database}.{self.table_name} does not exist"
)
return True
def _execute_athena_query(self, sql: str, wait: bool = True) -> Dict[str, Any]:
"""
Execute a query in Athena with proper error handling.
:param sql: SQL query to execute
:type sql: str
:param wait: Whether to wait for query completion
:type wait: bool
:return: Query execution details
:rtype: Dict[str, Any]
"""
import awswrangler as wr
try:
self.log.info(f"Executing query: {sql}")
response = wr.athena.start_query_execution(
sql=sql,
database=self.database,
wait=wait,
)
self.log.info(f"Query execution ID: {response['QueryExecutionId']}")
except Exception as e:
self.log.error(f"Failed to execute Athena query: {sql}")
self.log.error(f"Error details: {str(e)}")
raise AirflowException(f"Athena query execution failed: {str(e)}") from e
def _get_table_statistics(self) -> Dict[str, Any]:
"""
Get statistics for the Iceberg table.
:return: Dictionary containing file count and size statistics
:rtype: Dict[str, Any]
"""
import awswrangler as wr
import pandas as pd
try:
file_stats = wr.athena.read_sql_query(
f"SELECT count(*) as file_count, "
f"sum(file_size_in_bytes)/1024/1024 as total_size_mb "
f'FROM "{self.database}"."{self.table_name}$files"',
database=self.database,
)
# Handle case where there are no files
if file_stats.empty or pd.isna(file_stats["total_size_mb"].iloc[0]):
return {"file_count": 0, "total_size_mb": 0}
return file_stats.iloc[0].to_dict()
except Exception as e:
self.log.warning(f"Failed to get table statistics: {str(e)}")
return {"file_count": "unknown", "total_size_mb": "unknown"}