Skip to content

Commit

Permalink
Merge pull request #71 from cicekhayri/migrations
Browse files Browse the repository at this point in the history
Migrations
  • Loading branch information
cicekhayri authored Jan 20, 2024
2 parents ca8e975 + 65cbb68 commit cd17fb6
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 148 deletions.
8 changes: 4 additions & 4 deletions inspira/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
from inspira.cli.generate_model_file import generate_model_file
from inspira.cli.generate_repository_file import generate_repository_file
from inspira.cli.generate_service_file import generate_service_file

from inspira.migrations.migrations import run_migrations, create_migrations
from inspira.migrations.migrations import create_migrations, run_migrations

DATABASE_TYPES = ["postgres", "mysql", "sqlite", "mssql"]

Expand Down Expand Up @@ -115,12 +114,13 @@ def migration(migration_name):


@cli.command()
def migrate():
@click.option("--down", is_flag=True, help="Run Down migrations.")
def migrate(down):
"""
Run migrations from the migrations folder.
"""
try:
run_migrations()
run_migrations(down=down)
except Exception as e:
click.echo(f"Error: {e}")
click.echo("Migration failed. Check logs for more details.")
Expand Down
2 changes: 1 addition & 1 deletion inspira/cli/create_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from inspira.cli.create_app import generate_project
from inspira.cli.init_file import create_init_file
from inspira.constants import SRC_DIRECTORY
from inspira.utils import singularize, pluralize_word
from inspira.utils import pluralize_word, singularize


def create_src_directory():
Expand Down
1 change: 1 addition & 0 deletions inspira/cli/generate_service_file.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os

import click

from inspira.constants import SRC_DIRECTORY
Expand Down
107 changes: 69 additions & 38 deletions inspira/migrations/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,34 +49,14 @@ def initialize_database(engine):
Base.metadata.create_all(engine)


def execute_sql_file(file_path):
with open(file_path, "r") as file:
sql_content = file.read()

sql_statements = [
statement.strip() for statement in sql_content.split(";") if statement.strip()
]

with engine.connect() as connection:
try:
for statement in sql_statements:
connection.execute(text(statement))
connection.commit()
log.info("Migration run successfully.")
except SQLAlchemyError as e:
log.error("Error:", e)
connection.rollback()
log.info("Transaction rolled back.")


def create_migrations(migration_name):
if migration_file_exist(migration_name):
return

generate_migration_file(migration_name)


def run_migrations():
def run_migrations(down=False):
with engine.connect() as connection:
if not engine.dialect.has_table(connection, MIGRATION_DIRECTORY):
initialize_database(engine)
Expand All @@ -96,24 +76,65 @@ def run_migrations():
or 0
)

if not current_version:
execute_sql_file(file)
click.echo(
f"Applying migration for {migration_name} version {current_version}"
)
if not current_version and not down:
execute_up_migration(connection, file, migration_name)

insert_migration(current_version, migration_name)
continue

if down:
execute_down_migration(connection, file, migration_name)
remove_migration(migration_name)

def get_existing_columns(table_name):
metadata = MetaData()
metadata.reflect(bind=engine)

if table_name in metadata.tables:
table = metadata.tables[table_name]
return [column.name for column in table.columns]
else:
return None
def execute_up_migration(connection, file, migration_name):
with open(file, "r") as migration_file:
sql = migration_file.read()

up_sql_start = sql.find("-- Up")
if up_sql_start != -1:
up_sql_start += len("-- Up")
up_sql_end = sql.find("-- Down") if "-- Down" in sql else None
up_sql = sql[up_sql_start:up_sql_end].strip()

execute_sql_file_contents(connection, up_sql)

click.echo(f"Applying 'Up' migration for {migration_name}")
else:
click.echo(f"No 'Up' migration found in {migration_name}")


def execute_down_migration(connection, file, migration_name):
with open(file, "r") as migration_file:
sql = migration_file.read()

down_sql_start = sql.find("-- Down")
if down_sql_start != -1:
down_sql_start += len("-- Down")
down_sql_end = sql.find("-- End Down") if "-- End Down" in sql else None
down_sql = sql[down_sql_start:down_sql_end].strip()

execute_sql_file_contents(connection, down_sql)

click.echo(f"Applying 'Down' migration for {migration_name}")
else:
click.echo(f"No 'Down' migration found in {migration_name}")


def execute_sql_file_contents(connection, sql_content):
sql_statements = [
statement.strip() for statement in sql_content.split(";") if statement.strip()
]

try:
for statement in sql_statements:
connection.execute(text(statement))
connection.commit()
log.info("Migration run successfully.")
except SQLAlchemyError as e:
log.error("Error:", e)
connection.rollback()
log.info("Transaction rolled back.")


def insert_migration(current_version, migration_name):
Expand All @@ -124,7 +145,17 @@ def insert_migration(current_version, migration_name):
db_session.commit()


def get_existing_indexes(table_name):
inspector = inspect(engine)
indexes = inspector.get_indexes(table_name)
return [index["name"] for index in indexes]
def remove_migration(migration_name):
migration = (
db_session.query(Migration).filter_by(migration_name=migration_name).first()
)

if migration:
try:
db_session.delete(migration)
db_session.commit()
except Exception as e:
db_session.rollback()
log.error(f"Error deleting migration {migration_name}: {e}")
else:
log.error(f"Migration {migration_name} not found.")
4 changes: 3 additions & 1 deletion inspira/migrations/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ def generate_migration_file(migration_name):
)

with open(migration_file_path, "w") as migration_file:
pass
migration_file.write(f"-- Up\n")
migration_file.write("\n")
migration_file.write("\n-- Down\n")

log.info(
f"Migration file '{str(new_migration_number).zfill(4)}_{migration_name}.sql' created."
Expand Down
68 changes: 14 additions & 54 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import os
import shutil
from unittest.mock import AsyncMock
from unittest.mock import AsyncMock, MagicMock

import pytest
from click.testing import CliRunner

from inspira import Inspira
from inspira.config import Config
from inspira.constants import SRC_DIRECTORY, MIGRATION_DIRECTORY
from inspira.constants import MIGRATION_DIRECTORY, SRC_DIRECTORY
from inspira.requests import Request
from inspira.testclient import TestClient

Expand All @@ -31,6 +32,11 @@ def client_session(app):
return TestClient(app)


@pytest.fixture
def mock_connection():
return MagicMock()


@pytest.fixture
def request_with_session(mock_scope):
receive = AsyncMock()
Expand All @@ -40,12 +46,6 @@ def request_with_session(mock_scope):
return request


@pytest.fixture
def teardown_app_file():
yield
os.remove("main.py")


@pytest.fixture
def teardown_src_directory():
yield
Expand All @@ -59,18 +59,15 @@ def teardown_migration_directory():


@pytest.fixture
def teardown_database_file():
def teardown_main_file():
yield
os.remove("database.py")
os.remove("main.py")


@pytest.fixture
def setup_database_file(teardown_database_file):
file_name = "database.py"
with open(file_name, "w") as file:
# You can write content to the file if needed
file.write("Hello, this is a new file!")
def teardown_database_file():
yield
os.remove("database.py")


@pytest.fixture
Expand Down Expand Up @@ -114,33 +111,6 @@ def __init__(self, id):
return User


@pytest.fixture
def sample_sql_file(tmp_path):
sql_content = """
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(50) NULL,
email VARCHAR(120) NULL
);
"""
sql_file = tmp_path / "test.sql"
with open(sql_file, "w") as file:
file.write(sql_content)
return str(sql_file)


@pytest.fixture
def add_index_users(tmp_path, teardown_src_directory):
sql_content = """
CREATE INDEX ix_users_name ON users (name);
"""

sql_file = tmp_path / "test.sql"
with open(sql_file, "w") as file:
file.write(sql_content)
return str(sql_file)


@pytest.fixture
def setup_teardown_db_session():
from inspira.migrations.migrations import db_session, engine, initialize_database
Expand All @@ -151,15 +121,5 @@ def setup_teardown_db_session():


@pytest.fixture
def setup_test_environment():
# os.makedirs(SRC_DIRECTORY)

dirs_to_simulate = ["module1", "module2", "module3"]

for module in dirs_to_simulate:
module_dir = os.path.join(SRC_DIRECTORY, module)
os.makedirs(module_dir)
migrations_dir = os.path.join(module_dir, MIGRATION_DIRECTORY)
os.makedirs(migrations_dir)

yield SRC_DIRECTORY
def runner():
return CliRunner()
Loading

0 comments on commit cd17fb6

Please sign in to comment.