Source code for pain001.parquet.load_parquet_data

# Copyright (C) 2023-2026 Pain001. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
# Licensed under either of the Apache License, Version 2.0 or the MIT
# License, at your option. You may not use this file except in
# compliance with one of those licences. Copies are provided in
# LICENSE-APACHE and LICENSE-MIT.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Licences is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the applicable Licence for the specific language
# governing permissions and limitations.

"""Parquet data loader for payment data (optional feature)."""

# pylint: disable=duplicate-code
import os
from collections.abc import Generator
from typing import Any, cast

from pain001.exceptions import DataSourceError
from pain001.security import validate_path

# Optional import: pyarrow is not a required dependency
try:
    import pyarrow.parquet as pq  # type: ignore[import-untyped,import-not-found,unused-ignore]

    HAS_PARQUET_SUPPORT = True
except ImportError:  # pragma: no cover - pyarrow is an optional extra
    HAS_PARQUET_SUPPORT = False


def _check_parquet_support() -> None:
    """Check if pyarrow is installed for Parquet support.

    Raises:
        DataSourceError: If pyarrow is not installed.
    """
    if not HAS_PARQUET_SUPPORT:
        raise DataSourceError(
            "Parquet support requires pyarrow. Install with: pip install pyarrow"
        )


[docs] def load_parquet_data(file_path: str) -> list[dict[str, Any]]: """Load payment data from Parquet file. Parquet is a columnar storage format optimized for analytics workloads. It provides excellent compression and fast read performance for large datasets. Args: file_path: Path to Parquet file containing payment data. Returns: List of payment data dictionaries. Raises: FileNotFoundError: If file doesn't exist. DataSourceError: If pyarrow is not installed or the file is invalid or empty. Exception: A FileNotFoundError or DataSourceError raised inside the read block is re-raised unchanged via a bare ``raise``. Examples: >>> data = load_parquet_data('payments.parquet') # [{'id': 'MSG001', 'amount': '1000.00', ...}, ...] Note: Requires pyarrow: pip install pyarrow """ _check_parquet_support() # Validate path to prevent traversal attacks try: safe_path = validate_path(file_path) # nosec B108 - Returns sanitized string except Exception as e: raise FileNotFoundError( f"Parquet file path validation failed: {file_path}" ) from e # Check file existence using os.path for string path if not os.path.isfile(safe_path): raise FileNotFoundError(f"Parquet file not found: {file_path}") try: # Read Parquet file (now safe after validation) table = pq.read_table(str(safe_path)) # nosec B108 # Convert to list of dicts data = cast(list[dict[str, Any]], table.to_pylist()) if not data: raise DataSourceError(f"Parquet file is empty: {file_path}") return data except Exception as e: if isinstance(e, (FileNotFoundError, DataSourceError)): raise raise DataSourceError( f"Error reading Parquet file {file_path}: {e}" ) from e
[docs] def load_parquet_data_streaming( file_path: str, chunk_size: int = 1000 ) -> Generator[list[dict[str, Any]], None, None]: """Load Parquet data in chunks for memory efficiency. Uses pyarrow's batch reader for true streaming without loading the entire file into memory. Args: file_path: Path to Parquet file. chunk_size: Number of records per chunk (default: 1000). Yields: list[dict[str, Any]]: Chunks of payment data dictionaries. Raises: FileNotFoundError: If file doesn't exist. DataSourceError: If pyarrow is not installed or the file is invalid. Exception: A FileNotFoundError or DataSourceError raised inside the read block is re-raised unchanged via a bare ``raise``. Examples: >>> for chunk in load_parquet_data_streaming('large_payments.parquet'): ... process_batch(chunk) Note: Requires pyarrow: pip install pyarrow """ _check_parquet_support() # Validate path to prevent traversal attacks try: safe_path = validate_path(file_path) # nosec B108 except Exception as e: raise FileNotFoundError( f"Parquet file path validation failed: {file_path}" ) from e if not os.path.isfile(safe_path): raise FileNotFoundError(f"Parquet file not found: {file_path}") try: # Open Parquet file for streaming parquet_file = pq.ParquetFile(str(safe_path)) # Read in batches for batch in parquet_file.iter_batches(batch_size=chunk_size): # Convert batch to list of dicts chunk_data = cast(list[dict[str, Any]], batch.to_pylist()) if ( chunk_data ): # pragma: no cover - pyarrow never yields an empty batch yield chunk_data except Exception as e: if isinstance( e, (FileNotFoundError, DataSourceError) ): # pragma: no cover - only pyarrow could raise these here raise raise DataSourceError( f"Error reading Parquet file {file_path}: {e}" ) from e