From ed21ff3523152020d92c357c745efaaa2a10def5 Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Tue, 13 May 2025 21:09:38 +0800 Subject: [PATCH 1/5] Remove rich library from fastmigrate --- fastmigrate/core.py | 74 ++++++++++++++++++++++----------------------- pyproject.toml | 3 +- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/fastmigrate/core.py b/fastmigrate/core.py index 8f90a12..eb58693 100644 --- a/fastmigrate/core.py +++ b/fastmigrate/core.py @@ -11,10 +11,6 @@ from pathlib import Path from typing import Dict, Optional -from rich.console import Console - -# Initialize Rich console -console = Console() __all__ = ["run_migrations", "create_db", "get_db_version", "create_db_backup", # deprecated @@ -242,14 +238,14 @@ def execute_sql_script(db_path: Path, script_path: Path) -> bool: except sqlite3.Error as e: # SQL error occurred - console.print(f"[bold red]Error[/bold red] executing SQL script {script_path}:") - console.print(f" {e}", style="red") + sys.stderr.write(f"Error executing SQL script {script_path}:\n") + sys.stderr.write(f" {e}\n") return False except Exception as e: # Handle other errors (file not found, etc.) - console.print(f"[bold red]Error[/bold red] executing SQL script {script_path}:") - console.print(f" {e}", style="red") + sys.stderr.write(f"Error executing SQL script {script_path}:\n") + sys.stderr.write(f" {e}\n") return False finally: @@ -269,8 +265,9 @@ def execute_python_script(db_path: Path, script_path: Path) -> bool: ) return True except subprocess.CalledProcessError as e: - console.print(f"[bold red]Error[/bold red] executing Python script {script_path}:") - console.print(e.stderr.decode(), style="red") + sys.stderr.write(f"Error executing Python script {script_path}:\n") + sys.stderr.write(e.stderr.decode()) + sys.stderr.write('\n') return False @@ -286,8 +283,9 @@ def execute_shell_script(db_path: Path, script_path: Path) -> bool: ) return True except subprocess.CalledProcessError as e: - console.print(f"[bold red]Error[/bold red] executing shell script {script_path}:") - console.print(e.stderr.decode(), style="red") + sys.stderr.write(f"Error executing shell script {script_path}:\n") + sys.stderr.write(e.stderr.decode()) + sys.stderr.write('\n') return False @@ -306,7 +304,7 @@ def create_db_backup(db_path: Path) -> Path | None: db_path = Path(db_path) # Only proceed if the database exists if not db_path.exists(): - console.print(f"[yellow]Warning:[/yellow] Database file does not exist: {db_path}") + print(f"Warning: Database file does not exist: {db_path}") return None # Create a timestamped backup filename @@ -315,7 +313,7 @@ def create_db_backup(db_path: Path) -> Path | None: # Check if the backup file already exists if backup_path.exists(): - console.print(f"[red]Error:[/red] Backup file already exists: {backup_path}") + sys.stderr.write(f"Error: Backup file already exists: {backup_path}\n") return None conn = None @@ -332,17 +330,17 @@ def create_db_backup(db_path: Path) -> Path | None: if not backup_path.exists(): raise Exception("Backup file was not created") - console.print(f"[green]Database backup created:[/green] {backup_path}") + print(f"Database backup created: {backup_path}") return backup_path except Exception as e: - console.print(f"[bold red]Error during backup:[/bold red] {e}") + print(f"Error during backup: {e}") # Attempt to remove potentially incomplete backup file if backup_path.exists(): try: backup_path.unlink() # remove the file - console.print(f"[yellow]Removed incomplete backup file:[/yellow] {backup_path}") + print(f"Removed incomplete backup file: {backup_path}") except OSError as remove_err: - console.print(f"[bold red]Error removing incomplete backup file:[/bold red] {remove_err}") + sys.stderr.write(f"Error removing incomplete backup file: {remove_err}\n") return None finally: # this runs before return `backup_path` or `return None` in the try block @@ -373,7 +371,7 @@ def execute_migration_script(db_path: Path, script_path: Path) -> bool: elif ext == ".sh": return execute_shell_script(db_path, script_path) else: - console.print(f"[bold red]Unsupported script type:[/bold red] {script_path}") + sys.stderr.write(f"Unsupported script type: {script_path}\n") return False @@ -406,8 +404,8 @@ def run_migrations( # Check if database file exists if not db_path.exists(): - console.print(f"[bold red]Error:[/bold red] Database file does not exist: {db_path}") - console.print("The database file must exist before running migrations.") + sys.stderr.write(f"Error: Database file does not exist: {db_path}\n") + sys.stderr.write("The database file must exist before running migrations.\n") return False try: @@ -415,7 +413,7 @@ def run_migrations( try: create_db(db_path) except sqlite3.Error as e: - console.print(f"""[bold red]Error:[/bold red] Cannot migrate the db at {db_path}. + sys.stderr.write(f"""Error: Cannot migrate the db at {db_path}. This is because it is not managed by fastmigrate. Please do one of the following: @@ -425,7 +423,7 @@ def run_migrations( 2. Enroll your existing database, by manually verifying your existing db's data matches a version defined by your migration scripts, and then setting your db's version explicitly with -`fastmigrate.core._set_db_version()`. See enrolling.md for guidance.""") +`fastmigrate.core._set_db_version()`. See enrolling.md for guidance.\n""") return False # Get current version @@ -435,7 +433,7 @@ def run_migrations( try: migration_scripts = get_migration_scripts(migrations_dir) except ValueError as e: - console.print(f"[bold red]Error:[/bold red] {e}") + sys.stderr.write(f"Error: {e}\n") return False # Find pending migrations @@ -447,7 +445,7 @@ def run_migrations( if not pending_migrations: if verbose: - console.print(f"[green]Database is up to date[/green] (version {current_version})") + print(f"Database is up to date (version {current_version})") return True # Sort migrations by version @@ -462,7 +460,7 @@ def run_migrations( # Start timing this migration migration_start = time.time() if verbose: - console.print(f"[blue]Applying[/blue] migration [bold]{version}[/bold]: [cyan]{script_name}[/cyan]") + print(f"Applying migration {version}: {script_name}") # Each script will open its own connection @@ -470,14 +468,14 @@ def run_migrations( success = execute_migration_script(db_path, script_path) if not success: - console.print(f"[bold red]Migration failed:[/bold red] {script_path}") + sys.stderr.write(f"Migration failed: {script_path}\n") stats["failed"] += 1 # Show summary of failure - always show errors regardless of verbose flag - console.print("\n[bold red]Migration Failed[/bold red]") - console.print(f" • [bold]{stats['applied']}[/bold] migrations applied") - console.print(f" • [bold]{stats['failed']}[/bold] migrations failed") - console.print(f" • Total time: [bold]{time.time() - start_time:.2f}[/bold] seconds") + sys.stderr.write("\nMigration Failed\n") + print(f" • {stats['applied']} migrations applied") + print(f" • {stats['failed']} migrations failed") + print(f" • Total time: {time.time() - start_time:.2f} seconds") return False @@ -489,21 +487,21 @@ def run_migrations( # Update version _set_db_version(db_path, version) if verbose: - console.print(f"[green]✓[/green] Database updated to version [bold]{version}[/bold] [dim]({migration_duration:.2f}s)[/dim]") + print(f"✓ Database updated to version {version} ({migration_duration:.2f}s)") # Show summary of successful run if stats["applied"] > 0 and verbose: total_duration = time.time() - start_time - console.print("\n[bold green]Migration Complete[/bold green]") - console.print(f" • [bold]{stats['applied']}[/bold] migrations applied") - console.print(f" • Database now at version [bold]{sorted_versions[-1]}[/bold]") - console.print(f" • Total time: [bold]{total_duration:.2f}[/bold] seconds") + print("\nMigration Complete") + print(f" • {stats['applied']} migrations applied") + print(f" • Database now at version {sorted_versions[-1]}") + print(f" • Total time: {total_duration:.2f} seconds") return True except sqlite3.Error as e: - console.print(f"[bold red]Database error:[/bold red] {e}") + sys.stderr.write(f"Database error: {e}\n") return False except Exception as e: - console.print(f"[bold red]Error:[/bold red] {e}") + sys.stderr.write(f"Error: {e}\n") return False diff --git a/pyproject.toml b/pyproject.toml index df39064..3fd22fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,8 +10,7 @@ readme = "README.md" requires-python = ">=3.10" license = {text = "MIT"} dependencies = [ - "apswutils>=0.0.2", - "rich>=14.0.0", + "apswutils>=0.0.2" ] [dependency-groups] From a42505d0fe9a8c6e0c4c98fb9caa10e877b16775 Mon Sep 17 00:00:00 2001 From: Alexis Gallagher Date: Mon, 9 Jun 2025 10:50:51 -0700 Subject: [PATCH 2/5] Removes reporting of migration timing. Users can use `time` for that. --- fastmigrate/core.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/fastmigrate/core.py b/fastmigrate/core.py index eb58693..1b20d6a 100644 --- a/fastmigrate/core.py +++ b/fastmigrate/core.py @@ -5,7 +5,6 @@ import sqlite3 import subprocess import sys -import time import warnings from datetime import datetime from pathlib import Path @@ -398,8 +397,7 @@ def run_migrations( # Keep track of migration statistics stats = { "applied": 0, - "failed": 0, - "total_time": 0.0 + "failed": 0 } # Check if database file exists @@ -452,13 +450,10 @@ def run_migrations( sorted_versions = sorted(pending_migrations.keys()) # Execute migrations - start_time = time.time() for version in sorted_versions: script_path = pending_migrations[version] script_name = script_path.name - # Start timing this migration - migration_start = time.time() if verbose: print(f"Applying migration {version}: {script_name}") @@ -475,27 +470,21 @@ def run_migrations( sys.stderr.write("\nMigration Failed\n") print(f" • {stats['applied']} migrations applied") print(f" • {stats['failed']} migrations failed") - print(f" • Total time: {time.time() - start_time:.2f} seconds") return False - # Record migration duration - migration_duration = time.time() - migration_start - stats["total_time"] += migration_duration stats["applied"] += 1 # Update version _set_db_version(db_path, version) if verbose: - print(f"✓ Database updated to version {version} ({migration_duration:.2f}s)") + print(f"✓ Database updated to version {version}") # Show summary of successful run if stats["applied"] > 0 and verbose: - total_duration = time.time() - start_time print("\nMigration Complete") print(f" • {stats['applied']} migrations applied") print(f" • Database now at version {sorted_versions[-1]}") - print(f" • Total time: {total_duration:.2f} seconds") return True From 5edea4781bde201893c540c988aa44450ff83d5d Mon Sep 17 00:00:00 2001 From: Alexis Gallagher Date: Mon, 9 Jun 2025 11:09:23 -0700 Subject: [PATCH 3/5] refactor stderr.write to print(...,file=stderr) --- fastmigrate/core.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/fastmigrate/core.py b/fastmigrate/core.py index 1b20d6a..c3294af 100644 --- a/fastmigrate/core.py +++ b/fastmigrate/core.py @@ -5,6 +5,7 @@ import sqlite3 import subprocess import sys +from sys import stderr import warnings from datetime import datetime from pathlib import Path @@ -237,14 +238,14 @@ def execute_sql_script(db_path: Path, script_path: Path) -> bool: except sqlite3.Error as e: # SQL error occurred - sys.stderr.write(f"Error executing SQL script {script_path}:\n") - sys.stderr.write(f" {e}\n") + print(f"Error executing SQL script {script_path}:", file=stderr) + print(f" {e}", file=stderr) return False except Exception as e: # Handle other errors (file not found, etc.) - sys.stderr.write(f"Error executing SQL script {script_path}:\n") - sys.stderr.write(f" {e}\n") + print(f"Error executing SQL script {script_path}:", file=stderr) + print(f" {e}", file=stderr) return False finally: @@ -264,9 +265,9 @@ def execute_python_script(db_path: Path, script_path: Path) -> bool: ) return True except subprocess.CalledProcessError as e: - sys.stderr.write(f"Error executing Python script {script_path}:\n") + print(f"Error executing Python script {script_path}:", file=stderr) sys.stderr.write(e.stderr.decode()) - sys.stderr.write('\n') + print("",file=stderr) return False @@ -282,9 +283,9 @@ def execute_shell_script(db_path: Path, script_path: Path) -> bool: ) return True except subprocess.CalledProcessError as e: - sys.stderr.write(f"Error executing shell script {script_path}:\n") + print(f"Error executing shell script {script_path}:", file=stderr) sys.stderr.write(e.stderr.decode()) - sys.stderr.write('\n') + print("",file=stderr) return False @@ -312,7 +313,7 @@ def create_db_backup(db_path: Path) -> Path | None: # Check if the backup file already exists if backup_path.exists(): - sys.stderr.write(f"Error: Backup file already exists: {backup_path}\n") + print(f"Error: Backup file already exists: {backup_path}", file=stderr) return None conn = None @@ -339,7 +340,7 @@ def create_db_backup(db_path: Path) -> Path | None: backup_path.unlink() # remove the file print(f"Removed incomplete backup file: {backup_path}") except OSError as remove_err: - sys.stderr.write(f"Error removing incomplete backup file: {remove_err}\n") + print(f"Error removing incomplete backup file: {remove_err}", file=stderr) return None finally: # this runs before return `backup_path` or `return None` in the try block @@ -370,7 +371,7 @@ def execute_migration_script(db_path: Path, script_path: Path) -> bool: elif ext == ".sh": return execute_shell_script(db_path, script_path) else: - sys.stderr.write(f"Unsupported script type: {script_path}\n") + print(f"Unsupported script type: {script_path}", file=stderr) return False @@ -402,8 +403,8 @@ def run_migrations( # Check if database file exists if not db_path.exists(): - sys.stderr.write(f"Error: Database file does not exist: {db_path}\n") - sys.stderr.write("The database file must exist before running migrations.\n") + print(f"Error: Database file does not exist: {db_path}", file=stderr) + print("The database file must exist before running migrations.",file=stderr) return False try: @@ -411,7 +412,7 @@ def run_migrations( try: create_db(db_path) except sqlite3.Error as e: - sys.stderr.write(f"""Error: Cannot migrate the db at {db_path}. + print(f"""Error: Cannot migrate the db at {db_path}. This is because it is not managed by fastmigrate. Please do one of the following: @@ -421,7 +422,7 @@ def run_migrations( 2. Enroll your existing database, by manually verifying your existing db's data matches a version defined by your migration scripts, and then setting your db's version explicitly with -`fastmigrate.core._set_db_version()`. See enrolling.md for guidance.\n""") +`fastmigrate.core._set_db_version()`. See enrolling.md for guidance.""",file=stderr) return False # Get current version @@ -431,7 +432,7 @@ def run_migrations( try: migration_scripts = get_migration_scripts(migrations_dir) except ValueError as e: - sys.stderr.write(f"Error: {e}\n") + print(f"Error: {e}", file=stderr) return False # Find pending migrations @@ -463,11 +464,11 @@ def run_migrations( success = execute_migration_script(db_path, script_path) if not success: - sys.stderr.write(f"Migration failed: {script_path}\n") + print(f"Migration failed: {script_path}", file=stderr) stats["failed"] += 1 # Show summary of failure - always show errors regardless of verbose flag - sys.stderr.write("\nMigration Failed\n") + print("\nMigration Failed",file=stderr) print(f" • {stats['applied']} migrations applied") print(f" • {stats['failed']} migrations failed") @@ -489,8 +490,8 @@ def run_migrations( return True except sqlite3.Error as e: - sys.stderr.write(f"Database error: {e}\n") + print(f"Database error: {e}", file=stderr) return False except Exception as e: - sys.stderr.write(f"Error: {e}\n") + print(f"Error: {e}", file=stderr) return False From 70bc0d577e224485813077654757a7a8aa2a4fff Mon Sep 17 00:00:00 2001 From: Alexis Gallagher Date: Mon, 9 Jun 2025 11:18:15 -0700 Subject: [PATCH 4/5] Streamline error message and link to instructions --- fastmigrate/core.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fastmigrate/core.py b/fastmigrate/core.py index c3294af..e728682 100644 --- a/fastmigrate/core.py +++ b/fastmigrate/core.py @@ -419,10 +419,8 @@ def run_migrations( 1. Create a new, managed db using `fastmigrate.create_db()` or `fastmigrate_create_db` -2. Enroll your existing database, by manually verifying your existing -db's data matches a version defined by your migration scripts, and -then setting your db's version explicitly with -`fastmigrate.core._set_db_version()`. See enrolling.md for guidance.""",file=stderr) +2. Enroll your existing database, as described in +https://answerdotai.github.io/fastmigrate/enrolling.html""",file=stderr) return False # Get current version From 3822a483e6add9f7a600619c9da230efe0a1cb54 Mon Sep 17 00:00:00 2001 From: Alexis Gallagher Date: Mon, 9 Jun 2025 11:21:06 -0700 Subject: [PATCH 5/5] Report migration failures only via stderr --- fastmigrate/core.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/fastmigrate/core.py b/fastmigrate/core.py index e728682..224bfd2 100644 --- a/fastmigrate/core.py +++ b/fastmigrate/core.py @@ -462,13 +462,11 @@ def run_migrations( success = execute_migration_script(db_path, script_path) if not success: - print(f"Migration failed: {script_path}", file=stderr) - stats["failed"] += 1 - # Show summary of failure - always show errors regardless of verbose flag - print("\nMigration Failed",file=stderr) - print(f" • {stats['applied']} migrations applied") - print(f" • {stats['failed']} migrations failed") + stats["failed"] += 1 + print(f"""Migration failed: {script_path} + • {stats['applied']} migrations applied + • {stats['failed']} migrations failed""", file=stderr) return False