Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 118 additions & 8 deletions excel_to_sql/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,19 @@
from rich.console import Console
from rich.table import Table
import pandas as pd
import logging

from excel_to_sql.entities.project import Project
from excel_to_sql.entities.excel_file import ExcelFile
from excel_to_sql.entities.dataframe import DataFrame
from excel_to_sql.__version__ import __version__
from excel_to_sql.exceptions import (
ExcelToSqlError,
ExcelFileError,
ConfigurationError,
ValidationError,
DatabaseError,
)

app = Typer(
name="excel-to-sql",
Expand All @@ -21,6 +29,7 @@
)

console = Console()
logger = logging.getLogger(__name__)


# ──────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -187,21 +196,57 @@ def import_cmd(

except FileNotFoundError:
console.print(f"[red]Error:[/red] File not found: {excel_path}")
console.print("[dim]Tip: Check the file path and try again[/dim]")
raise Exit(1)

except ValueError as e:
console.print(f"[red]Error:[/red] {e}")
except PermissionError:
console.print(f"[red]Error:[/red] Permission denied: {excel_path}")
console.print("[dim]Tip: Check file permissions or run with appropriate access[/dim]")
raise Exit(1)

except pd.errors.EmptyDataError:
console.print(f"[red]Error:[/red] Excel file is empty: {excel_path}")
console.print("[dim]Tip: Ensure the file contains data in the first sheet[/dim]")
raise Exit(1)

except pd.errors.ParserError as e:
console.print(f"[red]Error:[/red] Invalid Excel file format: {excel_path}")
console.print(f"[dim]Details: {e}[/dim]")
console.print("[dim]Tip: Ensure the file is a valid .xlsx or .xls file[/dim]")
raise Exit(1)

except ConfigurationError as e:
console.print(f"[red]Error:[/red] Configuration error: {e.message}")
if e.context:
console.print(f"[dim]Context: {e.context}[/dim]")
console.print("[dim]Tip: Check your configuration files or run 'excel-to-sql init'[/dim]")
raise Exit(1)

except ValidationError as e:
console.print(f"[red]Error:[/red] Validation error: {e.message}")
if e.context:
console.print(f"[dim]Details: {e.context}[/dim]")
raise Exit(1)

except DatabaseError as e:
console.print(f"[red]Error:[/red] Database error: {e.message}")
if e.context:
console.print(f"[dim]Context: {e.context}[/dim]")
raise Exit(1)

except Exit:
# Re-raise Exit exceptions (they're intentional)
raise

except Exception as e:
console.print(f"[red]Error:[/red] Import failed")
console.print(f" {e}")
# Log unexpected errors
logger.exception(f"Unexpected error importing {excel_path}")
console.print("[red]Error:[/red] An unexpected error occurred during import")
console.print(f"[dim]Details: {e}[/dim]")
if "--debug" in sys.argv:
console.print(traceback.format_exc())
else:
console.print("[dim]Use --debug for more information[/dim]")
raise Exit(1)


Expand Down Expand Up @@ -292,8 +337,9 @@ def export_cmd(
try:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
except:
pass
except (AttributeError, TypeError):
# Cell value is None or has unexpected type, skip it
continue

adjusted_width = min(max_length + 2, 50) # Cap at 50
worksheet.column_dimensions[column_letter].width = adjusted_width
Expand Down Expand Up @@ -335,11 +381,32 @@ def export_cmd(

console.print(summary_table)

except FileNotFoundError:
console.print(f"[red]Error:[/red] Table not found in database")
if table:
console.print(f"[dim]Table: {table}[/dim]")
console.print("[dim]Tip: Check the table name or import data first[/dim]")
raise Exit(1)

except PermissionError:
console.print(f"[red]Error:[/red] Permission denied: {output}")
console.print("[dim]Tip: Check write permissions for the output directory[/dim]")
raise Exit(1)

except DatabaseError as e:
console.print(f"[red]Error:[/red] Database error: {e.message}")
if e.context:
console.print(f"[dim]Context: {e.context}[/dim]")
raise Exit(1)

except Exit:
raise

except Exception as e:
console.print(f"[red]Error:[/red] Export failed")
console.print(f"[dim]{e}[/dim]")
logger.exception(f"Unexpected error during export to {output}")
console.print("[red]Error:[/red] An unexpected error occurred during export")
console.print(f"[dim]Details: {e}[/dim]")
console.print("[dim]Use --debug for more information[/dim]")
raise Exit(1)


Expand All @@ -354,6 +421,10 @@ def status() -> None:
try:
# Load project
project = Project.from_current_directory()
except ConfigurationError as e:
console.print(f"[red]Error:[/red] Configuration error: {e.message}")
console.print("[dim]Tip: Run 'excel-to-sql init' to initialize[/dim]")
raise Exit(1)
except Exception:
console.print("[red]Error:[/red] Not an excel-to-sql project")
console.print("[dim]Run 'excel-to-sql init' to initialize[/dim]")
Expand Down Expand Up @@ -552,10 +623,28 @@ def magic(
"column_count": len(df.columns),
}

except FileNotFoundError:
console.print(f" [red]Error:[/red] File not found: {sheet_name}")
except PermissionError:
console.print(f" [red]Error:[/red] Permission denied: {sheet_name}")
except pd.errors.EmptyDataError:
console.print(f" [yellow]Warning:[/yellow] Empty sheet: {sheet_name}")
except pd.errors.ParserError as e:
console.print(f" [red]Error analyzing {sheet_name}:[/red] Invalid Excel format")
except ExcelFileError as e:
console.print(f" [red]Error analyzing {sheet_name}:[/red] {e.message}")
except Exception as e:
logger.warning(f"Unexpected error analyzing {sheet_name}: {e}")
console.print(f" [red]Error analyzing {sheet_name}:[/red] {e}")

except FileNotFoundError:
console.print(f"[red]Error:[/red] File not found: {excel_file.name}")
except PermissionError:
console.print(f"[red]Error:[/red] Permission denied: {excel_file.name}")
except ExcelFileError as e:
console.print(f"[red]Error processing {excel_file.name}:[/red] {e.message}")
except Exception as e:
logger.warning(f"Unexpected error processing {excel_file.name}: {e}")
console.print(f"[red]Error processing {excel_file.name}:[/red] {e}")

# Interactive mode
Expand All @@ -580,6 +669,27 @@ def magic(
df = header_detector.read_excel_with_header_detection(result["file"], result["sheet"])
quality_report = scorer.generate_quality_report(df, table_name)
quality_dict[table_name] = quality_report
except FileNotFoundError:
# Default quality report if file not found
quality_dict[table_name] = {
"score": 0,
"grade": "F",
"issues": ["File not found"]
}
except PermissionError:
# Default quality report if permission denied
quality_dict[table_name] = {
"score": 0,
"grade": "F",
"issues": ["Permission denied"]
}
except ExcelFileError:
# Default quality report if analysis fails
quality_dict[table_name] = {
"score": 50,
"grade": "C",
"issues": ["Excel file error"]
}
except Exception:
# Default quality report if analysis fails
quality_dict[table_name] = {
Expand Down
52 changes: 49 additions & 3 deletions excel_to_sql/entities/excel_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import pandas as pd
import hashlib

from excel_to_sql.exceptions import ExcelFileError


class ExcelFile:
"""
Expand Down Expand Up @@ -103,8 +105,27 @@ def read(
return pd.read_excel(self._path, sheet_name=actual_sheet, header=header_row, engine="openpyxl")

return pd.read_excel(self._path, sheet_name=actual_sheet, header=header, engine="openpyxl")
except (FileNotFoundError, PermissionError):
# Re-raise filesystem errors as-is
raise
except pd.errors.EmptyDataError as e:
raise ExcelFileError(
f"Excel file is empty: {self._path.name}",
file_path=str(self._path),
operation="read"
) from e
except pd.errors.ParserError as e:
raise ExcelFileError(
f"Invalid Excel file format: {self._path.name}",
file_path=str(self._path),
operation="read"
) from e
except Exception as e:
raise ValueError(f"Failed to read Excel file: {e}") from e
raise ExcelFileError(
f"Failed to read Excel file: {self._path.name}",
file_path=str(self._path),
operation="read"
) from e

def read_all_sheets(self) -> Dict[str, pd.DataFrame]:
"""
Expand All @@ -124,8 +145,26 @@ def read_all_sheets(self) -> Dict[str, pd.DataFrame]:

try:
return pd.read_excel(self._path, sheet_name=None, engine="openpyxl")
except (FileNotFoundError, PermissionError):
raise
except pd.errors.EmptyDataError as e:
raise ExcelFileError(
f"Excel file is empty: {self._path.name}",
file_path=str(self._path),
operation="read_all_sheets"
) from e
except pd.errors.ParserError as e:
raise ExcelFileError(
f"Invalid Excel file format: {self._path.name}",
file_path=str(self._path),
operation="read_all_sheets"
) from e
except Exception as e:
raise ValueError(f"Failed to read Excel file: {e}") from e
raise ExcelFileError(
f"Failed to read Excel file: {self._path.name}",
file_path=str(self._path),
operation="read_all_sheets"
) from e

def read_sheets(self, sheet_names: List[str]) -> Dict[str, pd.DataFrame]:
"""
Expand All @@ -146,8 +185,15 @@ def read_sheets(self, sheet_names: List[str]) -> Dict[str, pd.DataFrame]:
for sheet_name in sheet_names:
try:
result[sheet_name] = self.read(sheet_name)
except ExcelFileError:
# Re-raise ExcelFileError as-is
raise
except Exception as e:
raise ValueError(f"Failed to read sheet '{sheet_name}': {e}") from e
raise ExcelFileError(
f"Failed to read sheet: {sheet_name}",
file_path=str(self._path),
operation="read_sheets"
) from e

return result

Expand Down
Loading