Browse Source
* 🔥 Remove old SQLAlchemy models * ✨ Add new SQLModel models * 🔧 Update Alembic configs to work with SQLModel * ✨ Re-generate initial Alembic migration * 🔧 Update PostgreSQL driver connection string URL * ✨ Create new SQLModel engine * 🔥 Remove old unneeded SQLAlchemy-specific files * ♻️ Update init_db * ♻️ Use new SQLModel session * ♻️ Update conftest with new DB Session * ♻️ Update pre-start scripts to use SQLModel session * ♻️ Import new SQLModel models * ✨ Create new simplified create_user crud util * ♻️ Update import in CRUDBase class (soon to be removed) * 🙈 Update .gitignore with Python filespull/13907/head
committed by
GitHub
26 changed files with 193 additions and 163 deletions
@ -1,2 +1,3 @@ |
|||
__pycache__ |
|||
app.egg-info |
|||
*.pyc |
|||
|
@ -1,59 +0,0 @@ |
|||
"""First revision |
|||
|
|||
Revision ID: d4867f3a4c0a |
|||
Revises: |
|||
Create Date: 2019-04-17 13:53:32.978401 |
|||
|
|||
""" |
|||
from alembic import op |
|||
import sqlalchemy as sa |
|||
|
|||
|
|||
# revision identifiers, used by Alembic. |
|||
revision = "d4867f3a4c0a" |
|||
down_revision = None |
|||
branch_labels = None |
|||
depends_on = None |
|||
|
|||
|
|||
def upgrade(): |
|||
# ### commands auto generated by Alembic - please adjust! ### |
|||
op.create_table( |
|||
"user", |
|||
sa.Column("id", sa.Integer(), nullable=False), |
|||
sa.Column("full_name", sa.String(), nullable=True), |
|||
sa.Column("email", sa.String(), nullable=True), |
|||
sa.Column("hashed_password", sa.String(), nullable=True), |
|||
sa.Column("is_active", sa.Boolean(), nullable=True), |
|||
sa.Column("is_superuser", sa.Boolean(), nullable=True), |
|||
sa.PrimaryKeyConstraint("id"), |
|||
) |
|||
op.create_index(op.f("ix_user_email"), "user", ["email"], unique=True) |
|||
op.create_index(op.f("ix_user_full_name"), "user", ["full_name"], unique=False) |
|||
op.create_index(op.f("ix_user_id"), "user", ["id"], unique=False) |
|||
op.create_table( |
|||
"item", |
|||
sa.Column("id", sa.Integer(), nullable=False), |
|||
sa.Column("title", sa.String(), nullable=True), |
|||
sa.Column("description", sa.String(), nullable=True), |
|||
sa.Column("owner_id", sa.Integer(), nullable=True), |
|||
sa.ForeignKeyConstraint(["owner_id"], ["user.id"],), |
|||
sa.PrimaryKeyConstraint("id"), |
|||
) |
|||
op.create_index(op.f("ix_item_description"), "item", ["description"], unique=False) |
|||
op.create_index(op.f("ix_item_id"), "item", ["id"], unique=False) |
|||
op.create_index(op.f("ix_item_title"), "item", ["title"], unique=False) |
|||
# ### end Alembic commands ### |
|||
|
|||
|
|||
def downgrade(): |
|||
# ### commands auto generated by Alembic - please adjust! ### |
|||
op.drop_index(op.f("ix_item_title"), table_name="item") |
|||
op.drop_index(op.f("ix_item_id"), table_name="item") |
|||
op.drop_index(op.f("ix_item_description"), table_name="item") |
|||
op.drop_table("item") |
|||
op.drop_index(op.f("ix_user_id"), table_name="user") |
|||
op.drop_index(op.f("ix_user_full_name"), table_name="user") |
|||
op.drop_index(op.f("ix_user_email"), table_name="user") |
|||
op.drop_table("user") |
|||
# ### end Alembic commands ### |
@ -0,0 +1,48 @@ |
|||
"""Initialize models |
|||
|
|||
Revision ID: e2412789c190 |
|||
Revises: |
|||
Create Date: 2023-11-24 22:55:43.195942 |
|||
|
|||
""" |
|||
from alembic import op |
|||
import sqlalchemy as sa |
|||
import sqlmodel.sql.sqltypes |
|||
|
|||
|
|||
# revision identifiers, used by Alembic. |
|||
revision = 'e2412789c190' |
|||
down_revision = None |
|||
branch_labels = None |
|||
depends_on = None |
|||
|
|||
|
|||
def upgrade(): |
|||
# ### commands auto generated by Alembic - please adjust! ### |
|||
op.create_table('user', |
|||
sa.Column('email', sqlmodel.sql.sqltypes.AutoString(), nullable=False), |
|||
sa.Column('is_active', sa.Boolean(), nullable=False), |
|||
sa.Column('is_superuser', sa.Boolean(), nullable=False), |
|||
sa.Column('full_name', sqlmodel.sql.sqltypes.AutoString(), nullable=True), |
|||
sa.Column('id', sa.Integer(), nullable=False), |
|||
sa.Column('hashed_password', sqlmodel.sql.sqltypes.AutoString(), nullable=False), |
|||
sa.PrimaryKeyConstraint('id') |
|||
) |
|||
op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True) |
|||
op.create_table('item', |
|||
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True), |
|||
sa.Column('id', sa.Integer(), nullable=False), |
|||
sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=False), |
|||
sa.Column('owner_id', sa.Integer(), nullable=False), |
|||
sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ), |
|||
sa.PrimaryKeyConstraint('id') |
|||
) |
|||
# ### end Alembic commands ### |
|||
|
|||
|
|||
def downgrade(): |
|||
# ### commands auto generated by Alembic - please adjust! ### |
|||
op.drop_table('item') |
|||
op.drop_index(op.f('ix_user_email'), table_name='user') |
|||
op.drop_table('user') |
|||
# ### end Alembic commands ### |
@ -1,5 +0,0 @@ |
|||
# Import all the models, so that Base has them before being |
|||
# imported by Alembic |
|||
from app.db.base_class import Base # noqa |
|||
from app.models.item import Item # noqa |
|||
from app.models.user import User # noqa |
@ -1,13 +0,0 @@ |
|||
from typing import Any |
|||
|
|||
from sqlalchemy.ext.declarative import as_declarative, declared_attr |
|||
|
|||
|
|||
@as_declarative() |
|||
class Base: |
|||
id: Any |
|||
__name__: str |
|||
# Generate __tablename__ automatically |
|||
@declared_attr |
|||
def __tablename__(cls) -> str: |
|||
return cls.__name__.lower() |
@ -0,0 +1,5 @@ |
|||
from sqlmodel import create_engine |
|||
|
|||
from app.core.config import settings |
|||
|
|||
engine = create_engine(settings.SQLALCHEMY_DATABASE_URI) |
@ -1,25 +1,26 @@ |
|||
from sqlalchemy.orm import Session |
|||
from sqlmodel import Session, select |
|||
|
|||
from app import crud, schemas |
|||
from app import crud |
|||
from app.core.config import settings |
|||
from app.db import base # noqa: F401 |
|||
from app.models import User, UserCreate # noqa: F401 |
|||
|
|||
# make sure all SQL Alchemy models are imported (app.db.base) before initializing DB |
|||
# otherwise, SQL Alchemy might fail to initialize relationships properly |
|||
# make sure all SQLModel models are imported (app.models) before initializing DB |
|||
# otherwise, SQLModel might fail to initialize relationships properly |
|||
# for more details: https://github.com/tiangolo/full-stack-fastapi-postgresql/issues/28 |
|||
|
|||
|
|||
def init_db(db: Session) -> None: |
|||
def init_db(session: Session) -> None: |
|||
# Tables should be created with Alembic migrations |
|||
# But if you don't want to use migrations, create |
|||
# the tables un-commenting the next line |
|||
# Base.metadata.create_all(bind=engine) |
|||
|
|||
user = crud.user.get_by_email(db, email=settings.FIRST_SUPERUSER) |
|||
user = session.exec( |
|||
select(User).where(User.email == settings.FIRST_SUPERUSER) |
|||
).first() |
|||
if not user: |
|||
user_in = schemas.UserCreate( |
|||
user_in = UserCreate( |
|||
email=settings.FIRST_SUPERUSER, |
|||
password=settings.FIRST_SUPERUSER_PASSWORD, |
|||
is_superuser=True, |
|||
) |
|||
user = crud.user.create(db, obj_in=user_in) # noqa: F841 |
|||
user = crud.create_user(session, user_create=user_in) |
|||
|
@ -1,7 +0,0 @@ |
|||
from sqlalchemy import create_engine |
|||
from sqlalchemy.orm import sessionmaker |
|||
|
|||
from app.core.config import settings |
|||
|
|||
engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True) |
|||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) |
@ -0,0 +1,82 @@ |
|||
from typing import Union |
|||
|
|||
from pydantic import BaseModel, EmailStr |
|||
from sqlmodel import Field, Relationship, SQLModel |
|||
|
|||
|
|||
# Shared properties |
|||
class UserBase(SQLModel): |
|||
email: EmailStr = Field(unique=True, index=True) |
|||
is_active: bool = True |
|||
is_superuser: bool = False |
|||
full_name: Union[str, None] = None |
|||
|
|||
|
|||
# Properties to receive via API on creation |
|||
class UserCreate(UserBase): |
|||
password: str |
|||
|
|||
|
|||
# Properties to receive via API on update, all are optional |
|||
class UserUpdate(UserBase): |
|||
email: Union[EmailStr, None] = None |
|||
password: Union[str, None] = None |
|||
|
|||
|
|||
# Database model, database table inferred from class name |
|||
class User(UserBase, table=True): |
|||
id: Union[int, None] = Field(default=None, primary_key=True) |
|||
hashed_password: str |
|||
items: list["Item"] = Relationship(back_populates="owner") |
|||
|
|||
|
|||
# Properties to return via API, id is always required |
|||
class UserOut(UserBase): |
|||
id: int |
|||
|
|||
|
|||
# Shared properties |
|||
class ItemBase(SQLModel): |
|||
title: str |
|||
description: Union[str, None] = None |
|||
|
|||
|
|||
# Properties to receive on item creation |
|||
class ItemCreate(ItemBase): |
|||
title: str |
|||
|
|||
|
|||
# Properties to receive on item update |
|||
class ItemUpdate(ItemBase): |
|||
title: Union[str, None] = None |
|||
|
|||
|
|||
# Database model, database table inferred from class name |
|||
class Item(ItemBase, table=True): |
|||
id: Union[int, None] = Field(default=None, primary_key=True) |
|||
title: str |
|||
owner_id: Union[int, None] = Field( |
|||
default=None, foreign_key="user.id", nullable=False |
|||
) |
|||
owner: Union[User, None] = Relationship(back_populates="items") |
|||
|
|||
|
|||
# Properties to return via API, id is always required |
|||
class ItemOut(ItemBase): |
|||
id: int |
|||
|
|||
|
|||
# Generic message |
|||
class Msg(BaseModel): |
|||
msg: str |
|||
|
|||
|
|||
# JSON payload containing access token |
|||
class Token(BaseModel): |
|||
access_token: str |
|||
token_type: str |
|||
|
|||
|
|||
# Contents of JWT token |
|||
class TokenPayload(BaseModel): |
|||
sub: Union[int, None] = None |
@ -1,2 +0,0 @@ |
|||
from .item import Item |
|||
from .user import User |
@ -1,17 +0,0 @@ |
|||
from typing import TYPE_CHECKING |
|||
|
|||
from sqlalchemy import Column, ForeignKey, Integer, String |
|||
from sqlalchemy.orm import relationship |
|||
|
|||
from app.db.base_class import Base |
|||
|
|||
if TYPE_CHECKING: |
|||
from .user import User # noqa: F401 |
|||
|
|||
|
|||
class Item(Base): |
|||
id = Column(Integer, primary_key=True, index=True) |
|||
title = Column(String, index=True) |
|||
description = Column(String, index=True) |
|||
owner_id = Column(Integer, ForeignKey("user.id")) |
|||
owner = relationship("User", back_populates="items") |
@ -1,19 +0,0 @@ |
|||
from typing import TYPE_CHECKING |
|||
|
|||
from sqlalchemy import Boolean, Column, Integer, String |
|||
from sqlalchemy.orm import relationship |
|||
|
|||
from app.db.base_class import Base |
|||
|
|||
if TYPE_CHECKING: |
|||
from .item import Item # noqa: F401 |
|||
|
|||
|
|||
class User(Base): |
|||
id = Column(Integer, primary_key=True, index=True) |
|||
full_name = Column(String, index=True) |
|||
email = Column(String, unique=True, index=True, nullable=False) |
|||
hashed_password = Column(String, nullable=False) |
|||
is_active = Column(Boolean(), default=True) |
|||
is_superuser = Column(Boolean(), default=False) |
|||
items = relationship("Item", back_populates="owner") |
Loading…
Reference in new issue