kvesteri / sqlalchemy-continuum

Versioning extension for SQLAlchemy.
BSD 3-Clause "New" or "Revised" License
575 stars 127 forks source link

Example with encode/databases for async SQLAlchemy #259

Closed michaeltoohig closed 2 years ago

michaeltoohig commented 2 years ago

I'm having trouble using FastAPI and FastAPI-CRUDRouter which is using this project https://github.com/encode/databases with SQLAlchemy.

I probably need to provide a mapper and/or session at start up but the few ways I've tried providing a session from sessionmaker is not doing it. Infact, wherever I place the make_versioned(user_cls=None) nothing ever happens and I feel like it is never being called or failing silently, I'm not sure.

For testing, I just took the example from FastAPI-CRUDRouter to test a minimal example but had no luck. Perhaps someone has done this already and knows what to do?

Here is the full example I was using to test which also includes the basic example from FastAPI-Users as I wanted to make a plugin like the FlaskPlugin.

import databases
import sqlalchemy
from pydantic import BaseModel
from fastapi import FastAPI, Request
from fastapi_users import FastAPIUsers, models
from fastapi_users.authentication import JWTAuthentication
from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base

DATABASE_URL = "postgresql://postgres:postgres@localhost:5433/postgres"
SECRET = "SECRET"

class User(models.BaseUser):
    pass

class UserCreate(models.BaseUserCreate):
    pass

class UserUpdate(User, models.BaseUserUpdate):
    pass

class UserDB(User, models.BaseUserDB):
    pass

database = databases.Database(DATABASE_URL)
Base: DeclarativeMeta = declarative_base()

# Add continuum
from sqlalchemy_continuum import make_versioned
make_versioned(user_cls=None)

class UserTable(Base, SQLAlchemyBaseUserTable):
    # __tablename__ = "users_table"
    pass

from sqlalchemy import Column, Integer, String

# Lets test sqlalchemy-continuum here and make a plugin to attach the current user to the versioned history row
class Things(Base):
    __tablename__ = "things"
    __versioned__ = {}

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, nullable=False)

# Make schemas for `Things`
class ThingCreate(BaseModel):
    name: str

class Thing(ThingCreate):
    id: int

    class Config:
        orm_mode = True

# Add CRUDRouter for `Things`
from fastapi_crudrouter import DatabasesCRUDRouter
things_router = DatabasesCRUDRouter(
    schema=Thing,
    create_schema=ThingCreate,
    table=Things.__table__,
    database=database
)

engine = sqlalchemy.create_engine(DATABASE_URL)
Base.metadata.create_all(engine)

users = UserTable.__table__
user_db = SQLAlchemyUserDatabase(UserDB, database, users)

def on_after_register(user: UserDB, request: Request):
    print(f"User {user.id} has registered.")

def on_after_forgot_password(user: UserDB, token: str, request: Request):
    print(f"User {user.id} has forgot their password. Reset token: {token}")

def after_verification_request(user: UserDB, token: str, request: Request):
    print(f"Verification requested for user {user.id}. Verification token: {token}")

jwt_authentication = JWTAuthentication(
    secret=SECRET, lifetime_seconds=3600, tokenUrl="auth/jwt/login"
)

app = FastAPI()

# add our Things router
app.include_router(things_router)

fastapi_users = FastAPIUsers(
    user_db,
    [jwt_authentication],
    User,
    UserCreate,
    UserUpdate,
    UserDB,
)
app.include_router(
    fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"]
)
app.include_router(
    fastapi_users.get_register_router(on_after_register), prefix="/auth", tags=["auth"]
)
app.include_router(
    fastapi_users.get_reset_password_router(
        SECRET, after_forgot_password=on_after_forgot_password
    ),
    prefix="/auth",
    tags=["auth"],
)
app.include_router(
    fastapi_users.get_verify_router(
        SECRET, after_verification_request=after_verification_request
    ),
    prefix="/auth",
    tags=["auth"],
)
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])

@app.on_event("startup")
async def startup():
    await database.connect()

@app.on_event("shutdown")
async def shutdown():
    await database.disconnect()
michaeltoohig commented 2 years ago

I'm no longer interested in this question I created. I've upgraded to SQLAlchemy 1.4 and no longer use encode/databases.

marksteward commented 2 years ago

In case anyone comes across this issue in the future, the code above has no reference to configure_mappers, which is the most likely cause of "nothing ever happens and I feel like it is never being called".