refac: modernize type annotations (PEP 604 / PEP 585)

This commit is contained in:
Timothy Jaeryang Baek
2026-05-12 17:10:15 +09:00
parent a59c967d7e
commit 6d0295588e
197 changed files with 3265 additions and 3488 deletions
+94 -96
View File
@@ -1,29 +1,33 @@
from __future__ import annotations
import datetime
import time
from typing import Optional
from sqlalchemy import select, delete, update, func, or_, case, exists
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.internal.db import Base, JSONField, get_async_db_context
from open_webui.env import DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL
from open_webui.internal.db import Base, JSONField, get_async_db_context
from open_webui.utils.misc import throttle
from open_webui.utils.validate import validate_profile_image_url
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
from sqlalchemy import (
BigInteger,
JSON,
Column,
String,
BigInteger,
Boolean,
Text,
Column,
Date,
String,
Text,
case,
cast,
delete,
exists,
func,
or_,
select,
update,
)
from sqlalchemy.dialects.postgresql import JSONB
import datetime
from sqlalchemy.ext.asyncio import AsyncSession
####################
# User DB Schema
@@ -33,7 +37,7 @@ import datetime
class UserSettings(BaseModel):
ui: Optional[dict] = {}
ui: dict | None = {}
model_config = ConfigDict(extra='allow')
pass
@@ -76,29 +80,29 @@ class UserModel(BaseModel):
id: str
email: str
username: Optional[str] = None
username: str | None = None
role: str = 'pending'
name: str
profile_image_url: Optional[str] = None
profile_banner_image_url: Optional[str] = None
profile_image_url: str | None = None
profile_banner_image_url: str | None = None
bio: Optional[str] = None
gender: Optional[str] = None
date_of_birth: Optional[datetime.date] = None
timezone: Optional[str] = None
bio: str | None = None
gender: str | None = None
date_of_birth: datetime.date | None = None
timezone: str | None = None
presence_state: Optional[str] = None
status_emoji: Optional[str] = None
status_message: Optional[str] = None
status_expires_at: Optional[int] = None
presence_state: str | None = None
status_emoji: str | None = None
status_message: str | None = None
status_expires_at: int | None = None
info: Optional[dict] = None
settings: Optional[UserSettings] = None
info: dict | None = None
settings: UserSettings | None = None
oauth: Optional[dict] = None
scim: Optional[dict] = None
oauth: dict | None = None
scim: dict | None = None
last_active_at: int # timestamp in epoch
updated_at: int # timestamp in epoch
@@ -136,9 +140,9 @@ class ApiKeyModel(BaseModel):
id: str
user_id: str
key: str
data: Optional[dict] = None
expires_at: Optional[int] = None
last_used_at: Optional[int] = None
data: dict | None = None
expires_at: int | None = None
last_used_at: int | None = None
created_at: int # timestamp in epoch
updated_at: int # timestamp in epoch
@@ -153,9 +157,9 @@ class ApiKeyModel(BaseModel):
class UpdateProfileForm(BaseModel):
profile_image_url: str
name: str
bio: Optional[str] = None
gender: Optional[str] = None
date_of_birth: Optional[datetime.date] = None
bio: str | None = None
gender: str | None = None
date_of_birth: datetime.date | None = None
@field_validator('profile_image_url')
@classmethod
@@ -182,9 +186,9 @@ class UserGroupIdsListResponse(BaseModel):
class UserStatus(BaseModel):
status_emoji: Optional[str] = None
status_message: Optional[str] = None
status_expires_at: Optional[int] = None
status_emoji: str | None = None
status_message: str | None = None
status_expires_at: int | None = None
class UserInfoResponse(UserStatus):
@@ -192,8 +196,8 @@ class UserInfoResponse(UserStatus):
name: str
email: str
role: str
bio: Optional[str] = None
groups: Optional[list] = []
bio: str | None = None
groups: list | None = []
is_active: bool = False
@@ -205,7 +209,7 @@ class UserIdNameResponse(BaseModel):
class UserIdNameStatusResponse(UserStatus):
id: str
name: str
is_active: Optional[bool] = None
is_active: bool | None = None
class UserInfoListResponse(BaseModel):
@@ -239,15 +243,15 @@ class UserRoleUpdateForm(BaseModel):
class UserUpdateForm(BaseModel):
role: Optional[str] = None
name: Optional[str] = None
email: Optional[str] = None
profile_image_url: Optional[str] = None
password: Optional[str] = None
role: str | None = None
name: str | None = None
email: str | None = None
profile_image_url: str | None = None
password: str | None = None
@field_validator('profile_image_url', mode='before')
@classmethod
def check_profile_image_url(cls, v: Optional[str]) -> Optional[str]:
def check_profile_image_url(cls, v: str | None) -> str | None:
if v is None:
return v
return validate_profile_image_url(v)
@@ -261,10 +265,10 @@ class UsersTable:
email: str,
profile_image_url: str = '/user.png',
role: str = 'pending',
username: Optional[str] = None,
oauth: Optional[dict] = None,
db: Optional[AsyncSession] = None,
) -> Optional[UserModel]:
username: str | None = None,
oauth: dict | None = None,
db: AsyncSession | None = None,
) -> UserModel | None:
async with get_async_db_context(db) as db:
user = UserModel(
**{
@@ -289,7 +293,7 @@ class UsersTable:
else:
return None
async def get_user_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[UserModel]:
async def get_user_by_id(self, id: str, db: AsyncSession | None = None) -> UserModel | None:
try:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).filter_by(id=id))
@@ -298,7 +302,7 @@ class UsersTable:
except Exception:
return None
async def get_user_by_api_key(self, api_key: str, db: Optional[AsyncSession] = None) -> Optional[UserModel]:
async def get_user_by_api_key(self, api_key: str, db: AsyncSession | None = None) -> UserModel | None:
try:
async with get_async_db_context(db) as db:
result = await db.execute(
@@ -309,7 +313,7 @@ class UsersTable:
except Exception:
return None
async def get_user_by_email(self, email: str, db: Optional[AsyncSession] = None) -> Optional[UserModel]:
async def get_user_by_email(self, email: str, db: AsyncSession | None = None) -> UserModel | None:
try:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).filter(func.lower(User.email) == email.lower()))
@@ -318,9 +322,7 @@ class UsersTable:
except Exception:
return None
async def get_user_by_oauth_sub(
self, provider: str, sub: str, db: Optional[AsyncSession] = None
) -> Optional[UserModel]:
async def get_user_by_oauth_sub(self, provider: str, sub: str, db: AsyncSession | None = None) -> UserModel | None:
try:
async with get_async_db_context(db) as db:
dialect_name = db.bind.dialect.name
@@ -339,8 +341,8 @@ class UsersTable:
return None
async def get_user_by_scim_external_id(
self, provider: str, external_id: str, db: Optional[AsyncSession] = None
) -> Optional[UserModel]:
self, provider: str, external_id: str, db: AsyncSession | None = None
) -> UserModel | None:
try:
async with get_async_db_context(db) as db:
dialect_name = db.bind.dialect.name
@@ -359,15 +361,15 @@ class UsersTable:
async def get_users(
self,
filter: Optional[dict] = None,
skip: Optional[int] = None,
limit: Optional[int] = None,
db: Optional[AsyncSession] = None,
filter: dict | None = None,
skip: int | None = None,
limit: int | None = None,
db: AsyncSession | None = None,
) -> dict:
async with get_async_db_context(db) as db:
# Import here to avoid circular imports
from open_webui.models.groups import GroupMember
from open_webui.models.channels import ChannelMember
from open_webui.models.groups import GroupMember
# Join GroupMember so we can order by group_id when requested
stmt = select(User)
@@ -501,7 +503,7 @@ class UsersTable:
'total': total,
}
async def get_users_by_group_id(self, group_id: str, db: Optional[AsyncSession] = None) -> list[UserModel]:
async def get_users_by_group_id(self, group_id: str, db: AsyncSession | None = None) -> list[UserModel]:
async with get_async_db_context(db) as db:
from open_webui.models.groups import GroupMember
@@ -511,25 +513,23 @@ class UsersTable:
users = result.scalars().all()
return [UserModel.model_validate(user) for user in users]
async def get_users_by_user_ids(
self, user_ids: list[str], db: Optional[AsyncSession] = None
) -> list[UserStatusModel]:
async def get_users_by_user_ids(self, user_ids: list[str], db: AsyncSession | None = None) -> list[UserStatusModel]:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).filter(User.id.in_(user_ids)))
users = result.scalars().all()
return [UserModel.model_validate(user) for user in users]
async def get_num_users(self, db: Optional[AsyncSession] = None) -> Optional[int]:
async def get_num_users(self, db: AsyncSession | None = None) -> int | None:
async with get_async_db_context(db) as db:
result = await db.execute(select(func.count()).select_from(User))
return result.scalar()
async def has_users(self, db: Optional[AsyncSession] = None) -> bool:
async def has_users(self, db: AsyncSession | None = None) -> bool:
async with get_async_db_context(db) as db:
result = await db.execute(select(exists(select(User))))
return result.scalar()
async def get_first_user(self, db: Optional[AsyncSession] = None) -> UserModel:
async def get_first_user(self, db: AsyncSession | None = None) -> UserModel:
try:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).order_by(User.created_at).limit(1))
@@ -538,7 +538,7 @@ class UsersTable:
except Exception:
return None
async def get_user_webhook_url_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[str]:
async def get_user_webhook_url_by_id(self, id: str, db: AsyncSession | None = None) -> str | None:
try:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).filter_by(id=id))
@@ -551,7 +551,7 @@ class UsersTable:
except Exception:
return None
async def get_num_users_active_today(self, db: Optional[AsyncSession] = None) -> Optional[int]:
async def get_num_users_active_today(self, db: AsyncSession | None = None) -> int | None:
async with get_async_db_context(db) as db:
current_timestamp = int(datetime.datetime.now().timestamp())
today_midnight_timestamp = current_timestamp - (current_timestamp % 86400)
@@ -560,9 +560,7 @@ class UsersTable:
)
return result.scalar()
async def update_user_role_by_id(
self, id: str, role: str, db: Optional[AsyncSession] = None
) -> Optional[UserModel]:
async def update_user_role_by_id(self, id: str, role: str, db: AsyncSession | None = None) -> UserModel | None:
try:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).filter_by(id=id))
@@ -577,8 +575,8 @@ class UsersTable:
return None
async def update_user_status_by_id(
self, id: str, form_data: UserStatus, db: Optional[AsyncSession] = None
) -> Optional[UserModel]:
self, id: str, form_data: UserStatus, db: AsyncSession | None = None
) -> UserModel | None:
try:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).filter_by(id=id))
@@ -594,8 +592,8 @@ class UsersTable:
return None
async def update_user_profile_image_url_by_id(
self, id: str, profile_image_url: str, db: Optional[AsyncSession] = None
) -> Optional[UserModel]:
self, id: str, profile_image_url: str, db: AsyncSession | None = None
) -> UserModel | None:
try:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).filter_by(id=id))
@@ -610,7 +608,7 @@ class UsersTable:
return None
@throttle(DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL)
async def update_last_active_by_id(self, id: str, db: Optional[AsyncSession] = None) -> None:
async def update_last_active_by_id(self, id: str, db: AsyncSession | None = None) -> None:
try:
async with get_async_db_context(db) as db:
await db.execute(update(User).filter_by(id=id).values(last_active_at=int(time.time())))
@@ -619,8 +617,8 @@ class UsersTable:
pass
async def update_user_oauth_by_id(
self, id: str, provider: str, sub: str, db: Optional[AsyncSession] = None
) -> Optional[UserModel]:
self, id: str, provider: str, sub: str, db: AsyncSession | None = None
) -> UserModel | None:
"""
Update or insert an OAuth provider/sub pair into the user's oauth JSON field.
Example resulting structure:
@@ -656,8 +654,8 @@ class UsersTable:
id: str,
provider: str,
external_id: str,
db: Optional[AsyncSession] = None,
) -> Optional[UserModel]:
db: AsyncSession | None = None,
) -> UserModel | None:
"""
Update or insert a SCIM provider/external_id pair into the user's scim JSON field.
Example resulting structure:
@@ -684,7 +682,7 @@ class UsersTable:
except Exception:
return None
async def update_user_by_id(self, id: str, updated: dict, db: Optional[AsyncSession] = None) -> Optional[UserModel]:
async def update_user_by_id(self, id: str, updated: dict, db: AsyncSession | None = None) -> UserModel | None:
try:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).filter_by(id=id))
@@ -701,8 +699,8 @@ class UsersTable:
return None
async def update_user_settings_by_id(
self, id: str, updated: dict, db: Optional[AsyncSession] = None
) -> Optional[UserModel]:
self, id: str, updated: dict, db: AsyncSession | None = None
) -> UserModel | None:
try:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).filter_by(id=id))
@@ -726,10 +724,10 @@ class UsersTable:
except Exception:
return None
async def delete_user_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool:
async def delete_user_by_id(self, id: str, db: AsyncSession | None = None) -> bool:
try:
from open_webui.models.groups import Groups
from open_webui.models.chats import Chats
from open_webui.models.groups import Groups
# Remove User from Groups
await Groups.remove_user_from_all_groups(id)
@@ -748,7 +746,7 @@ class UsersTable:
except Exception:
return False
async def get_user_api_key_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[str]:
async def get_user_api_key_by_id(self, id: str, db: AsyncSession | None = None) -> str | None:
try:
async with get_async_db_context(db) as db:
result = await db.execute(select(ApiKey).filter_by(user_id=id))
@@ -757,7 +755,7 @@ class UsersTable:
except Exception:
return None
async def update_user_api_key_by_id(self, id: str, api_key: str, db: Optional[AsyncSession] = None) -> bool:
async def update_user_api_key_by_id(self, id: str, api_key: str, db: AsyncSession | None = None) -> bool:
try:
async with get_async_db_context(db) as db:
await db.execute(delete(ApiKey).filter_by(user_id=id))
@@ -779,7 +777,7 @@ class UsersTable:
except Exception:
return False
async def delete_user_api_key_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool:
async def delete_user_api_key_by_id(self, id: str, db: AsyncSession | None = None) -> bool:
try:
async with get_async_db_context(db) as db:
await db.execute(delete(ApiKey).filter_by(user_id=id))
@@ -788,13 +786,13 @@ class UsersTable:
except Exception:
return False
async def get_valid_user_ids(self, user_ids: list[str], db: Optional[AsyncSession] = None) -> list[str]:
async def get_valid_user_ids(self, user_ids: list[str], db: AsyncSession | None = None) -> list[str]:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).filter(User.id.in_(user_ids)))
users = result.scalars().all()
return [user.id for user in users]
async def get_super_admin_user(self, db: Optional[AsyncSession] = None) -> Optional[UserModel]:
async def get_super_admin_user(self, db: AsyncSession | None = None) -> UserModel | None:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).filter_by(role='admin').limit(1))
user = result.scalars().first()
@@ -803,7 +801,7 @@ class UsersTable:
else:
return None
async def get_active_user_count(self, db: Optional[AsyncSession] = None) -> int:
async def get_active_user_count(self, db: AsyncSession | None = None) -> int:
async with get_async_db_context(db) as db:
# Consider user active if last_active_at within the last 3 minutes
three_minutes_ago = int(time.time()) - 180
@@ -820,7 +818,7 @@ class UsersTable:
return user.last_active_at >= three_minutes_ago
return False
async def is_user_active(self, user_id: str, db: Optional[AsyncSession] = None) -> bool:
async def is_user_active(self, user_id: str, db: AsyncSession | None = None) -> bool:
async with get_async_db_context(db) as db:
result = await db.execute(select(User).filter_by(id=user_id))
user = result.scalars().first()