Search code examples
postgresqldatabase-migrationfastapipython-3.9ormar

Alembic migrations in ormar not working (FastAPI)


I want to migrate through Alembic, but something doesn't work. I don't understand what exactly I'm doing wrong.

My alembic .env

from logging.config import fileConfig

from sqlalchemy import create_engine
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from db import ORMAR_DATABASE_URL
from alembic import context
import sys, os

sys.path.append(os.getcwd())
config = context.config

fileConfig(config.config_file_name)

from db import Base
target_metadata = Base.metadata
URL = "postgresql://admin:admin@localhost/fa_naesmi"



def run_migrations_offline():


context.configure(
    url=URL,
    target_metadata=target_metadata,
    literal_binds=True,
    dialect_opts={"paramstyle": "named"},
    user_module_prefix='sa.'
)

with context.begin_transaction():
    context.run_migrations()


def run_migrations_online():
connectable = create_engine(URL)

with connectable.connect() as connection:
    context.configure(
        connection=connection,
        target_metadata=target_metadata,
        user_module_prefix='sa.'
    )

    with context.begin_transaction():
        context.run_migrations()


if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

my db.py file:

import sqlalchemy
import databases
from sqlalchemy.ext.declarative import declarative_base
ORMAR_DATABASE_URL = "postgresql://admin:admin@localhost/fa_naesmi"

Base = declarative_base()
metadata = sqlalchemy.MetaData()
database = databases.Database(ORMAR_DATABASE_URL)
engine = sqlalchemy.create_engine(ORMAR_DATABASE_URL)

and my models.py:

import datetime
import ormar
from typing import Optional
from db import database, metadata, Base


class MainMeta(Base, ormar.ModelMeta):
    metadata = metadata
    database = database


class Category(Base, ormar.Model):
    class Meta(MainMeta):
        pass

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)

after using alembic revision -m "first", it doesn't migrate my models.

revision = '9176fb20d67a'
down_revision = '9159cff21eb5'
branch_labels = None
depends_on = None

def upgrade():
    pass


def downgrade():
    pass

I already wrote in console alembic revision --autogenerate -m "razraz" and it creates alembic database tables, but migrations still not working.


Solution

  • The error is caused by usage of declarative_base() from sqlalchemy.

    Sqlalchemy consists of two main parts:

    • ORM - which is a object relational mapping part with Models, relations, queries etc.
    • core - which is much more 'raw' and basic and in general it's related to table and columns creation, as well as queries generation (joins, where clause etc.)

    In ormar only the latter is used and you cannot apply any concept from the ORM part. In fact ormar is kind of (simplified by async and pydantic based) equivalent of the ORM part.

    That means that you cannot inherit from Base <- that's the orm part I mentioned.

    So your sample should look something like following snippets:

    alembic .env

    from logging.config import fileConfig
    from sqlalchemy import create_engine
    
    from db import ORMAR_DATABASE_URL
    from models import metadata # adjust path if needed
    from alembic import context
    import sys, os
    
    sys.path.append(os.getcwd())
    config = context.config
    
    fileConfig(config.config_file_name)
    
    # note how it's 'raw' metadata not the one attached to Base as there is no Base
    target_metadata = metadata
    URL = ORMAR_DATABASE_URL
    
    
    def run_migrations_offline():
    
    
    context.configure(
        url=URL,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
        user_module_prefix='sa.'
    )
    
    with context.begin_transaction():
        context.run_migrations()
    
    
    def run_migrations_online():
    connectable = create_engine(URL)
    
    with connectable.connect() as connection:
        context.configure(
            connection=connection,
            target_metadata=target_metadata,
            user_module_prefix='sa.'
        )
    
        with context.begin_transaction():
            context.run_migrations()
    
    
    if context.is_offline_mode():
        run_migrations_offline()
    else:
        run_migrations_online()
    

    db.py file:

    import sqlalchemy
    import databases
    ORMAR_DATABASE_URL = "postgresql://admin:admin@localhost/fa_naesmi"
    
    # note lack of declarative_base
    metadata = sqlalchemy.MetaData()
    database = databases.Database(ORMAR_DATABASE_URL)
    engine = sqlalchemy.create_engine(ORMAR_DATABASE_URL)
    

    models.py:

    import datetime
    import ormar
    from typing import Optional
    from db import database, metadata
    
    
    # you cannot subclass Base class thats ORM part
    class MainMeta(ormar.ModelMeta):
        metadata = metadata
        database = database
    
    
    class Category(ormar.Model):
        class Meta(MainMeta):
            pass
    
        id: int = ormar.Integer(primary_key=True)
        name: str = ormar.String(max_length=100)
    

    Disclaimer: I'm creator of ormar - if you have more questions or trouble with getting it to work let me know here. If that solves you issue remember to mark answer as accepted.

    Thanks!