PyAthena Documentation

repository·master·Indexed 19 days ago

https://github.com/pyathena-dev/pyathena

A Python DB API 2.0 (PEP 249) compliant client for Amazon Athena. PyAthena enables executing SQL queries from Python applications and supports both synchronous connections via connect() and native asyncio via aio_connect(). It provides specialized cursors for high-performance data retrieval, including integrations with Pandas, Apache Arrow, Polars, and Spark, as well as a lightweight AioS3FSCursor for S3 operations.

Tokens
45.3K
Snippets
119
Records
171
Agent score
66%

What's inside PyAthena

  1. Overview of PyAthena features

    master

    PyAthena is a Python client for Amazon Athena that provides several key capabilities:

    Core Capabilities

    • DB API 2.0 Compliance: Full PEP 249 compatibility.
    • SQLAlchemy Integration: Native dialect support including table reflection and ORM capabilities.
    • Multiple Cursor Types: Support for Standard, Pandas, Arrow, Polars, S3FS, and Spark cursors.
    • Async Support: Asynchronous query execution.

    Data Type Support

    PyAthena handles complex Amazon Athena data types by converting them to native Python structures:

    • STRUCT/ROW: Support for complex nested data structures.
    • ARRAY: Ordered collections converted to Python lists.
    • MAP: Key-value structures converted to Python dictionaries.
    • JSON: Seamless parsing and conversion.

    Additional Capabilities

    • Connection Management: Efficient pooling and configuration.
    • Result Caching: Ability to reuse Athena query results.
    • S3 Integration: Direct S3 data access and staging support.
  2. Compare AsyncCursor and AioCursor

    master

    PyAthena provides two families of async cursors depending on your concurrency needs:

    • AsyncCursor: Best for adding concurrency to synchronous code. It uses concurrent.futures.ThreadPoolExecutor and blocks one thread per query. execute() returns a (query_id, Future) tuple.
    • AioCursor: Best for asynchronous frameworks (e.g., FastAPI, aiohttp). It uses native asyncio (await / async for) and is non-blocking. execute() returns an awaitable cursor.
    FeatureAsyncCursorAioCursor
    Concurrency modelThreadPoolExecutorNative asyncio
    Event loopBlocks a thread per queryNon-blocking
    Connectionconnect() (sync)aio_connect() (async)
    Iterationfor row in result_setasync for row in cursor
  3. Use ArrowCursor for high-performance data retrieval

    master

    The ArrowCursor is a specialized cursor designed for high-performance data retrieval. It works by downloading the CSV file produced by an Athena query execution to S3 and loading it directly into a pyarrow.Table object. This method is significantly faster than using the standard Cursor for large datasets.

    You can instantiate ArrowCursor in three ways:

    1. By passing cursor_class=ArrowCursor to the connect() function or Connection object.
    2. By passing ArrowCursor as an argument to the .cursor() method.
    3. By passing ArrowCursor and a custom converter to the .cursor() method.
    from pyathena import connect
    from pyathena.arrow.cursor import ArrowCursor
    
    # Option 1: Via connect()
    cursor = connect(s3_staging_dir="s3://YOUR_S3_BUCKET/path/to/",
                     region_name="us-west-2",
                     cursor_class=ArrowCursor).cursor()
    
    # Option 2: Via .cursor()
    cursor = connect(s3_staging_dir="s3://YOUR_S3_BUCKET/path/to/",
                     region_name="us-west-2").cursor(ArrowCursor)
  4. How PyAthena's S3FileSystem works with fsspec

    master

    PyAthena provides an S3FileSystem implementation that is fsspec-compatible and built on boto3. It is designed to be a drop-in replacement for s3fs.

    Automatic Registration: When you import pyathena.pandas or pyathena.polars, PyAthena automatically registers S3FileSystem as the s3 and s3a protocols via fsspec. This means calling fsspec.filesystem("s3") will return PyAthena's implementation instead of the default s3fs.

    Manual Overriding: If you need to restore s3fs after PyAthena has registered its own implementation, you can explicitly re-register it using fsspec.register_implementation.

    import fsspec
    import s3fs
    
    import pyathena.pandas  # Registers PyAthena's S3FileSystem.
    
    # Restore s3fs if needed:
    fsspec.register_implementation("s3", s3fs.S3FileSystem, clobber=True)
  5. Manage Athena query execution models

    master
    PyAthena provides classes to model different types of Athena execution and calculation states. Use AthenaQueryExecution for standard query operations and AthenaCalculationExecution (along with AthenaCalculationExecutionStatus) to manage and track the status of Athena calculations.
  6. Understand Spark base classes and execution patterns

    master

    The Spark integration is built upon several base classes that define how Spark operations are executed and how calculations are handled:

    • SparkBaseCursor: The foundational base class for Spark-specific cursors.
    • WithCalculationExecution: A mixin or base class used to manage the execution lifecycle of Spark calculations.
  7. Compare PyAthena Cursor Types

    master

    PyAthena provides different cursor implementations that perform differently depending on the result set size. Based on the 2018-09-15 benchmarks:

    • PyAthena Cursor: The standard cursor implementation.
    • PyAthena PandasCursor: Optimized for performance, especially with large and medium result sets. It significantly outperforms the standard cursor when handling large volumes of data.
    • PyAthenaJDBC Cursor: A cursor implementation that utilizes JDBC (tested in this benchmark environment).
  8. Handle PyAthena exceptions using the exception hierarchy

    master
    PyAthena uses a structured exception hierarchy for error handling. All PyAthena-specific errors derive from pyathena.error.Error. When writing robust code, you should catch specific exceptions based on the nature of the failure (e.g., InterfaceError for connection issues, DataError for invalid data, or ProgrammingError for SQL syntax errors) rather than catching the generic Error class whenever possible.
  9. Process large datasets in chunks with PandasCursor

    master

    To avoid memory exhaustion when loading large datasets, use the chunksize option. When chunksize is specified, .as_pandas() returns a PandasDataFrameIterator instead of a single DataFrame. This iterator behaves similarly to pandas' TextFileReader.

    Chunking Methods:

    • iter_chunks(): A convenient method to iterate over chunks of the dataset. Memory can be freed manually after processing each chunk.
    • get_chunk(n): Retrieves the next n rows as a DataFrame. Raises StopIteration when no more rows are available.
    • as_pandas() on the iterator: Collects all chunks into a single large DataFrame (equivalent to pandas.concat).
    from pyathena import connect
    from pyathena.pandas.cursor import PandasCursor
    
    cursor = connect(s3_staging_dir="s3://...", region_name="us-west-2", cursor_class=PandasCursor).cursor()
    
    # Method 1: Using iter_chunks()
    cursor.execute("SELECT * FROM large_table", chunksize=50_000)
    for chunk in cursor.iter_chunks():
        # Process chunk
        del chunk  # Free memory
    
    # Method 2: Using get_chunk()
    df_iter = cursor.execute("SELECT * FROM table LIMIT 15", chunksize=1_000_000).as_pandas()
    chunk = df_iter.get_chunk(10)
  10. Customize Polars DataFrame dtypes with a Converter

    master

    You can control how Athena types are mapped to Polars types by subclassing pyathena.converter.Converter and providing a mappings dictionary. This is useful for ensuring specific precision or type handling.

    Note: If the unload option is enabled, the conversion uses the schema from the Parquet file itself, and the types setting in your custom converter will be ignored.

    import polars as pl
    from pyathena.converter import Converter
    
    class CustomPolarsTypeConverter(Converter):
        def __init__(self):
            super().__init__(
                mappings=None,
                types={
                    "integer": pl.Int32,
                    "bigint": pl.Int64,
                    "float": pl.Float32,
                    "double": pl.Float64,
                    # ... other mappings
                }
            )
    
        def convert(self, type_, value):
            pass # Not used in PolarsCursor
    
    # Usage
    cursor = connect(s3_staging_dir="s3://...",
                     region_name="us-west-2",
                     converter=CustomPolarsTypeConverter()).cursor(PolarsCursor)