Spaces:
Sleeping
Sleeping
| """ | |
| JSON extractor for structured data files. | |
| Supports only CSV, XLS, XLSX file types for JSON extraction. | |
| Returns error for unsupported file types. | |
| """ | |
| from __future__ import annotations | |
| import io | |
| import warnings | |
| from pathlib import Path | |
| from typing import Any, Dict, Optional, Union | |
| import pandas as pd | |
| from logger import get_logger | |
| logger = get_logger(__name__) | |
| SUPPORTED_EXTENSIONS: frozenset[str] = frozenset({".csv", ".xls", ".xlsx"}) | |
| MAX_CSV_ROWS = 100_000 | |
| MAX_EXCEL_ROWS = 50_000 | |
| MAX_CELL_COUNT = 2_000_000 | |
| try: | |
| from app.core.config import settings as _app_settings | |
| MAX_FILE_SIZE_BYTES: int = _app_settings.MAX_FILE_SIZE_BYTES | |
| except Exception: | |
| MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024 | |
| def _validate_file_size(size: int) -> Optional[str]: | |
| if size > MAX_FILE_SIZE_BYTES: | |
| return f"File size {size} bytes exceeds limit of {MAX_FILE_SIZE_BYTES} bytes" | |
| return None | |
| def _check_memory_usage(rows: int, cols: int) -> Optional[str]: | |
| cell_count = rows * cols | |
| if cell_count > MAX_CELL_COUNT: | |
| approx_mb = (cell_count * 50) / (1024 * 1024) | |
| return f"Data size too large (approx {approx_mb:.1f} MB). Too many cells: {rows}x{cols}" | |
| return None | |
| def _read_dataframe(ext: str, file_path: Union[str, Path], file_data: Optional[bytes]) -> "pd.DataFrame": | |
| """Read a CSV/Excel file from either an in-memory buffer or a path.""" | |
| if ext == ".csv": | |
| if file_data: | |
| return pd.read_csv(io.BytesIO(file_data), nrows=MAX_CSV_ROWS + 1, low_memory=False) | |
| return pd.read_csv(file_path, nrows=MAX_CSV_ROWS + 1, low_memory=False) | |
| engine = "openpyxl" if ext == ".xlsx" else "xlrd" | |
| if file_data: | |
| return pd.read_excel(io.BytesIO(file_data), engine=engine) | |
| return pd.read_excel(file_path, engine=engine) | |
| def extract_json_from_file( | |
| file_path: Union[str, Path], | |
| file_data: Optional[bytes] = None, | |
| ) -> Dict[str, Any]: | |
| """ | |
| Extract JSON data from structured files (CSV, XLS, XLSX). | |
| Parameters | |
| ---------- | |
| file_path : Union[str, Path] | |
| Path to the file or filename with extension | |
| file_data : Optional[bytes] | |
| Raw file data (for stream processing) | |
| Returns | |
| ------- | |
| Dict[str, Any] | |
| Extracted data or error information | |
| """ | |
| ext = Path(file_path).suffix.lower() | |
| if ext not in SUPPORTED_EXTENSIONS: | |
| return { | |
| "error": f"Unsupported file type: {ext}. Supported: {', '.join(sorted(SUPPORTED_EXTENSIONS))}", | |
| "file_type": ext, | |
| } | |
| # Validate file size | |
| if file_data is not None: | |
| source_bytes = len(file_data) | |
| else: | |
| path = Path(file_path) | |
| source_bytes = path.stat().st_size if path.exists() else 0 | |
| size_error = _validate_file_size(source_bytes) | |
| if size_error: | |
| return {"error": size_error, "file_type": ext} | |
| try: | |
| with warnings.catch_warnings(): | |
| warnings.simplefilter("ignore", UserWarning) | |
| df = _read_dataframe(ext, file_path, file_data) | |
| except pd.errors.EmptyDataError: | |
| return {"error": "File is empty or has no data", "file_type": ext} | |
| except MemoryError: | |
| return {"error": "Out of memory processing file", "file_type": ext} | |
| except Exception as exc: | |
| logger.exception("JSON extraction failed for %s", ext) | |
| return { | |
| "error": f"Processing failed: {exc}", | |
| "file_type": ext, | |
| "exception_type": type(exc).__name__, | |
| } | |
| max_rows = MAX_EXCEL_ROWS if ext != ".csv" else MAX_CSV_ROWS | |
| if len(df) > max_rows: | |
| return { | |
| "error": f"File contains {len(df)} rows, exceeds limit of {max_rows}", | |
| "file_type": ext, | |
| "row_count": len(df), | |
| } | |
| mem_error = _check_memory_usage(len(df), len(df.columns)) | |
| if mem_error: | |
| return {"error": mem_error, "file_type": ext} | |
| logger.info("Extracted JSON from %s: %d rows, %d cols", ext, len(df), len(df.columns)) | |
| return { | |
| "success": True, | |
| "file_type": ext, | |
| "data": { | |
| "columns": list(df.columns), | |
| "rows": df.where(pd.notnull(df), None).to_dict(orient="records"), | |
| "shape": [len(df), len(df.columns)], | |
| "dtypes": {col: str(dtype) for col, dtype in df.dtypes.items()}, | |
| }, | |
| } | |
| def is_supported_file_type(file_path: Union[str, Path]) -> bool: | |
| """Check if file type is supported for JSON extraction.""" | |
| return Path(file_path).suffix.lower() in SUPPORTED_EXTENSIONS | |
| def get_supported_extensions() -> list[str]: | |
| """Get sorted list of supported file extensions for JSON extraction.""" | |
| return sorted(SUPPORTED_EXTENSIONS) | |