No SQLModel

This commit is contained in:
ciomek committed 2026-09-04 23:41:35 +02:00
1 parent 5f0e8c11b4
commit 9964fbf044
16 files changed
+91 -84

No files matched your search

+2 -3
View File
@@ -4,9 +4,8 @@ from sqlalchemy import engine_from_config
from sqlalchemy import pool from sqlalchemy import pool
from alembic import context from alembic import context
from sqlmodel import SQLModel
from loon.web.db import database_url from loon.web.db import Base, database_url
import loon.web import loon.web
@@ -23,7 +22,7 @@ if config.config_file_name is not None:
# for 'autogenerate' support # for 'autogenerate' support
# from myapp import mymodel # from myapp import mymodel
# target_metadata = mymodel.Base.metadata # target_metadata = mymodel.Base.metadata
target_metadata = SQLModel.metadata target_metadata = Base.metadata
config.set_main_option("sqlalchemy.url", database_url()) config.set_main_option("sqlalchemy.url", database_url())
+6 -7
View File
@@ -10,7 +10,6 @@ from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
import sqlmodel
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '0f809a005dc6' revision: str = '0f809a005dc6'
@@ -24,14 +23,14 @@ def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('logtype', op.create_table('logtype',
sa.Column('id', sa.Integer(), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('code', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column('code', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id'), sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code') sa.UniqueConstraint('code')
) )
op.create_table('minecraftuser', op.create_table('minecraftuser',
sa.Column('id', sa.Integer(), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column('uuid', sa.String(), nullable=False),
sa.Column('username', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column('username', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id'), sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('uuid') sa.UniqueConstraint('uuid')
) )
@@ -39,15 +38,15 @@ def upgrade() -> None:
sa.Column('id', sa.Integer(), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('log_type_id', sa.Integer(), nullable=False), sa.Column('log_type_id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False), sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('message', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column('message', sa.String(), nullable=False),
sa.ForeignKeyConstraint(['log_type_id'], ['logtype.id'], ), sa.ForeignKeyConstraint(['log_type_id'], ['logtype.id'], ),
sa.PrimaryKeyConstraint('id') sa.PrimaryKeyConstraint('id')
) )
op.create_table('user', op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('minecraft_user_id', sa.Integer(), nullable=False), sa.Column('minecraft_user_id', sa.Integer(), nullable=False),
sa.Column('internal_username', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column('internal_username', sa.String(), nullable=False),
sa.Column('password', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column('password', sa.String(), nullable=False),
sa.Column('admin', sa.Boolean(), nullable=False), sa.Column('admin', sa.Boolean(), nullable=False),
sa.ForeignKeyConstraint(['minecraft_user_id'], ['minecraftuser.id'], ), sa.ForeignKeyConstraint(['minecraft_user_id'], ['minecraftuser.id'], ),
sa.PrimaryKeyConstraint('id'), sa.PrimaryKeyConstraint('id'),
@@ -1,7 +1,8 @@
import json import json
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlmodel import Session, select from sqlalchemy import select
from sqlalchemy.orm import Session
from loon.web.db import engine from loon.web.db import engine
from loon.web.logs.service import write_log, player_tag from loon.web.logs.service import write_log, player_tag
@@ -25,16 +26,16 @@ async def change_password_handler(client, userdata, msg, data):
with Session(engine) as session: with Session(engine) as session:
statement = select(User).join(MinecraftUser).where(MinecraftUser.uuid == uuid) statement = select(User).join(MinecraftUser).where(MinecraftUser.uuid == uuid)
user = session.exec(statement).first() user = session.execute(statement).scalars().first()
if user is None: if user is None:
client.publish(f"loon/auth/change_password/{uuid}/response", client.publish(f"loon/auth/change_password/{uuid}/response",
json.dumps({"success": False, "error": "The user is not registered!"})) json.dumps({"success": False, "error": "The user is not registered!"}))
return return
user = session.exec( user = session.execute(
select(User).join(MinecraftUser).where(MinecraftUser.uuid == uuid) select(User).join(MinecraftUser).where(MinecraftUser.uuid == uuid)
).first() ).scalars().first()
user.password = password user.password = password
internal_username = user.internal_username internal_username = user.internal_username
+5 -4
View File
@@ -1,7 +1,8 @@
import json import json
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlmodel import Session, select from sqlalchemy import select
from sqlalchemy.orm import Session
from loon.web.db import engine from loon.web.db import engine
from loon.web.logs.service import write_log, player_tag from loon.web.logs.service import write_log, player_tag
@@ -25,7 +26,7 @@ async def register_request_handler(client, userdata, msg, data):
internal_username = data["internalUsername"] internal_username = data["internalUsername"]
with Session(engine) as session: with Session(engine) as session:
minecraft_user = session.exec(select(MinecraftUser).where(MinecraftUser.uuid == uuid)).first() minecraft_user = session.execute(select(MinecraftUser).where(MinecraftUser.uuid == uuid)).scalars().first()
if minecraft_user is None: if minecraft_user is None:
client.publish(f"loon/auth/register/{uuid}/response", client.publish(f"loon/auth/register/{uuid}/response",
@@ -33,7 +34,7 @@ async def register_request_handler(client, userdata, msg, data):
return return
statement = select(User).where(User.minecraft_user_id == minecraft_user.id) statement = select(User).where(User.minecraft_user_id == minecraft_user.id)
user = session.exec(statement).first() user = session.execute(statement).scalars().first()
if user is not None: if user is not None:
client.publish(f"loon/auth/register/{uuid}/response", client.publish(f"loon/auth/register/{uuid}/response",
@@ -41,7 +42,7 @@ async def register_request_handler(client, userdata, msg, data):
return return
statement = select(User).where(User.internal_username == internal_username) statement = select(User).where(User.internal_username == internal_username)
user = session.exec(statement).first() user = session.execute(statement).scalars().first()
if user is not None: if user is not None:
client.publish(f"loon/auth/register/{uuid}/response", client.publish(f"loon/auth/register/{uuid}/response",
+4 -3
View File
@@ -1,6 +1,7 @@
import json import json
from sqlmodel import Session, select from sqlalchemy import select
from sqlalchemy.orm import Session
from loon.web.db import engine from loon.web.db import engine
from loon.web.users.models import MinecraftUser, User from loon.web.users.models import MinecraftUser, User
@@ -19,9 +20,9 @@ async def show_username_handler(client, userdata, msg, data):
return return
with Session(engine) as session: with Session(engine) as session:
user = session.exec( user = session.execute(
select(User).join(MinecraftUser).where(MinecraftUser.uuid == uuid) select(User).join(MinecraftUser).where(MinecraftUser.uuid == uuid)
).first() ).scalars().first()
if user is None: if user is None:
client.publish(f"loon/auth/show_username/{uuid}/response", client.publish(f"loon/auth/show_username/{uuid}/response",
+4 -3
View File
@@ -1,4 +1,5 @@
from sqlmodel import Session, select from sqlalchemy import select
from sqlalchemy.orm import Session
from loon.web.db import engine from loon.web.db import engine
from loon.web.users.models import User from loon.web.users.models import User
@@ -6,9 +7,9 @@ from loon.web.users.models import User
def choose_admin(username: str) -> None: def choose_admin(username: str) -> None:
with Session(engine) as session: with Session(engine) as session:
user = session.exec( user = session.execute(
select(User).where(User.internal_username == username) select(User).where(User.internal_username == username)
).first() ).scalars().first()
if user is None: if user is None:
print(f"No user named {username!r}.") print(f"No user named {username!r}.")
+7 -7
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload, Session
from sqlmodel import Session, select from sqlalchemy import select
from starlette.requests import Request from starlette.requests import Request
from loon.web.auth.middleware import admin from loon.web.auth.middleware import admin
@@ -22,7 +22,7 @@ async def list_logs(request: Request):
.options(selectinload(LogEntry.log_type)) .options(selectinload(LogEntry.log_type))
.order_by(LogEntry.created_at) .order_by(LogEntry.created_at)
) )
entries = session.exec(statement).all() entries = session.execute(statement).scalars().all()
return [ return [
LogEntryResponse( LogEntryResponse(
id=entry.id, id=entry.id,
@@ -39,7 +39,7 @@ async def list_logs(request: Request):
async def list_mc_users(request: Request): async def list_mc_users(request: Request):
with Session(engine) as session: with Session(engine) as session:
statement = select(MinecraftUser).order_by(MinecraftUser.username) statement = select(MinecraftUser).order_by(MinecraftUser.username)
return session.exec(statement).all() return session.execute(statement).scalars().all()
@router.get("/users", response_model=list[UserPublic]) @router.get("/users", response_model=list[UserPublic])
@@ -51,7 +51,7 @@ async def list_users(request: Request):
.options(selectinload(User.minecraft_user)) .options(selectinload(User.minecraft_user))
.order_by(User.internal_username) .order_by(User.internal_username)
) )
return session.exec(statement).all() return session.execute(statement).scalars().all()
@router.get("/users/{internal_username}", response_model=UserPublic) @router.get("/users/{internal_username}", response_model=UserPublic)
@@ -63,7 +63,7 @@ async def get_user(request: Request, internal_username: str):
.where(User.internal_username == internal_username) .where(User.internal_username == internal_username)
.options(selectinload(User.minecraft_user)) .options(selectinload(User.minecraft_user))
) )
user = session.exec(statement).first() user = session.execute(statement).scalars().first()
if user is None: if user is None:
raise HTTPException(status_code=404) raise HTTPException(status_code=404)
return user return user
@@ -79,7 +79,7 @@ async def get_user_by_uuid(request: Request, uuid: str):
.where(MinecraftUser.uuid == uuid) .where(MinecraftUser.uuid == uuid)
.options(selectinload(User.minecraft_user)) .options(selectinload(User.minecraft_user))
) )
user = session.exec(statement).first() user = session.execute(statement).scalars().first()
if user is None: if user is None:
raise HTTPException(status_code=404) raise HTTPException(status_code=404)
return user return user
+4 -4
View File
@@ -8,8 +8,8 @@ from starlette.authentication import (
) )
from starlette.requests import HTTPConnection from starlette.requests import HTTPConnection
from starlette.responses import JSONResponse, Response from starlette.responses import JSONResponse, Response
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload, Session
from sqlmodel import Session, select from sqlalchemy import select
from loon.web.db import engine from loon.web.db import engine
from loon.web.users.models import User from loon.web.users.models import User
@@ -63,9 +63,9 @@ class JWTAuthBackend(AuthenticationBackend):
raise AuthenticationError("Invalid or expired token") raise AuthenticationError("Invalid or expired token")
with Session(engine) as session: with Session(engine) as session:
user = session.exec( user = session.execute(
select(User).where(User.id == user_id).options(selectinload(User.minecraft_user)) select(User).where(User.id == user_id).options(selectinload(User.minecraft_user))
).first() ).scalars().first()
if user is None: if user is None:
raise AuthenticationError("User not found") raise AuthenticationError("User not found")
+3 -2
View File
@@ -1,5 +1,6 @@
from fastapi import APIRouter, HTTPException, Request, status from fastapi import APIRouter, HTTPException, Request, status
from sqlmodel import Session, select from sqlalchemy import select
from sqlalchemy.orm import Session
from loon.web import limiter from loon.web import limiter
from loon.web.auth.middleware import authenticated from loon.web.auth.middleware import authenticated
@@ -17,7 +18,7 @@ router = APIRouter(prefix="/auth", tags=["auth"])
async def get_jwt(request: Request, body: JWTRequest) -> JWTResponse: async def get_jwt(request: Request, body: JWTRequest) -> JWTResponse:
with Session(engine) as session: with Session(engine) as session:
statement = select(User).where(User.internal_username == body.username) statement = select(User).where(User.internal_username == body.username)
user = session.exec(statement).first() user = session.execute(statement).scalars().first()
if user is None or user.id is None or not verify_password(body.password, user.password): if user is None or user.id is None or not verify_password(body.password, user.password):
raise HTTPException( raise HTTPException(
+6 -1
View File
@@ -1,8 +1,13 @@
from sqlmodel import SQLModel, create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase
from config import settings from config import settings
class Base(DeclarativeBase):
pass
def database_url(url: str | None = None) -> str: def database_url(url: str | None = None) -> str:
url = url or settings.database.url url = url or settings.database.url
for src, dst in ( for src, dst in (
+18 -11
View File
@@ -1,17 +1,24 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from sqlmodel import SQLModel, Field, Relationship from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from loon.web.db import Base
class LogType(SQLModel, table=True): class LogType(Base):
id: int = Field(default=None, primary_key=True) __tablename__ = "logtype"
code: str = Field(nullable=False, unique=True)
entries: list["LogEntry"] = Relationship(back_populates="log_type") id: Mapped[int] = mapped_column(primary_key=True)
code: Mapped[str] = mapped_column(unique=True)
entries: Mapped[list["LogEntry"]] = relationship(back_populates="log_type")
class LogEntry(SQLModel, table=True): class LogEntry(Base):
id: int = Field(default=None, primary_key=True) __tablename__ = "logentry"
log_type_id: int = Field(foreign_key="logtype.id", nullable=False)
log_type: LogType = Relationship(back_populates="entries") id: Mapped[int] = mapped_column(primary_key=True)
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) log_type_id: Mapped[int] = mapped_column(ForeignKey("logtype.id"))
message: str = Field(nullable=False) log_type: Mapped[LogType] = relationship(back_populates="entries")
created_at: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc))
message: Mapped[str]
+3 -2
View File
@@ -1,6 +1,7 @@
import asyncio import asyncio
from sqlmodel import Session, select from sqlalchemy import select
from sqlalchemy.orm import Session
from loon.web.db import engine from loon.web.db import engine
from loon.web.logs.models import LogType, LogEntry from loon.web.logs.models import LogType, LogEntry
@@ -14,7 +15,7 @@ def player_tag(uuid: str, username: str) -> str:
def _write_log(log_type: LogTypeEnum, message: str) -> None: def _write_log(log_type: LogTypeEnum, message: str) -> None:
with Session(engine) as session: with Session(engine) as session:
statement = select(LogType).where(LogType.code == log_type.value) statement = select(LogType).where(LogType.code == log_type.value)
log_type_object = session.exec(statement).first() log_type_object = session.execute(statement).scalars().first()
if not log_type_object: if not log_type_object:
log_type_object = LogType(code=log_type.value) log_type_object = LogType(code=log_type.value)
+20 -13
View File
@@ -1,22 +1,29 @@
from sqlmodel import SQLModel, Field, Relationship from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from loon.web.db import Base
class MinecraftUser(SQLModel, table=True): class MinecraftUser(Base):
id: int = Field(default=None, primary_key=True) __tablename__ = "minecraftuser"
uuid: str = Field(unique=True)
username: str id: Mapped[int] = mapped_column(primary_key=True)
user: "User" = Relationship(back_populates="minecraft_user") uuid: Mapped[str] = mapped_column(unique=True)
username: Mapped[str]
user: Mapped["User"] = relationship(back_populates="minecraft_user")
class User(SQLModel, table=True): class User(Base):
id: int = Field(default=None, primary_key=True) __tablename__ = "user"
minecraft_user_id: int = Field(foreign_key="minecraftuser.id", nullable=False, unique=True) id: Mapped[int] = mapped_column(primary_key=True)
minecraft_user: MinecraftUser = Relationship(back_populates="user")
internal_username: str = Field(unique=True) minecraft_user_id: Mapped[int] = mapped_column(ForeignKey("minecraftuser.id"), unique=True)
password: str minecraft_user: Mapped[MinecraftUser] = relationship(back_populates="user")
admin: bool = Field(default=False)
internal_username: Mapped[str] = mapped_column(unique=True)
password: Mapped[str]
admin: Mapped[bool] = mapped_column(default=False)
@property @property
def uuid(self) -> str: def uuid(self) -> str:
+4 -3
View File
@@ -1,6 +1,7 @@
import asyncio import asyncio
from sqlmodel import Session, select from sqlalchemy import select
from sqlalchemy.orm import Session
from loon.web.db import engine from loon.web.db import engine
from loon.web.users.models import MinecraftUser from loon.web.users.models import MinecraftUser
@@ -10,9 +11,9 @@ def _sync_minecraft_users(players: list[dict]) -> list[MinecraftUser]:
players_by_uuid = {player["uuid"]: player["username"] for player in players} players_by_uuid = {player["uuid"]: player["username"] for player in players}
with Session(engine) as session: with Session(engine) as session:
existing = session.exec( existing = session.execute(
select(MinecraftUser).where(MinecraftUser.uuid.in_(players_by_uuid)) select(MinecraftUser).where(MinecraftUser.uuid.in_(players_by_uuid))
).all() ).scalars().all()
existing_by_uuid = {minecraft_user.uuid: minecraft_user for minecraft_user in existing} existing_by_uuid = {minecraft_user.uuid: minecraft_user for minecraft_user in existing}
new_users = [] new_users = []
-1
View File
@@ -55,7 +55,6 @@ dependencies = [
"shellingham==1.5.4", "shellingham==1.5.4",
"slowapi>=0.1.10", "slowapi>=0.1.10",
"sqlalchemy==2.0.51", "sqlalchemy==2.0.51",
"sqlmodel==0.0.39",
"starlette==1.3.1", "starlette==1.3.1",
"typer==0.27.0", "typer==0.27.0",
"typing-extensions==4.16.0", "typing-extensions==4.16.0",
Generated
-16
View File
@@ -516,7 +516,6 @@ dependencies = [
{ name = "shellingham" }, { name = "shellingham" },
{ name = "slowapi" }, { name = "slowapi" },
{ name = "sqlalchemy" }, { name = "sqlalchemy" },
{ name = "sqlmodel" },
{ name = "starlette" }, { name = "starlette" },
{ name = "typer" }, { name = "typer" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
@@ -580,7 +579,6 @@ requires-dist = [
{ name = "shellingham", specifier = "==1.5.4" }, { name = "shellingham", specifier = "==1.5.4" },
{ name = "slowapi", specifier = ">=0.1.10" }, { name = "slowapi", specifier = ">=0.1.10" },
{ name = "sqlalchemy", specifier = "==2.0.51" }, { name = "sqlalchemy", specifier = "==2.0.51" },
{ name = "sqlmodel", specifier = "==0.0.39" },
{ name = "starlette", specifier = "==1.3.1" }, { name = "starlette", specifier = "==1.3.1" },
{ name = "typer", specifier = "==0.27.0" }, { name = "typer", specifier = "==0.27.0" },
{ name = "typing-extensions", specifier = "==4.16.0" }, { name = "typing-extensions", specifier = "==4.16.0" },
@@ -1022,20 +1020,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" }, { url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" },
] ]
[[package]]
name = "sqlmodel"
version = "0.0.39"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "sqlalchemy" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/68/ee/22a0559283c3cf6048678e787ed5d4959dcd00dedd8ba4567eeae684eeb1/sqlmodel-0.0.39.tar.gz", hash = "sha256:23d8e50a8d8ee936032ed79c55023a5d618dd6bc3c510bbf4909d1a7a605a570", size = 91057, upload-time = "2026-06-25T13:01:38.475Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cf/7d/b9813a582d4eb310be35e1fc7dfaae71207d7b62e9e53be314ebd251b53b/sqlmodel-0.0.39-py3-none-any.whl", hash = "sha256:90ebe92ce5cc11d7fff8dc7cb594790a102333c8fe7c14865254f6fc5c939795", size = 29680, upload-time = "2026-06-25T13:01:37.494Z" },
]
[[package]] [[package]]
name = "starlette" name = "starlette"
version = "1.3.1" version = "1.3.1"