refac: async db

This commit is contained in:
Timothy Jaeryang Baek
2026-04-12 14:22:11 -05:00
parent b618d84065
commit 27169124f2
74 changed files with 4831 additions and 4479 deletions
+29 -29
View File
@@ -11,8 +11,8 @@ from open_webui.models.groups import Groups
from open_webui.models.users import Users
from open_webui.models.feedbacks import Feedbacks
from open_webui.utils.auth import get_admin_user
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -59,10 +59,10 @@ async def get_model_analytics(
end_date: Optional[int] = Query(None, description='End timestamp (epoch)'),
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get message counts per model."""
counts = ChatMessages.get_message_count_by_model(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
counts = await ChatMessages.get_message_count_by_model(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
models = [
ModelAnalyticsEntry(model_id=model_id, count=count)
for model_id, count in sorted(counts.items(), key=lambda x: -x[1])
@@ -77,17 +77,17 @@ async def get_user_analytics(
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
limit: int = Query(50, description='Max users to return'),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get message counts and token usage per user with user info."""
counts = ChatMessages.get_message_count_by_user(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
token_usage = ChatMessages.get_token_usage_by_user(
counts = await ChatMessages.get_message_count_by_user(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
token_usage = await ChatMessages.get_token_usage_by_user(
start_date=start_date, end_date=end_date, group_id=group_id, db=db
)
# Get user info for top users
top_user_ids = [uid for uid, _ in sorted(counts.items(), key=lambda x: -x[1])[:limit]]
user_info = {u.id: u for u in Users.get_users_by_user_ids(top_user_ids, db=db)}
user_info = {u.id: u for u in await Users.get_users_by_user_ids(top_user_ids, db=db)}
users = []
for user_id in top_user_ids:
@@ -118,13 +118,13 @@ async def get_messages(
skip: int = Query(0),
limit: int = Query(50, le=100),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Query messages with filters."""
if chat_id:
return ChatMessages.get_messages_by_chat_id(chat_id=chat_id, db=db)
return await ChatMessages.get_messages_by_chat_id(chat_id=chat_id, db=db)
elif model_id:
return ChatMessages.get_messages_by_model_id(
return await ChatMessages.get_messages_by_model_id(
model_id=model_id,
start_date=start_date,
end_date=end_date,
@@ -133,7 +133,7 @@ async def get_messages(
db=db,
)
elif user_id:
return ChatMessages.get_messages_by_user_id(user_id=user_id, skip=skip, limit=limit, db=db)
return await ChatMessages.get_messages_by_user_id(user_id=user_id, skip=skip, limit=limit, db=db)
else:
# Return empty if no filter specified
return []
@@ -152,16 +152,16 @@ async def get_summary(
end_date: Optional[int] = Query(None, description='End timestamp (epoch)'),
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get summary statistics for the dashboard."""
model_counts = ChatMessages.get_message_count_by_model(
model_counts = await ChatMessages.get_message_count_by_model(
start_date=start_date, end_date=end_date, group_id=group_id, db=db
)
user_counts = ChatMessages.get_message_count_by_user(
user_counts = await ChatMessages.get_message_count_by_user(
start_date=start_date, end_date=end_date, group_id=group_id, db=db
)
chat_counts = ChatMessages.get_message_count_by_chat(
chat_counts = await ChatMessages.get_message_count_by_chat(
start_date=start_date, end_date=end_date, group_id=group_id, db=db
)
@@ -189,13 +189,13 @@ async def get_daily_stats(
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
granularity: str = Query('daily', description="Granularity: 'hourly' or 'daily'"),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get message counts grouped by model for time-series chart."""
if granularity == 'hourly':
counts = ChatMessages.get_hourly_message_counts_by_model(start_date=start_date, end_date=end_date, db=db)
counts = await ChatMessages.get_hourly_message_counts_by_model(start_date=start_date, end_date=end_date, db=db)
else:
counts = ChatMessages.get_daily_message_counts_by_model(
counts = await ChatMessages.get_daily_message_counts_by_model(
start_date=start_date, end_date=end_date, group_id=group_id, db=db
)
return DailyStatsResponse(
@@ -224,10 +224,10 @@ async def get_token_usage(
end_date: Optional[int] = Query(None),
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get token usage aggregated by model."""
usage = ChatMessages.get_token_usage_by_model(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
usage = await ChatMessages.get_token_usage_by_model(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
models = [
TokenUsageEntry(model_id=model_id, **data)
@@ -271,12 +271,12 @@ async def get_model_chats(
skip: int = Query(0),
limit: int = Query(50, le=100),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get chats that used a specific model, with preview and feedback info."""
# Get chat IDs that used this model
chat_ids = ChatMessages.get_chat_ids_by_model_id(
chat_ids = await ChatMessages.get_chat_ids_by_model_id(
model_id=model_id,
start_date=start_date,
end_date=end_date,
@@ -291,7 +291,7 @@ async def get_model_chats(
# Get chat details from messages only
chats_data = []
for chat_id in chat_ids:
messages = ChatMessages.get_messages_by_chat_id(chat_id, db=db)
messages = await ChatMessages.get_messages_by_chat_id(chat_id, db=db)
if not messages:
continue
@@ -312,7 +312,7 @@ async def get_model_chats(
# Get user info
user_name = None
if user_id:
user_info = Users.get_user_by_id(user_id, db=db)
user_info = await Users.get_user_by_id(user_id, db=db)
user_name = user_info.name if user_info else None
# Timestamps from messages
@@ -357,12 +357,12 @@ async def get_model_overview(
model_id: str,
days: int = Query(30, description='Number of days of history (0 for all)'),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get model overview with feedback history and chat tags."""
# Get chat IDs that used this model
chat_ids = ChatMessages.get_chat_ids_by_model_id(
chat_ids = await ChatMessages.get_chat_ids_by_model_id(
model_id=model_id,
start_date=None,
end_date=None,
@@ -381,7 +381,7 @@ async def get_model_overview(
start_dt = now - timedelta(days=days)
for chat_id in chat_ids:
feedbacks = Feedbacks.get_feedbacks_by_chat_id(chat_id, db=db)
feedbacks = await Feedbacks.get_feedbacks_by_chat_id(chat_id, db=db)
for fb in feedbacks:
if fb.data and 'rating' in fb.data:
rating = fb.data['rating']
@@ -425,7 +425,7 @@ async def get_model_overview(
# Get chat tags
tag_counts: dict[str, int] = defaultdict(int)
for chat_id in chat_ids:
chat = Chats.get_chat_by_id(chat_id, db=db)
chat = await Chats.get_chat_by_id(chat_id, db=db)
if chat and chat.meta:
for tag in chat.meta.get('tags', []):
tag_counts[tag] += 1
+3 -3
View File
@@ -330,7 +330,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
detail=ERROR_MESSAGES.NOT_FOUND,
)
if user.role != 'admin' and not has_permission(user.id, 'chat.tts', request.app.state.config.USER_PERMISSIONS):
if user.role != 'admin' and not await has_permission(user.id, 'chat.tts', request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@@ -1208,13 +1208,13 @@ def split_audio(file_path, max_bytes, format='mp3', bitrate='32k'):
@router.post('/transcriptions')
def transcription(
async def transcription(
request: Request,
file: UploadFile = File(...),
language: Optional[str] = Form(None),
user=Depends(get_verified_user),
):
if user.role != 'admin' and not has_permission(user.id, 'chat.stt', request.app.state.config.USER_PERMISSIONS):
if user.role != 'admin' and not await has_permission(user.id, 'chat.stt', request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+66 -66
View File
@@ -70,8 +70,8 @@ from open_webui.utils.auth import (
get_password_hash,
get_http_authorization_cred,
)
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.utils.webhook import post_webhook
from open_webui.utils.access_control import get_permissions, has_permission
from open_webui.utils.groups import apply_default_group_assignment
@@ -96,7 +96,7 @@ log = logging.getLogger(__name__)
signin_rate_limiter = RateLimiter(redis_client=get_redis_client(), limit=5 * 3, window=60 * 3)
def create_session_response(request: Request, user, db, response: Response = None, set_cookie: bool = False) -> dict:
async def create_session_response(request: Request, user, db, response: Response = None, set_cookie: bool = False) -> dict:
"""
Create JWT token and build session response for a user.
Shared helper for signin, signup, ldap_auth, add_user, and token_exchange endpoints.
@@ -131,7 +131,7 @@ def create_session_response(request: Request, user, db, response: Response = Non
**({'max_age': max_age} if max_age is not None else {}),
)
user_permissions = get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
user_permissions = await get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
return {
'token': token,
@@ -167,7 +167,7 @@ async def get_session_user(
request: Request,
response: Response,
user=Depends(get_current_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
auth_header = request.headers.get('Authorization')
auth_token = get_http_authorization_cred(auth_header)
@@ -197,7 +197,7 @@ async def get_session_user(
**({'max_age': max_age} if max_age is not None else {}),
)
user_permissions = get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
user_permissions = await get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
return {
'token': token,
@@ -227,10 +227,10 @@ async def get_session_user(
async def update_profile(
form_data: UpdateProfileForm,
session_user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if session_user:
user = Users.update_user_by_id(
user = await Users.update_user_by_id(
session_user.id,
form_data.model_dump(),
db=db,
@@ -256,10 +256,10 @@ class UpdateTimezoneForm(BaseModel):
async def update_timezone(
form_data: UpdateTimezoneForm,
session_user=Depends(get_current_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if session_user:
Users.update_user_by_id(
await Users.update_user_by_id(
session_user.id,
{'timezone': form_data.timezone},
db=db,
@@ -278,12 +278,12 @@ async def update_timezone(
async def update_password(
form_data: UpdatePasswordForm,
session_user=Depends(get_current_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
raise HTTPException(400, detail=ERROR_MESSAGES.ACTION_PROHIBITED)
if session_user:
user = Auths.authenticate_user(
user = await Auths.authenticate_user(
session_user.email,
lambda pw: verify_password(form_data.password, pw),
db=db,
@@ -295,7 +295,7 @@ async def update_password(
except Exception as e:
raise HTTPException(400, detail=str(e))
hashed = get_password_hash(form_data.new_password)
return Auths.update_user_password_by_id(user.id, hashed, db=db)
return await Auths.update_user_password_by_id(user.id, hashed, db=db)
else:
raise HTTPException(400, detail=ERROR_MESSAGES.INCORRECT_PASSWORD)
else:
@@ -310,7 +310,7 @@ async def ldap_auth(
request: Request,
response: Response,
form_data: LdapForm,
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
# Security checks FIRST - before loading any config
if not request.app.state.config.ENABLE_LDAP:
@@ -476,12 +476,12 @@ async def ldap_auth(
if not await asyncio.to_thread(connection_user.bind):
raise HTTPException(400, 'Authentication failed.')
user = Users.get_user_by_email(email, db=db)
user = await Users.get_user_by_email(email, db=db)
if not user:
try:
# Insert with default role first to avoid TOCTOU race on
# first-user registration. Matches signup_handler pattern.
user = Auths.insert_new_auth(
user = await Auths.insert_new_auth(
email=email,
password=str(uuid.uuid4()),
name=cn,
@@ -494,11 +494,11 @@ async def ldap_auth(
# Atomically check if this is the only user *after* the
# insert. Only the single user present should become admin.
if Users.get_num_users(db=db) == 1:
Users.update_user_role_by_id(user.id, 'admin', db=db)
user = Users.get_user_by_id(user.id, db=db)
if await Users.get_num_users(db=db) == 1:
await Users.update_user_role_by_id(user.id, 'admin', db=db)
user = await Users.get_user_by_id(user.id, db=db)
apply_default_group_assignment(
await apply_default_group_assignment(
request.app.state.config.DEFAULT_GROUP_ID,
user.id,
db=db,
@@ -510,19 +510,19 @@ async def ldap_auth(
log.error(f'LDAP user creation error: {str(err)}')
raise HTTPException(500, detail='Internal error occurred during LDAP user creation.')
user = Auths.authenticate_user_by_email(email, db=db)
user = await Auths.authenticate_user_by_email(email, db=db)
if user:
if ENABLE_LDAP_GROUP_MANAGEMENT and user_groups:
if ENABLE_LDAP_GROUP_CREATION:
Groups.create_groups_by_group_names(user.id, user_groups, db=db)
await Groups.create_groups_by_group_names(user.id, user_groups, db=db)
try:
Groups.sync_groups_by_group_names(user.id, user_groups, db=db)
await Groups.sync_groups_by_group_names(user.id, user_groups, db=db)
log.info(f'Successfully synced groups for user {user.id}: {user_groups}')
except Exception as e:
log.error(f'Failed to sync groups for user {user.id}: {e}')
return create_session_response(request, user, db, response, set_cookie=True)
return await create_session_response(request, user, db, response, set_cookie=True)
else:
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
else:
@@ -542,7 +542,7 @@ async def signin(
request: Request,
response: Response,
form_data: SigninForm,
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if not ENABLE_PASSWORD_AUTH:
raise HTTPException(
@@ -564,7 +564,7 @@ async def signin(
except Exception as e:
pass
if not Users.get_user_by_email(email.lower(), db=db):
if not await Users.get_user_by_email(email.lower(), db=db):
await signup_handler(
request,
email,
@@ -573,20 +573,20 @@ async def signin(
db=db,
)
user = Auths.authenticate_user_by_email(email, db=db)
user = await Auths.authenticate_user_by_email(email, db=db)
if user:
if WEBUI_AUTH_TRUSTED_GROUPS_HEADER:
group_names = request.headers.get(WEBUI_AUTH_TRUSTED_GROUPS_HEADER, '').split(',')
group_names = [name.strip() for name in group_names if name.strip()]
if group_names:
Groups.sync_groups_by_group_names(user.id, group_names, db=db)
await Groups.sync_groups_by_group_names(user.id, group_names, db=db)
if WEBUI_AUTH_TRUSTED_ROLE_HEADER:
trusted_role = request.headers.get(WEBUI_AUTH_TRUSTED_ROLE_HEADER, '').lower().strip()
if trusted_role in {'admin', 'user', 'pending'}:
if user.role != trusted_role:
Users.update_user_role_by_id(user.id, trusted_role, db=db)
await Users.update_user_role_by_id(user.id, trusted_role, db=db)
elif trusted_role:
log.warning(f'Ignoring invalid trusted role header value: {trusted_role}')
@@ -594,14 +594,14 @@ async def signin(
admin_email = 'admin@localhost'
admin_password = 'admin'
if Users.get_user_by_email(admin_email.lower(), db=db):
user = Auths.authenticate_user(
if await Users.get_user_by_email(admin_email.lower(), db=db):
user = await Auths.authenticate_user(
admin_email.lower(),
lambda pw: verify_password(admin_password, pw),
db=db,
)
else:
if Users.has_users(db=db):
if await Users.has_users(db=db):
raise HTTPException(400, detail=ERROR_MESSAGES.EXISTING_USERS)
await signup_handler(
@@ -612,7 +612,7 @@ async def signin(
db=db,
)
user = Auths.authenticate_user(
user = await Auths.authenticate_user(
admin_email.lower(),
lambda pw: verify_password(admin_password, pw),
db=db,
@@ -633,14 +633,14 @@ async def signin(
# decode safely — ignore incomplete UTF-8 sequences
form_data.password = password_bytes.decode('utf-8', errors='ignore')
user = Auths.authenticate_user(
user = await Auths.authenticate_user(
form_data.email.lower(),
lambda pw: verify_password(form_data.password, pw),
db=db,
)
if user:
return create_session_response(request, user, db, response, set_cookie=True)
return await create_session_response(request, user, db, response, set_cookie=True)
else:
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
@@ -657,7 +657,7 @@ async def signup_handler(
name: str,
profile_image_url: str = '/user.png',
*,
db: Session,
db: AsyncSession,
) -> UserModel:
"""
Core user-creation logic shared by the signup endpoint and
@@ -671,7 +671,7 @@ async def signup_handler(
# first-user registration can all see an empty table and each get admin.
hashed = get_password_hash(password)
user = Auths.insert_new_auth(
user = await Auths.insert_new_auth(
email=email.lower(),
password=hashed,
name=name,
@@ -684,9 +684,9 @@ async def signup_handler(
# Atomically check if this is the only user *after* the insert.
# Only the single user present at this point should become admin.
if Users.get_num_users(db=db) == 1:
Users.update_user_role_by_id(user.id, 'admin', db=db)
user = Users.get_user_by_id(user.id, db=db)
if await Users.get_num_users(db=db) == 1:
await Users.update_user_role_by_id(user.id, 'admin', db=db)
user = await Users.get_user_by_id(user.id, db=db)
request.app.state.config.ENABLE_SIGNUP = False
if request.app.state.config.WEBHOOK_URL:
@@ -701,7 +701,7 @@ async def signup_handler(
},
)
apply_default_group_assignment(
await apply_default_group_assignment(
request.app.state.config.DEFAULT_GROUP_ID,
user.id,
db=db,
@@ -715,9 +715,9 @@ async def signup(
request: Request,
response: Response,
form_data: SignupForm,
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
has_users = Users.has_users(db=db)
has_users = await Users.has_users(db=db)
if WEBUI_AUTH:
if not request.app.state.config.ENABLE_SIGNUP or not request.app.state.config.ENABLE_LOGIN_FORM:
@@ -730,7 +730,7 @@ async def signup(
if not validate_email_format(form_data.email.lower()):
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT)
if Users.get_user_by_email(form_data.email.lower(), db=db):
if await Users.get_user_by_email(form_data.email.lower(), db=db):
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
try:
@@ -747,7 +747,7 @@ async def signup(
form_data.profile_image_url,
db=db,
)
return create_session_response(request, user, db, response, set_cookie=True)
return await create_session_response(request, user, db, response, set_cookie=True)
except HTTPException:
raise
except Exception as err:
@@ -756,7 +756,7 @@ async def signup(
@router.get('/signout')
async def signout(request: Request, response: Response, db: Session = Depends(get_session)):
async def signout(request: Request, response: Response, db: AsyncSession = Depends(get_async_session)):
# get auth token from headers or cookies
token = None
auth_header = request.headers.get('Authorization')
@@ -777,7 +777,7 @@ async def signout(request: Request, response: Response, db: Session = Depends(ge
if oauth_session_id:
response.delete_cookie('oauth_session_id')
session = OAuthSessions.get_session_by_id(oauth_session_id, db=db)
session = await OAuthSessions.get_session_by_id(oauth_session_id, db=db)
# If a custom end_session_endpoint is configured (e.g. AWS Cognito), redirect
# there directly instead of attempting OIDC discovery.
@@ -852,12 +852,12 @@ async def add_user(
request: Request,
form_data: AddUserForm,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if not validate_email_format(form_data.email.lower()):
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT)
if Users.get_user_by_email(form_data.email.lower(), db=db):
if await Users.get_user_by_email(form_data.email.lower(), db=db):
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
try:
@@ -867,7 +867,7 @@ async def add_user(
raise HTTPException(400, detail=str(e))
hashed = get_password_hash(form_data.password)
user = Auths.insert_new_auth(
user = await Auths.insert_new_auth(
form_data.email.lower(),
hashed,
form_data.name,
@@ -877,7 +877,7 @@ async def add_user(
)
if user:
apply_default_group_assignment(
await apply_default_group_assignment(
request.app.state.config.DEFAULT_GROUP_ID,
user.id,
db=db,
@@ -909,7 +909,7 @@ async def add_user(
@router.get('/admin/details')
async def get_admin_details(request: Request, user=Depends(get_current_user), db: Session = Depends(get_session)):
async def get_admin_details(request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)):
if request.app.state.config.SHOW_ADMIN_DETAILS:
admin_email = request.app.state.config.ADMIN_EMAIL
admin_name = None
@@ -917,11 +917,11 @@ async def get_admin_details(request: Request, user=Depends(get_current_user), db
log.info(f'Admin details - Email: {admin_email}, Name: {admin_name}')
if admin_email:
admin = Users.get_user_by_email(admin_email, db=db)
admin = await Users.get_user_by_email(admin_email, db=db)
if admin:
admin_name = admin.name
else:
admin = Users.get_first_user(db=db)
admin = await Users.get_first_user(db=db)
if admin:
admin_email = admin.email
admin_name = admin.name
@@ -1173,10 +1173,10 @@ async def update_ldap_config(request: Request, form_data: LdapConfigForm, user=D
# create api key
@router.post('/api_key', response_model=ApiKey)
async def generate_api_key(request: Request, user=Depends(get_current_user), db: Session = Depends(get_session)):
async def generate_api_key(request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)):
if not request.app.state.config.ENABLE_API_KEYS or (
user.role != 'admin'
and not has_permission(user.id, 'features.api_keys', request.app.state.config.USER_PERMISSIONS)
and not await has_permission(user.id, 'features.api_keys', request.app.state.config.USER_PERMISSIONS)
):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
@@ -1184,7 +1184,7 @@ async def generate_api_key(request: Request, user=Depends(get_current_user), db:
)
api_key = create_api_key()
success = Users.update_user_api_key_by_id(user.id, api_key, db=db)
success = await Users.update_user_api_key_by_id(user.id, api_key, db=db)
if success:
return {
@@ -1196,14 +1196,14 @@ async def generate_api_key(request: Request, user=Depends(get_current_user), db:
# delete api key
@router.delete('/api_key', response_model=bool)
async def delete_api_key(user=Depends(get_current_user), db: Session = Depends(get_session)):
return Users.delete_user_api_key_by_id(user.id, db=db)
async def delete_api_key(user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)):
return await Users.delete_user_api_key_by_id(user.id, db=db)
# get api key
@router.get('/api_key', response_model=ApiKey)
async def get_api_key(user=Depends(get_current_user), db: Session = Depends(get_session)):
api_key = Users.get_user_api_key_by_id(user.id, db=db)
async def get_api_key(user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)):
api_key = await Users.get_user_api_key_by_id(user.id, db=db)
if api_key:
return {
'api_key': api_key,
@@ -1227,7 +1227,7 @@ async def token_exchange(
response: Response,
provider: str,
form_data: TokenExchangeForm,
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""
Exchange an external OAuth provider token for an OpenWebUI JWT.
@@ -1296,14 +1296,14 @@ async def token_exchange(
email = email.lower()
# Try to find the user by OAuth sub
user = Users.get_user_by_oauth_sub(provider, sub, db=db)
user = await Users.get_user_by_oauth_sub(provider, sub, db=db)
if not user and OAUTH_MERGE_ACCOUNTS_BY_EMAIL.value:
# Try to find by email if merge is enabled
user = Users.get_user_by_email(email, db=db)
user = await Users.get_user_by_email(email, db=db)
if user:
# Link the OAuth sub to this user
Users.update_user_oauth_by_id(user.id, provider, sub, db=db)
await Users.update_user_oauth_by_id(user.id, provider, sub, db=db)
if not user:
raise HTTPException(
@@ -1311,4 +1311,4 @@ async def token_exchange(
detail='User not found. Please sign in via the web interface first.',
)
return create_session_response(request, user, db)
return await create_session_response(request, user, db)
+45 -45
View File
@@ -3,7 +3,7 @@ import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy.orm import Session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.models.automations import (
Automations,
@@ -23,7 +23,7 @@ from open_webui.utils.automations import (
)
from open_webui.utils.auth import get_verified_user, get_admin_user
from open_webui.utils.access_control import has_permission
from open_webui.internal.db import get_session
from open_webui.internal.db import get_async_session
from open_webui.constants import ERROR_MESSAGES
log = logging.getLogger(__name__)
@@ -38,8 +38,8 @@ PAGE_ITEM_COUNT = 30
############################
def check_automations_permission(request, user):
if user.role != 'admin' and not has_permission(
async def check_automations_permission(request, user):
if user.role != 'admin' and not await has_permission(
user.id, 'features.automations', request.app.state.config.USER_PERMISSIONS
):
raise HTTPException(
@@ -61,7 +61,7 @@ def check_automation_access(automation, user):
)
def check_automation_limits(request, user, rrule_str: str, db, is_create: bool = False):
async def check_automation_limits(request, user, rrule_str: str, db, is_create: bool = False):
"""Enforce global automation limits. Admins bypass all checks."""
if user.role == 'admin':
return
@@ -71,7 +71,7 @@ def check_automation_limits(request, user, rrule_str: str, db, is_create: bool =
max_count = request.app.state.config.AUTOMATION_MAX_COUNT
if max_count:
max_count = int(max_count)
if max_count > 0 and Automations.count_by_user(user.id, db=db) >= max_count:
if max_count > 0 and await Automations.count_by_user(user.id, db=db) >= max_count:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f'Automation limit reached ({max_count})',
@@ -90,9 +90,9 @@ def check_automation_limits(request, user, rrule_str: str, db, is_create: bool =
)
def enrich_automation(automation: AutomationModel, db: Session, tz: str = None) -> AutomationResponse:
async def enrich_automation(automation: AutomationModel, db: AsyncSession, tz: str = None) -> AutomationResponse:
"""Full enrichment for single-item views (includes next_runs computation)."""
last_run = AutomationRuns.get_latest(automation.id, db=db)
last_run = await AutomationRuns.get_latest(automation.id, db=db)
return AutomationResponse(
**automation.model_dump(),
last_run=last_run,
@@ -112,14 +112,14 @@ async def get_automation_items(
status: Optional[str] = None,
page: Optional[int] = 1,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
check_automations_permission(request, user)
await check_automations_permission(request, user)
limit = PAGE_ITEM_COUNT
page = max(1, page)
skip = (page - 1) * limit
result = Automations.search_automations(
result = await Automations.search_automations(
user_id=user.id,
query=query,
status=status,
@@ -130,7 +130,7 @@ async def get_automation_items(
# Batch-fetch latest runs in a single query instead of N+1
ids = [item.id for item in result.items]
latest_runs = AutomationRuns.get_latest_batch(ids, db=db) if ids else {}
latest_runs = await AutomationRuns.get_latest_batch(ids, db=db) if ids else {}
return {
'items': [
@@ -154,9 +154,9 @@ async def create_new_automation(
request: Request,
form_data: AutomationForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
check_automations_permission(request, user)
await check_automations_permission(request, user)
try:
validate_rrule(form_data.data.rrule)
except ValueError as e:
@@ -165,7 +165,7 @@ async def create_new_automation(
detail=str(e),
)
check_automation_limits(request, user, form_data.data.rrule, db, is_create=True)
await check_automation_limits(request, user, form_data.data.rrule, db, is_create=True)
# Validate terminal server exists if linked
if form_data.data.terminal and form_data.data.terminal.server_id:
@@ -177,8 +177,8 @@ async def create_new_automation(
)
tz = user.timezone
automation = Automations.insert(user.id, form_data, next_run_ns(form_data.data.rrule, tz=tz), db=db)
return enrich_automation(automation, db, tz=tz)
automation = await Automations.insert(user.id, form_data, next_run_ns(form_data.data.rrule, tz=tz), db=db)
return await enrich_automation(automation, db, tz=tz)
############################
@@ -191,12 +191,12 @@ async def get_automation_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
check_automations_permission(request, user)
automation = Automations.get_by_id(id, db=db)
await check_automations_permission(request, user)
automation = await Automations.get_by_id(id, db=db)
check_automation_access(automation, user)
return enrich_automation(automation, db, tz=user.timezone)
return await enrich_automation(automation, db, tz=user.timezone)
############################
@@ -210,10 +210,10 @@ async def update_automation_by_id(
id: str,
form_data: AutomationForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
check_automations_permission(request, user)
automation = Automations.get_by_id(id, db=db)
await check_automations_permission(request, user)
automation = await Automations.get_by_id(id, db=db)
check_automation_access(automation, user)
try:
@@ -224,7 +224,7 @@ async def update_automation_by_id(
detail=str(e),
)
check_automation_limits(request, user, form_data.data.rrule, db, is_create=False)
await check_automation_limits(request, user, form_data.data.rrule, db, is_create=False)
# Validate terminal server exists if linked
if form_data.data.terminal and form_data.data.terminal.server_id:
@@ -236,8 +236,8 @@ async def update_automation_by_id(
)
tz = user.timezone
updated = Automations.update_by_id(id, form_data, next_run_ns(form_data.data.rrule, tz=tz), db=db)
return enrich_automation(updated, db, tz=tz)
updated = await Automations.update_by_id(id, form_data, next_run_ns(form_data.data.rrule, tz=tz), db=db)
return await enrich_automation(updated, db, tz=tz)
############################
@@ -250,13 +250,13 @@ async def toggle_automation_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
check_automations_permission(request, user)
automation = Automations.get_by_id(id, db=db)
await check_automations_permission(request, user)
automation = await Automations.get_by_id(id, db=db)
check_automation_access(automation, user)
toggled = Automations.toggle(id, next_run_ns(automation.data['rrule'], tz=user.timezone), db=db)
return enrich_automation(toggled, db, tz=user.timezone)
toggled = await Automations.toggle(id, next_run_ns(automation.data['rrule'], tz=user.timezone), db=db)
return await enrich_automation(toggled, db, tz=user.timezone)
############################
@@ -269,13 +269,13 @@ async def run_automation_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
check_automations_permission(request, user)
automation = Automations.get_by_id(id, db=db)
await check_automations_permission(request, user)
automation = await Automations.get_by_id(id, db=db)
check_automation_access(automation, user)
asyncio.create_task(execute_automation(request.app, automation))
return enrich_automation(automation, db, tz=user.timezone)
return await enrich_automation(automation, db, tz=user.timezone)
############################
@@ -288,13 +288,13 @@ async def delete_automation_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
check_automations_permission(request, user)
automation = Automations.get_by_id(id, db=db)
await check_automations_permission(request, user)
automation = await Automations.get_by_id(id, db=db)
check_automation_access(automation, user)
AutomationRuns.delete_by_automation(id, db=db)
return Automations.delete(id, db=db)
await AutomationRuns.delete_by_automation(id, db=db)
return await Automations.delete(id, db=db)
############################
@@ -309,9 +309,9 @@ async def get_automation_runs(
skip: int = 0,
limit: int = 50,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
check_automations_permission(request, user)
automation = Automations.get_by_id(id, db=db)
await check_automations_permission(request, user)
automation = await Automations.get_by_id(id, db=db)
check_automation_access(automation, user)
return AutomationRuns.get_by_automation(id, skip=skip, limit=limit, db=db)
return await AutomationRuns.get_by_automation(id, skip=skip, limit=limit, db=db)
File diff suppressed because it is too large Load Diff
+132 -132
View File
@@ -1,7 +1,7 @@
import json
import logging
from typing import Optional
from sqlalchemy.orm import Session
from sqlalchemy.ext.asyncio import AsyncSession
import asyncio
from fastapi.responses import StreamingResponse
@@ -25,7 +25,7 @@ from open_webui.models.chats import (
)
from open_webui.models.tags import TagModel, Tags
from open_webui.models.folders import Folders
from open_webui.internal.db import get_session
from open_webui.internal.db import get_async_session
from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
from open_webui.constants import ERROR_MESSAGES
@@ -49,19 +49,19 @@ router = APIRouter()
@router.get('/', response_model=list[ChatTitleIdResponse])
@router.get('/list', response_model=list[ChatTitleIdResponse])
def get_session_user_chat_list(
async def get_session_user_chat_list(
user=Depends(get_verified_user),
page: Optional[int] = None,
include_pinned: Optional[bool] = False,
include_folders: Optional[bool] = False,
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
try:
if page is not None:
limit = 60
skip = (page - 1) * limit
return Chats.get_chat_title_id_list_by_user_id(
return await Chats.get_chat_title_id_list_by_user_id(
user.id,
include_folders=include_folders,
include_pinned=include_pinned,
@@ -70,7 +70,7 @@ def get_session_user_chat_list(
db=db,
)
else:
return Chats.get_chat_title_id_list_by_user_id(
return await Chats.get_chat_title_id_list_by_user_id(
user.id,
include_folders=include_folders,
include_pinned=include_pinned,
@@ -88,17 +88,17 @@ def get_session_user_chat_list(
@router.get('/stats/usage', response_model=ChatUsageStatsListResponse)
def get_session_user_chat_usage_stats(
async def get_session_user_chat_usage_stats(
items_per_page: Optional[int] = 50,
page: Optional[int] = 1,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
try:
limit = items_per_page
skip = (page - 1) * limit
result = Chats.get_chats_by_user_id(user.id, skip=skip, limit=limit, db=db)
result = await Chats.get_chats_by_user_id(user.id, skip=skip, limit=limit, db=db)
chats = result.items
total = result.total
@@ -332,11 +332,11 @@ def _process_chat_for_export(chat) -> Optional[ChatStatsExport]:
return None
def calculate_chat_stats(user_id, skip=0, limit=10, filter=None):
async def calculate_chat_stats(user_id, skip=0, limit=10, filter=None):
if filter is None:
filter = {}
result = Chats.get_chats_by_user_id(
result = await Chats.get_chats_by_user_id(
user_id,
skip=skip,
limit=limit,
@@ -352,12 +352,12 @@ def calculate_chat_stats(user_id, skip=0, limit=10, filter=None):
return chat_stats_export_list, result.total
def generate_chat_stats_jsonl_generator(user_id, filter):
async def generate_chat_stats_jsonl_generator(user_id, filter):
"""
Synchronous generator for streaming chat stats export.
Async generator for streaming chat stats export.
NOTE: We intentionally do NOT pass a shared db session here. Instead, we let
each batch create its own short-lived session via get_db_context(None).
each batch create its own short-lived session via get_async_db_context(None).
This is critical for SQLite in low-resource environments because:
1. SQLite uses file-level locking
2. Holding a session open for the entire streaming duration blocks other requests
@@ -368,12 +368,12 @@ def generate_chat_stats_jsonl_generator(user_id, filter):
while True:
# Each batch gets its own session that closes after the query
result = Chats.get_chats_by_user_id(
result = await Chats.get_chats_by_user_id(
user_id,
filter=filter,
skip=skip,
limit=limit,
db=None, # Let get_db_context create a fresh session per batch
db=None, # Let get_async_db_context create a fresh session per batch
)
if not result.items:
break
@@ -421,7 +421,7 @@ async def export_chat_stats(
limit = CHAT_EXPORT_PAGE_ITEM_COUNT
skip = (page - 1) * limit
chat_stats_export_list, total = await asyncio.to_thread(calculate_chat_stats, user.id, skip, limit, filter)
chat_stats_export_list, total = await calculate_chat_stats(user.id, skip, limit, filter)
return ChatStatsExportList(items=chat_stats_export_list, total=total, page=page)
@@ -440,7 +440,7 @@ async def export_single_chat_stats(
request: Request,
chat_id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""
Export stats for exactly one chat by ID.
@@ -454,7 +454,7 @@ async def export_single_chat_stats(
)
try:
chat = Chats.get_chat_by_id(chat_id, db=db)
chat = await Chats.get_chat_by_id(chat_id, db=db)
if not chat:
raise HTTPException(
@@ -469,8 +469,8 @@ async def export_single_chat_stats(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
# Process the chat for export
chat_stats = await asyncio.to_thread(_process_chat_for_export, chat)
# Process the chat for export (pure computation, no DB)
chat_stats = _process_chat_for_export(chat)
if not chat_stats:
raise HTTPException(
@@ -491,15 +491,15 @@ async def export_single_chat_stats(
async def delete_all_user_chats(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role == 'user' and not has_permission(user.id, 'chat.delete', request.app.state.config.USER_PERMISSIONS):
if user.role == 'user' and not await has_permission(user.id, 'chat.delete', request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
result = Chats.delete_chats_by_user_id(user.id, db=db)
result = await Chats.delete_chats_by_user_id(user.id, db=db)
return result
@@ -516,7 +516,7 @@ async def get_user_chat_list_by_user_id(
order_by: Optional[str] = None,
direction: Optional[str] = None,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if not ENABLE_ADMIN_CHAT_ACCESS:
raise HTTPException(
@@ -538,7 +538,7 @@ async def get_user_chat_list_by_user_id(
if direction:
filter['direction'] = direction
return Chats.get_chat_list_by_user_id(user_id, include_archived=True, filter=filter, skip=skip, limit=limit, db=db)
return await Chats.get_chat_list_by_user_id(user_id, include_archived=True, filter=filter, skip=skip, limit=limit, db=db)
############################
@@ -550,10 +550,10 @@ async def get_user_chat_list_by_user_id(
async def create_new_chat(
form_data: ChatForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
try:
chat = Chats.insert_new_chat(user.id, form_data, db=db)
chat = await Chats.insert_new_chat(user.id, form_data, db=db)
return ChatResponse(**chat.model_dump())
except Exception as e:
log.exception(e)
@@ -569,10 +569,10 @@ async def create_new_chat(
async def import_chats(
form_data: ChatsImportForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
try:
chats = Chats.import_chats(user.id, form_data.chats, db=db)
chats = await Chats.import_chats(user.id, form_data.chats, db=db)
return chats
except Exception as e:
log.exception(e)
@@ -585,11 +585,11 @@ async def import_chats(
@router.get('/search', response_model=list[ChatTitleIdResponse])
def search_user_chats(
async def search_user_chats(
text: str,
page: Optional[int] = None,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if page is None:
page = 1
@@ -599,7 +599,7 @@ def search_user_chats(
chat_list = [
ChatTitleIdResponse(**chat.model_dump())
for chat in Chats.get_chats_by_user_id_and_search_text(user.id, text, skip=skip, limit=limit, db=db)
for chat in await Chats.get_chats_by_user_id_and_search_text(user.id, text, skip=skip, limit=limit, db=db)
]
# Delete tag if no chat is found
@@ -607,9 +607,9 @@ def search_user_chats(
if page == 1 and len(words) == 1 and words[0].startswith('tag:'):
tag_id = words[0].replace('tag:', '')
if len(chat_list) == 0:
if Tags.get_tag_by_name_and_user_id(tag_id, user.id, db=db):
if await Tags.get_tag_by_name_and_user_id(tag_id, user.id, db=db):
log.debug(f'deleting tag: {tag_id}')
Tags.delete_tag_by_name_and_user_id(tag_id, user.id, db=db)
await Tags.delete_tag_by_name_and_user_id(tag_id, user.id, db=db)
return chat_list
@@ -620,15 +620,15 @@ def search_user_chats(
@router.get('/folder/{folder_id}', response_model=list[ChatResponse])
async def get_chats_by_folder_id(folder_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def get_chats_by_folder_id(folder_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
folder_ids = [folder_id]
children_folders = Folders.get_children_folders_by_id_and_user_id(folder_id, user.id, db=db)
children_folders = await Folders.get_children_folders_by_id_and_user_id(folder_id, user.id, db=db)
if children_folders:
folder_ids.extend([folder.id for folder in children_folders])
return [
ChatResponse(**chat.model_dump())
for chat in Chats.get_chats_by_folder_ids_and_user_id(folder_ids, user.id, db=db)
for chat in await Chats.get_chats_by_folder_ids_and_user_id(folder_ids, user.id, db=db)
]
@@ -637,13 +637,13 @@ async def get_chat_list_by_folder_id(
folder_id: str,
page: Optional[int] = 1,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
try:
limit = 10
skip = (page - 1) * limit
chats = Chats.get_chats_by_folder_id_and_user_id(folder_id, user.id, skip=skip, limit=limit, db=db)
chats = await Chats.get_chats_by_folder_id_and_user_id(folder_id, user.id, skip=skip, limit=limit, db=db)
return [
{'title': chat.title, 'id': chat.id, 'updated_at': chat.updated_at, 'last_read_at': chat.last_read_at}
for chat in chats
@@ -660,8 +660,8 @@ async def get_chat_list_by_folder_id(
@router.get('/pinned', response_model=list[ChatTitleIdResponse])
async def get_user_pinned_chats(user=Depends(get_verified_user), db: Session = Depends(get_session)):
return Chats.get_pinned_chats_by_user_id(user.id, db=db)
async def get_user_pinned_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
return await Chats.get_pinned_chats_by_user_id(user.id, db=db)
############################
@@ -670,8 +670,8 @@ async def get_user_pinned_chats(user=Depends(get_verified_user), db: Session = D
@router.get('/all', response_model=list[ChatResponse])
async def get_user_chats(user=Depends(get_verified_user), db: Session = Depends(get_session)):
result = Chats.get_chats_by_user_id(user.id, db=db)
async def get_user_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
result = await Chats.get_chats_by_user_id(user.id, db=db)
return [ChatResponse(**chat.model_dump()) for chat in result.items]
@@ -681,8 +681,8 @@ async def get_user_chats(user=Depends(get_verified_user), db: Session = Depends(
@router.get('/all/archived', response_model=list[ChatResponse])
async def get_user_archived_chats(user=Depends(get_verified_user), db: Session = Depends(get_session)):
return [ChatResponse(**chat.model_dump()) for chat in Chats.get_archived_chats_by_user_id(user.id, db=db)]
async def get_user_archived_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
return [ChatResponse(**chat.model_dump()) for chat in await Chats.get_archived_chats_by_user_id(user.id, db=db)]
############################
@@ -691,9 +691,9 @@ async def get_user_archived_chats(user=Depends(get_verified_user), db: Session =
@router.get('/all/tags', response_model=list[TagModel])
async def get_all_user_tags(user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def get_all_user_tags(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
try:
tags = Tags.get_tags_by_user_id(user.id, db=db)
tags = await Tags.get_tags_by_user_id(user.id, db=db)
return tags
except Exception as e:
log.exception(e)
@@ -706,13 +706,13 @@ async def get_all_user_tags(user=Depends(get_verified_user), db: Session = Depen
@router.get('/all/db', response_model=list[ChatResponse])
async def get_all_user_chats_in_db(user=Depends(get_admin_user), db: Session = Depends(get_session)):
async def get_all_user_chats_in_db(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
if not ENABLE_ADMIN_EXPORT:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
return [ChatResponse(**chat.model_dump()) for chat in Chats.get_chats(db=db)]
return [ChatResponse(**chat.model_dump()) for chat in await Chats.get_chats(db=db)]
############################
@@ -727,7 +727,7 @@ async def get_archived_session_user_chat_list(
order_by: Optional[str] = None,
direction: Optional[str] = None,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if page is None:
page = 1
@@ -743,7 +743,7 @@ async def get_archived_session_user_chat_list(
if direction:
filter['direction'] = direction
return Chats.get_archived_chat_list_by_user_id(
return await Chats.get_archived_chat_list_by_user_id(
user.id,
filter=filter,
skip=skip,
@@ -758,8 +758,8 @@ async def get_archived_session_user_chat_list(
@router.post('/archive/all', response_model=bool)
async def archive_all_chats(user=Depends(get_verified_user), db: Session = Depends(get_session)):
return Chats.archive_all_chats_by_user_id(user.id, db=db)
async def archive_all_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
return await Chats.archive_all_chats_by_user_id(user.id, db=db)
############################
@@ -768,8 +768,8 @@ async def archive_all_chats(user=Depends(get_verified_user), db: Session = Depen
@router.post('/unarchive/all', response_model=bool)
async def unarchive_all_chats(user=Depends(get_verified_user), db: Session = Depends(get_session)):
return Chats.unarchive_all_chats_by_user_id(user.id, db=db)
async def unarchive_all_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
return await Chats.unarchive_all_chats_by_user_id(user.id, db=db)
############################
@@ -784,7 +784,7 @@ async def get_shared_session_user_chat_list(
order_by: Optional[str] = None,
direction: Optional[str] = None,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if page is None:
page = 1
@@ -800,7 +800,7 @@ async def get_shared_session_user_chat_list(
if direction:
filter['direction'] = direction
return Chats.get_shared_chat_list_by_user_id(
return await Chats.get_shared_chat_list_by_user_id(
user.id,
filter=filter,
skip=skip,
@@ -815,14 +815,14 @@ async def get_shared_session_user_chat_list(
@router.get('/share/{share_id}', response_model=Optional[ChatResponse])
async def get_shared_chat_by_id(share_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def get_shared_chat_by_id(share_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
if user.role == 'pending':
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)
if user.role == 'user' or (user.role == 'admin' and not ENABLE_ADMIN_CHAT_ACCESS):
chat = Chats.get_chat_by_share_id(share_id, db=db)
chat = await Chats.get_chat_by_share_id(share_id, db=db)
elif user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS:
chat = Chats.get_chat_by_id(share_id, db=db)
chat = await Chats.get_chat_by_id(share_id, db=db)
if chat:
return ChatResponse(**chat.model_dump())
@@ -849,11 +849,11 @@ class TagFilterForm(TagForm):
async def get_user_chat_list_by_tag_name(
form_data: TagFilterForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
chats = Chats.get_chat_list_by_user_id_and_tag_name(user.id, form_data.name, form_data.skip, form_data.limit, db=db)
chats = await Chats.get_chat_list_by_user_id_and_tag_name(user.id, form_data.name, form_data.skip, form_data.limit, db=db)
if len(chats) == 0:
Tags.delete_tag_by_name_and_user_id(form_data.name, user.id, db=db)
await Tags.delete_tag_by_name_and_user_id(form_data.name, user.id, db=db)
return chats
@@ -864,8 +864,8 @@ async def get_user_chat_list_by_tag_name(
@router.get('/{id}', response_model=Optional[ChatResponse])
async def get_chat_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
async def get_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
return ChatResponse(**chat.model_dump())
@@ -884,12 +884,12 @@ async def update_chat_by_id(
id: str,
form_data: ChatForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
updated_chat = {**chat.chat, **form_data.chat}
chat = Chats.update_chat_by_id(id, updated_chat, db=db)
chat = await Chats.update_chat_by_id(id, updated_chat, db=db)
return ChatResponse(**chat.model_dump())
else:
raise HTTPException(
@@ -911,9 +911,9 @@ async def update_chat_message_by_id(
message_id: str,
form_data: MessageForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
chat = Chats.get_chat_by_id(id, db=db)
chat = await Chats.get_chat_by_id(id, db=db)
if not chat:
raise HTTPException(
@@ -927,7 +927,7 @@ async def update_chat_message_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
chat = Chats.upsert_message_to_chat_by_id_and_message_id(
chat = await Chats.upsert_message_to_chat_by_id_and_message_id(
id,
message_id,
{
@@ -935,7 +935,7 @@ async def update_chat_message_by_id(
},
)
event_emitter = get_event_emitter(
event_emitter = await get_event_emitter(
{
'user_id': user.id,
'chat_id': id,
@@ -973,9 +973,9 @@ async def send_chat_message_event_by_id(
message_id: str,
form_data: EventForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
chat = Chats.get_chat_by_id(id, db=db)
chat = await Chats.get_chat_by_id(id, db=db)
if not chat:
raise HTTPException(
@@ -989,7 +989,7 @@ async def send_chat_message_event_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
event_emitter = get_event_emitter(
event_emitter = await get_event_emitter(
{
'user_id': user.id,
'chat_id': id,
@@ -1017,36 +1017,36 @@ async def delete_chat_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role == 'admin':
chat = Chats.get_chat_by_id(id, db=db)
chat = await Chats.get_chat_by_id(id, db=db)
if not chat:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
Chats.delete_orphan_tags_for_user(chat.meta.get('tags', []), user.id, threshold=1, db=db)
await Chats.delete_orphan_tags_for_user(chat.meta.get('tags', []), user.id, threshold=1, db=db)
result = Chats.delete_chat_by_id(id, db=db)
result = await Chats.delete_chat_by_id(id, db=db)
return result
else:
if not has_permission(user.id, 'chat.delete', request.app.state.config.USER_PERMISSIONS):
if not await has_permission(user.id, 'chat.delete', request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if not chat:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
Chats.delete_orphan_tags_for_user(chat.meta.get('tags', []), user.id, threshold=1, db=db)
await Chats.delete_orphan_tags_for_user(chat.meta.get('tags', []), user.id, threshold=1, db=db)
result = Chats.delete_chat_by_id_and_user_id(id, user.id, db=db)
result = await Chats.delete_chat_by_id_and_user_id(id, user.id, db=db)
return result
@@ -1056,8 +1056,8 @@ async def delete_chat_by_id(
@router.get('/{id}/pinned', response_model=Optional[bool])
async def get_pinned_status_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
async def get_pinned_status_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
return chat.pinned
else:
@@ -1070,10 +1070,10 @@ async def get_pinned_status_by_id(id: str, user=Depends(get_verified_user), db:
@router.post('/{id}/pin', response_model=Optional[ChatResponse])
async def pin_chat_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
async def pin_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
chat = Chats.toggle_chat_pinned_by_id(id, db=db)
chat = await Chats.toggle_chat_pinned_by_id(id, db=db)
return chat
else:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())
@@ -1093,9 +1093,9 @@ async def clone_chat_by_id(
form_data: CloneForm,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
updated_chat = {
**chat.chat,
@@ -1104,7 +1104,7 @@ async def clone_chat_by_id(
'title': form_data.title if form_data.title else f'Clone of {chat.title}',
}
chats = Chats.import_chats(
chats = await Chats.import_chats(
user.id,
[
ChatImportForm(
@@ -1137,11 +1137,11 @@ async def clone_chat_by_id(
@router.post('/{id}/clone/shared', response_model=Optional[ChatResponse])
async def clone_shared_chat_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def clone_shared_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
if user.role == 'admin':
chat = Chats.get_chat_by_id(id, db=db)
chat = await Chats.get_chat_by_id(id, db=db)
else:
chat = Chats.get_chat_by_share_id(id, db=db)
chat = await Chats.get_chat_by_share_id(id, db=db)
if chat:
updated_chat = {
@@ -1151,7 +1151,7 @@ async def clone_shared_chat_by_id(id: str, user=Depends(get_verified_user), db:
'title': f'Clone of {chat.title}',
}
chats = Chats.import_chats(
chats = await Chats.import_chats(
user.id,
[
ChatImportForm(
@@ -1184,18 +1184,18 @@ async def clone_shared_chat_by_id(id: str, user=Depends(get_verified_user), db:
@router.post('/{id}/archive', response_model=Optional[ChatResponse])
async def archive_chat_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
async def archive_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
chat = Chats.toggle_chat_archive_by_id(id, db=db)
chat = await Chats.toggle_chat_archive_by_id(id, db=db)
tag_ids = chat.meta.get('tags', [])
if chat.archived:
# Archived chats are excluded from count — clean up orphans
Chats.delete_orphan_tags_for_user(tag_ids, user.id, db=db)
await Chats.delete_orphan_tags_for_user(tag_ids, user.id, db=db)
else:
# Unarchived — ensure tag rows exist
Tags.ensure_tags_exist(tag_ids, user.id, db=db)
await Tags.ensure_tags_exist(tag_ids, user.id, db=db)
return ChatResponse(**chat.model_dump())
else:
@@ -1212,24 +1212,24 @@ async def share_chat_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if (user.role != 'admin') and (
not has_permission(user.id, 'chat.share', request.app.state.config.USER_PERMISSIONS)
not await has_permission(user.id, 'chat.share', request.app.state.config.USER_PERMISSIONS)
):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
if chat.share_id:
shared_chat = Chats.update_shared_chat_by_chat_id(chat.id, db=db)
shared_chat = await Chats.update_shared_chat_by_chat_id(chat.id, db=db)
return ChatResponse(**shared_chat.model_dump())
shared_chat = Chats.insert_shared_chat_by_chat_id(chat.id, db=db)
shared_chat = await Chats.insert_shared_chat_by_chat_id(chat.id, db=db)
if not shared_chat:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -1250,14 +1250,14 @@ async def share_chat_by_id(
@router.delete('/{id}/share', response_model=Optional[bool])
async def delete_shared_chat_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
async def delete_shared_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
if not chat.share_id:
return False
result = Chats.delete_shared_chat_by_chat_id(id, db=db)
update_result = Chats.update_chat_share_id_by_id(id, None, db=db)
result = await Chats.delete_shared_chat_by_chat_id(id, db=db)
update_result = await Chats.update_chat_share_id_by_id(id, None, db=db)
return result and update_result != None
else:
@@ -1281,11 +1281,11 @@ async def update_chat_folder_id_by_id(
id: str,
form_data: ChatFolderIdForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
chat = Chats.update_chat_folder_id_by_id_and_user_id(id, user.id, form_data.folder_id, db=db)
chat = await Chats.update_chat_folder_id_by_id_and_user_id(id, user.id, form_data.folder_id, db=db)
return ChatResponse(**chat.model_dump())
else:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())
@@ -1297,11 +1297,11 @@ async def update_chat_folder_id_by_id(
@router.get('/{id}/tags', response_model=list[TagModel])
async def get_chat_tags_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
async def get_chat_tags_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
tags = chat.meta.get('tags', [])
return Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db)
return await Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db)
else:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)
@@ -1316,9 +1316,9 @@ async def add_tag_by_id_and_tag_name(
id: str,
form_data: TagForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
tags = chat.meta.get('tags', [])
tag_id = form_data.name.replace(' ', '_').lower()
@@ -1330,11 +1330,11 @@ async def add_tag_by_id_and_tag_name(
)
if tag_id not in tags:
Chats.add_chat_tag_by_id_and_user_id_and_tag_name(id, user.id, form_data.name, db=db)
await Chats.add_chat_tag_by_id_and_user_id_and_tag_name(id, user.id, form_data.name, db=db)
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
tags = chat.meta.get('tags', [])
return Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db)
return await Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db)
else:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())
@@ -1349,18 +1349,18 @@ async def delete_tag_by_id_and_tag_name(
id: str,
form_data: TagForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
Chats.delete_tag_by_id_and_user_id_and_tag_name(id, user.id, form_data.name, db=db)
await Chats.delete_tag_by_id_and_user_id_and_tag_name(id, user.id, form_data.name, db=db)
if Chats.count_chats_by_tag_name_and_user_id(form_data.name, user.id, db=db) == 0:
Tags.delete_tag_by_name_and_user_id(form_data.name, user.id, db=db)
if await Chats.count_chats_by_tag_name_and_user_id(form_data.name, user.id, db=db) == 0:
await Tags.delete_tag_by_name_and_user_id(form_data.name, user.id, db=db)
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
tags = chat.meta.get('tags', [])
return Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db)
return await Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db)
else:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)
@@ -1371,12 +1371,12 @@ async def delete_tag_by_id_and_tag_name(
@router.delete('/{id}/tags/all', response_model=Optional[bool])
async def delete_all_tags_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
async def delete_all_tags_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
old_tags = chat.meta.get('tags', [])
Chats.delete_all_tags_by_id_and_user_id(id, user.id, db=db)
Chats.delete_orphan_tags_for_user(old_tags, user.id, db=db)
await Chats.delete_all_tags_by_id_and_user_id(id, user.id, db=db)
await Chats.delete_orphan_tags_for_user(old_tags, user.id, db=db)
return True
else:
+33 -33
View File
@@ -20,8 +20,8 @@ from open_webui.models.feedbacks import (
from open_webui.constants import ERROR_MESSAGES
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -208,10 +208,10 @@ class LeaderboardResponse(BaseModel):
async def get_leaderboard(
query: Optional[str] = None,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get model leaderboard with Elo ratings. Query filters by tag similarity."""
feedbacks = Feedbacks.get_feedbacks_for_leaderboard(db=db)
feedbacks = await Feedbacks.get_feedbacks_for_leaderboard(db=db)
similarities = None
if query and query.strip():
@@ -244,10 +244,10 @@ async def get_model_history(
model_id: str,
days: int = 30,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get daily win/loss history for a specific model."""
history = Feedbacks.get_model_evaluation_history(model_id=model_id, days=days, db=db)
history = await Feedbacks.get_model_evaluation_history(model_id=model_id, days=days, db=db)
return ModelHistoryResponse(model_id=model_id, history=history)
@@ -292,24 +292,24 @@ async def update_config(
@router.get('/feedbacks/models', response_model=list[str])
async def get_feedback_model_ids(user=Depends(get_admin_user), db: Session = Depends(get_session)):
return Feedbacks.get_distinct_model_ids(db=db)
async def get_feedback_model_ids(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
return await Feedbacks.get_distinct_model_ids(db=db)
@router.get('/feedbacks/all', response_model=list[FeedbackResponse])
async def get_all_feedbacks(user=Depends(get_admin_user), db: Session = Depends(get_session)):
feedbacks = Feedbacks.get_all_feedbacks(db=db)
async def get_all_feedbacks(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
feedbacks = await Feedbacks.get_all_feedbacks(db=db)
return feedbacks
@router.get('/feedbacks/all/ids', response_model=list[FeedbackIdResponse])
async def get_all_feedback_ids(user=Depends(get_admin_user), db: Session = Depends(get_session)):
return Feedbacks.get_all_feedback_ids(db=db)
async def get_all_feedback_ids(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
return await Feedbacks.get_all_feedback_ids(db=db)
@router.delete('/feedbacks/all')
async def delete_all_feedbacks(user=Depends(get_admin_user), db: Session = Depends(get_session)):
success = Feedbacks.delete_all_feedbacks(db=db)
async def delete_all_feedbacks(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
success = await Feedbacks.delete_all_feedbacks(db=db)
return success
@@ -317,23 +317,23 @@ async def delete_all_feedbacks(user=Depends(get_admin_user), db: Session = Depen
async def export_all_feedbacks(
model_id: Optional[str] = None,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
feedbacks = Feedbacks.get_all_feedbacks(db=db)
feedbacks = await Feedbacks.get_all_feedbacks(db=db)
if model_id:
feedbacks = [f for f in feedbacks if f.data and f.data.get('model_id') == model_id]
return feedbacks
@router.get('/feedbacks/user', response_model=list[FeedbackUserResponse])
async def get_feedbacks(user=Depends(get_verified_user), db: Session = Depends(get_session)):
feedbacks = Feedbacks.get_feedbacks_by_user_id(user.id, db=db)
async def get_feedbacks(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
feedbacks = await Feedbacks.get_feedbacks_by_user_id(user.id, db=db)
return feedbacks
@router.delete('/feedbacks', response_model=bool)
async def delete_feedbacks(user=Depends(get_verified_user), db: Session = Depends(get_session)):
success = Feedbacks.delete_feedbacks_by_user_id(user.id, db=db)
async def delete_feedbacks(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
success = await Feedbacks.delete_feedbacks_by_user_id(user.id, db=db)
return success
@@ -347,7 +347,7 @@ async def get_feedbacks(
page: Optional[int] = 1,
model_id: Optional[str] = None,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
limit = PAGE_ITEM_COUNT
@@ -362,7 +362,7 @@ async def get_feedbacks(
if model_id:
filter['model_id'] = model_id
result = Feedbacks.get_feedback_items(filter=filter, skip=skip, limit=limit, db=db)
result = await Feedbacks.get_feedback_items(filter=filter, skip=skip, limit=limit, db=db)
return result
@@ -371,9 +371,9 @@ async def create_feedback(
request: Request,
form_data: FeedbackForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
feedback = Feedbacks.insert_new_feedback(user_id=user.id, form_data=form_data, db=db)
feedback = await Feedbacks.insert_new_feedback(user_id=user.id, form_data=form_data, db=db)
if not feedback:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -384,11 +384,11 @@ async def create_feedback(
@router.get('/feedback/{id}', response_model=FeedbackModel)
async def get_feedback_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def get_feedback_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
if user.role == 'admin':
feedback = Feedbacks.get_feedback_by_id(id=id, db=db)
feedback = await Feedbacks.get_feedback_by_id(id=id, db=db)
else:
feedback = Feedbacks.get_feedback_by_id_and_user_id(id=id, user_id=user.id, db=db)
feedback = await Feedbacks.get_feedback_by_id_and_user_id(id=id, user_id=user.id, db=db)
if not feedback:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
@@ -401,12 +401,12 @@ async def update_feedback_by_id(
id: str,
form_data: FeedbackForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role == 'admin':
feedback = Feedbacks.update_feedback_by_id(id=id, form_data=form_data, db=db)
feedback = await Feedbacks.update_feedback_by_id(id=id, form_data=form_data, db=db)
else:
feedback = Feedbacks.update_feedback_by_id_and_user_id(id=id, user_id=user.id, form_data=form_data, db=db)
feedback = await Feedbacks.update_feedback_by_id_and_user_id(id=id, user_id=user.id, form_data=form_data, db=db)
if not feedback:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
@@ -415,11 +415,11 @@ async def update_feedback_by_id(
@router.delete('/feedback/{id}')
async def delete_feedback_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def delete_feedback_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
if user.role == 'admin':
success = Feedbacks.delete_feedback_by_id(id=id, db=db)
success = await Feedbacks.delete_feedback_by_id(id=id, db=db)
else:
success = Feedbacks.delete_feedback_by_id_and_user_id(id=id, user_id=user.id, db=db)
success = await Feedbacks.delete_feedback_by_id_and_user_id(id=id, user_id=user.id, db=db)
if not success:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
+53 -53
View File
@@ -21,8 +21,8 @@ from fastapi import (
)
from fastapi.responses import FileResponse, StreamingResponse
from sqlalchemy.orm import Session
from open_webui.internal.db import get_session, SessionLocal
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.internal.db import get_async_session, SessionLocal
from open_webui.constants import ERROR_MESSAGES
from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT
@@ -88,16 +88,16 @@ def _is_text_file(file_path: str, chunk_size: int = 8192) -> bool:
return False
def process_uploaded_file(
async def process_uploaded_file(
request,
file,
file_path,
file_item,
file_metadata,
user,
db: Optional[Session] = None,
db: Optional[AsyncSession] = None,
):
def _process_handler(db_session):
async def _process_handler(db_session):
try:
content_type = file.content_type
@@ -141,7 +141,7 @@ def process_uploaded_file(
except Exception as e:
log.error(f'Error processing file: {file_item.id}')
Files.update_file_data_by_id(
await Files.update_file_data_by_id(
file_item.id,
{
'status': 'failed',
@@ -158,7 +158,7 @@ def process_uploaded_file(
@router.post('/', response_model=FileModelResponse)
def upload_file(
async def upload_file(
request: Request,
background_tasks: BackgroundTasks,
file: UploadFile = File(...),
@@ -166,9 +166,9 @@ def upload_file(
process: bool = Query(True),
process_in_background: bool = Query(True),
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
return upload_file_handler(
return await upload_file_handler(
request,
file=file,
metadata=metadata,
@@ -180,7 +180,7 @@ def upload_file(
)
def upload_file_handler(
async def upload_file_handler(
request: Request,
file: UploadFile = File(...),
metadata: Optional[dict | str] = Form(None),
@@ -188,7 +188,7 @@ def upload_file_handler(
process_in_background: bool = Query(True),
user=Depends(get_verified_user),
background_tasks: Optional[BackgroundTasks] = None,
db: Optional[Session] = None,
db: Optional[AsyncSession] = None,
):
log.info(f'file.content_type: {file.content_type} {process}')
@@ -236,7 +236,7 @@ def upload_file_handler(
},
)
file_item = Files.insert_new_file(
file_item = await Files.insert_new_file(
user.id,
FileForm(
**{
@@ -258,9 +258,9 @@ def upload_file_handler(
)
if 'channel_id' in file_metadata:
channel = Channels.get_channel_by_id_and_user_id(file_metadata['channel_id'], user.id, db=db)
channel = await Channels.get_channel_by_id_and_user_id(file_metadata['channel_id'], user.id, db=db)
if channel:
Channels.add_file_to_channel_by_id(channel.id, file_item.id, user.id, db=db)
await Channels.add_file_to_channel_by_id(channel.id, file_item.id, user.id, db=db)
if process:
if background_tasks and process_in_background:
@@ -275,7 +275,7 @@ def upload_file_handler(
)
return {'status': True, **file_item.model_dump()}
else:
process_uploaded_file(
await process_uploaded_file(
request,
file,
file_path,
@@ -317,12 +317,12 @@ async def list_files(
user=Depends(get_verified_user),
page: int = Query(1, ge=1, description='Page number (1-indexed)'),
content: bool = Query(True),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
skip = (page - 1) * PAGE_SIZE
user_id = None if (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL) else user.id
result = Files.get_file_list(user_id=user_id, skip=skip, limit=PAGE_SIZE, db=db)
result = await Files.get_file_list(user_id=user_id, skip=skip, limit=PAGE_SIZE, db=db)
if not content:
for file in result.items:
@@ -347,7 +347,7 @@ async def search_files(
skip: int = Query(0, ge=0, description='Number of files to skip'),
limit: int = Query(100, ge=1, le=1000, description='Maximum number of files to return'),
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""
Search for files by filename with support for wildcard patterns.
@@ -357,7 +357,7 @@ async def search_files(
user_id = None if (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL) else user.id
# Use optimized database query with pagination
files = Files.search_files(
files = await Files.search_files(
user_id=user_id,
filename=filename,
skip=skip,
@@ -385,8 +385,8 @@ async def search_files(
@router.delete('/all')
async def delete_all_files(user=Depends(get_admin_user), db: Session = Depends(get_session)):
result = Files.delete_all_files(db=db)
async def delete_all_files(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
result = await Files.delete_all_files(db=db)
if result:
try:
Storage.delete_all_files()
@@ -412,8 +412,8 @@ async def delete_all_files(user=Depends(get_admin_user), db: Session = Depends(g
@router.get('/{id}', response_model=Optional[FileModel])
async def get_file_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
file = Files.get_file_by_id(id, db=db)
async def get_file_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
file = await Files.get_file_by_id(id, db=db)
if not file:
raise HTTPException(
@@ -421,7 +421,7 @@ async def get_file_by_id(id: str, user=Depends(get_verified_user), db: Session =
detail=ERROR_MESSAGES.NOT_FOUND,
)
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
return file
else:
raise HTTPException(
@@ -435,9 +435,9 @@ async def get_file_process_status(
id: str,
stream: bool = Query(False),
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
file = Files.get_file_by_id(id, db=db)
file = await Files.get_file_by_id(id, db=db)
if not file:
raise HTTPException(
@@ -445,7 +445,7 @@ async def get_file_process_status(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
if stream:
MAX_FILE_PROCESSING_DURATION = 3600 * 2
@@ -454,7 +454,7 @@ async def get_file_process_status(
# Each poll creates its own short-lived session to avoid holding a
# connection for hours. A WebSocket push would be more efficient.
for _ in range(MAX_FILE_PROCESSING_DURATION):
file_item = Files.get_file_by_id(file_id) # Creates own session
file_item = await Files.get_file_by_id(file_id) # Creates own session
if file_item:
data = file_item.model_dump().get('data', {})
status = data.get('status')
@@ -495,8 +495,8 @@ async def get_file_process_status(
@router.get('/{id}/data/content')
async def get_file_data_content_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
file = Files.get_file_by_id(id, db=db)
async def get_file_data_content_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
file = await Files.get_file_by_id(id, db=db)
if not file:
raise HTTPException(
@@ -504,7 +504,7 @@ async def get_file_data_content_by_id(id: str, user=Depends(get_verified_user),
detail=ERROR_MESSAGES.NOT_FOUND,
)
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
return {'content': file.data.get('content', '')}
else:
raise HTTPException(
@@ -523,14 +523,14 @@ class ContentForm(BaseModel):
@router.post('/{id}/data/content/update')
def update_file_data_content_by_id(
async def update_file_data_content_by_id(
request: Request,
id: str,
form_data: ContentForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
file = Files.get_file_by_id(id, db=db)
file = await Files.get_file_by_id(id, db=db)
if not file:
raise HTTPException(
@@ -538,7 +538,7 @@ def update_file_data_content_by_id(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'write', user, db=db):
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'write', user, db=db):
try:
process_file(
request,
@@ -546,7 +546,7 @@ def update_file_data_content_by_id(
user=user,
db=db,
)
file = Files.get_file_by_id(id=id, db=db)
file = await Files.get_file_by_id(id=id, db=db)
except Exception as e:
log.exception(e)
log.error(f'Error processing file: {file.id}')
@@ -554,7 +554,7 @@ def update_file_data_content_by_id(
# Propagate content change to all knowledge collections referencing
# this file. Without this the old embeddings remain in the knowledge
# collection and RAG returns both stale and current data (#20558).
knowledges = Knowledges.get_knowledges_by_file_id(id, db=db)
knowledges = await Knowledges.get_knowledges_by_file_id(id, db=db)
for knowledge in knowledges:
try:
# Remove old embeddings for this file from the KB collection
@@ -587,9 +587,9 @@ async def get_file_content_by_id(
id: str,
user=Depends(get_verified_user),
attachment: bool = Query(False),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
file = Files.get_file_by_id(id, db=db)
file = await Files.get_file_by_id(id, db=db)
if not file:
raise HTTPException(
@@ -597,7 +597,7 @@ async def get_file_content_by_id(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
try:
file_path = Storage.get_file(file.path)
file_path = Path(file_path)
@@ -646,8 +646,8 @@ async def get_file_content_by_id(
@router.get('/{id}/content/html')
async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
file = Files.get_file_by_id(id, db=db)
async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
file = await Files.get_file_by_id(id, db=db)
if not file:
raise HTTPException(
@@ -655,14 +655,14 @@ async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user),
detail=ERROR_MESSAGES.NOT_FOUND,
)
file_user = Users.get_user_by_id(file.user_id, db=db)
file_user = await Users.get_user_by_id(file.user_id, db=db)
if not file_user or file_user.role != 'admin':
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
try:
file_path = Storage.get_file(file.path)
file_path = Path(file_path)
@@ -693,8 +693,8 @@ async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user),
@router.get('/{id}/content/{file_name}')
async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
file = Files.get_file_by_id(id, db=db)
async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
file = await Files.get_file_by_id(id, db=db)
if not file:
raise HTTPException(
@@ -702,7 +702,7 @@ async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db: S
detail=ERROR_MESSAGES.NOT_FOUND,
)
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
file_path = file.path
# Handle Unicode filenames
@@ -749,8 +749,8 @@ async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db: S
@router.delete('/{id}')
async def delete_file_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
file = Files.get_file_by_id(id, db=db)
async def delete_file_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
file = await Files.get_file_by_id(id, db=db)
if not file:
raise HTTPException(
@@ -758,12 +758,12 @@ async def delete_file_by_id(id: str, user=Depends(get_verified_user), db: Sessio
detail=ERROR_MESSAGES.NOT_FOUND,
)
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'write', user, db=db):
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'write', user, db=db):
# Clean up KB associations and embeddings before deleting
knowledges = Knowledges.get_knowledges_by_file_id(id, db=db)
knowledges = await Knowledges.get_knowledges_by_file_id(id, db=db)
for knowledge in knowledges:
# Remove KB-file relationship
Knowledges.remove_file_from_knowledge_by_id(knowledge.id, id, db=db)
await Knowledges.remove_file_from_knowledge_by_id(knowledge.id, id, db=db)
# Clean KB embeddings (same logic as /knowledge/{id}/file/remove)
try:
VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'file_id': id})
@@ -772,7 +772,7 @@ async def delete_file_by_id(id: str, user=Depends(get_verified_user), db: Sessio
except Exception as e:
log.debug(f'KB embedding cleanup for {knowledge.id}: {e}')
result = Files.delete_file_by_id(id, db=db)
result = await Files.delete_file_by_id(id, db=db)
if result:
try:
Storage.delete_file(file.path)
+35 -35
View File
@@ -22,8 +22,8 @@ from open_webui.models.knowledge import Knowledges
from open_webui.config import UPLOAD_DIR
from open_webui.constants import ERROR_MESSAGES
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status, Request
@@ -48,7 +48,7 @@ router = APIRouter()
async def get_folders(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if request.app.state.config.ENABLE_FOLDERS is False:
raise HTTPException(
@@ -56,7 +56,7 @@ async def get_folders(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id,
'features.folders',
request.app.state.config.USER_PERMISSIONS,
@@ -67,29 +67,29 @@ async def get_folders(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
folders = Folders.get_folders_by_user_id(user.id, db=db)
folders = await Folders.get_folders_by_user_id(user.id, db=db)
# Verify folder data integrity
folder_list = []
for folder in folders:
if folder.parent_id and not Folders.get_folder_by_id_and_user_id(folder.parent_id, user.id, db=db):
folder = Folders.update_folder_parent_id_by_id_and_user_id(folder.id, user.id, None, db=db)
if folder.parent_id and not await Folders.get_folder_by_id_and_user_id(folder.parent_id, user.id, db=db):
folder = await Folders.update_folder_parent_id_by_id_and_user_id(folder.id, user.id, None, db=db)
if folder.data:
if 'files' in folder.data:
valid_files = []
for file in folder.data['files']:
if file.get('type') == 'file':
if Files.check_access_by_user_id(file.get('id'), user.id, 'read', db=db):
if await Files.check_access_by_user_id(file.get('id'), user.id, 'read', db=db):
valid_files.append(file)
elif file.get('type') == 'collection':
if Knowledges.check_access_by_user_id(file.get('id'), user.id, 'read', db=db):
if await Knowledges.check_access_by_user_id(file.get('id'), user.id, 'read', db=db):
valid_files.append(file)
else:
valid_files.append(file)
folder.data['files'] = valid_files
Folders.update_folder_by_id_and_user_id(folder.id, user.id, FolderUpdateForm(data=folder.data), db=db)
await Folders.update_folder_by_id_and_user_id(folder.id, user.id, FolderUpdateForm(data=folder.data), db=db)
folder_list.append(FolderNameIdResponse(**folder.model_dump()))
@@ -102,12 +102,12 @@ async def get_folders(
@router.post('/')
def create_folder(
async def create_folder(
form_data: FolderForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
folder = Folders.get_folder_by_parent_id_and_user_id_and_name(form_data.parent_id, user.id, form_data.name, db=db)
folder = await Folders.get_folder_by_parent_id_and_user_id_and_name(form_data.parent_id, user.id, form_data.name, db=db)
if folder:
raise HTTPException(
@@ -116,7 +116,7 @@ def create_folder(
)
try:
folder = Folders.insert_new_folder(user.id, form_data, form_data.parent_id, db=db)
folder = await Folders.insert_new_folder(user.id, form_data, form_data.parent_id, db=db)
return folder
except Exception as e:
log.exception(e)
@@ -133,8 +133,8 @@ def create_folder(
@router.get('/{id}', response_model=Optional[FolderModel])
async def get_folder_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
folder = Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
async def get_folder_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
if folder:
return folder
else:
@@ -154,13 +154,13 @@ async def update_folder_name_by_id(
id: str,
form_data: FolderUpdateForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
folder = Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
if folder:
if form_data.name is not None:
# Check if folder with same name exists
existing_folder = Folders.get_folder_by_parent_id_and_user_id_and_name(
existing_folder = await Folders.get_folder_by_parent_id_and_user_id_and_name(
folder.parent_id, user.id, form_data.name, db=db
)
if existing_folder and existing_folder.id != id:
@@ -170,7 +170,7 @@ async def update_folder_name_by_id(
)
try:
folder = Folders.update_folder_by_id_and_user_id(id, user.id, form_data, db=db)
folder = await Folders.update_folder_by_id_and_user_id(id, user.id, form_data, db=db)
return folder
except Exception as e:
log.exception(e)
@@ -200,11 +200,11 @@ async def update_folder_parent_id_by_id(
id: str,
form_data: FolderParentIdForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
folder = Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
if folder:
existing_folder = Folders.get_folder_by_parent_id_and_user_id_and_name(
existing_folder = await Folders.get_folder_by_parent_id_and_user_id_and_name(
form_data.parent_id, user.id, folder.name, db=db
)
@@ -215,7 +215,7 @@ async def update_folder_parent_id_by_id(
)
try:
folder = Folders.update_folder_parent_id_by_id_and_user_id(id, user.id, form_data.parent_id, db=db)
folder = await Folders.update_folder_parent_id_by_id_and_user_id(id, user.id, form_data.parent_id, db=db)
return folder
except Exception as e:
log.exception(e)
@@ -245,12 +245,12 @@ async def update_folder_is_expanded_by_id(
id: str,
form_data: FolderIsExpandedForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
folder = Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
if folder:
try:
folder = Folders.update_folder_is_expanded_by_id_and_user_id(id, user.id, form_data.is_expanded, db=db)
folder = await Folders.update_folder_is_expanded_by_id_and_user_id(id, user.id, form_data.is_expanded, db=db)
return folder
except Exception as e:
log.exception(e)
@@ -277,10 +277,10 @@ async def delete_folder_by_id(
id: str,
delete_contents: Optional[bool] = True,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if Chats.count_chats_by_folder_id_and_user_id(id, user.id, db=db):
chat_delete_permission = has_permission(
if await Chats.count_chats_by_folder_id_and_user_id(id, user.id, db=db):
chat_delete_permission = await has_permission(
user.id, 'chat.delete', request.app.state.config.USER_PERMISSIONS, db=db
)
if user.role != 'admin' and not chat_delete_permission:
@@ -290,18 +290,18 @@ async def delete_folder_by_id(
)
folders = []
folders.append(Folders.get_folder_by_id_and_user_id(id, user.id, db=db))
folders.append(await Folders.get_folder_by_id_and_user_id(id, user.id, db=db))
while folders:
folder = folders.pop()
if folder:
try:
folder_ids = Folders.delete_folder_by_id_and_user_id(folder.id, user.id, db=db)
folder_ids = await Folders.delete_folder_by_id_and_user_id(folder.id, user.id, db=db)
for folder_id in folder_ids:
if delete_contents:
Chats.delete_chats_by_user_id_and_folder_id(user.id, folder_id, db=db)
await Chats.delete_chats_by_user_id_and_folder_id(user.id, folder_id, db=db)
else:
Chats.move_chats_by_user_id_and_folder_id(user.id, folder_id, None, db=db)
await Chats.move_chats_by_user_id_and_folder_id(user.id, folder_id, None, db=db)
return True
except Exception as e:
@@ -313,7 +313,7 @@ async def delete_folder_by_id(
)
finally:
# Get all subfolders
subfolders = Folders.get_folders_by_parent_id_and_user_id(folder.id, user.id, db=db)
subfolders = await Folders.get_folders_by_parent_id_and_user_id(folder.id, user.id, db=db)
folders.extend(subfolders)
else:
+50 -50
View File
@@ -26,8 +26,8 @@ from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.utils.auth import get_admin_user, get_verified_user
from pydantic import BaseModel, HttpUrl
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -42,13 +42,13 @@ router = APIRouter()
@router.get('/', response_model=list[FunctionResponse])
async def get_functions(user=Depends(get_verified_user), db: Session = Depends(get_session)):
return Functions.get_functions(db=db)
async def get_functions(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
return await Functions.get_functions(db=db)
@router.get('/list', response_model=list[FunctionUserResponse])
async def get_function_list(user=Depends(get_admin_user), db: Session = Depends(get_session)):
return Functions.get_function_list(db=db)
async def get_function_list(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
return await Functions.get_function_list(db=db)
############################
@@ -60,9 +60,9 @@ async def get_function_list(user=Depends(get_admin_user), db: Session = Depends(
async def get_functions(
include_valves: bool = False,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
return Functions.get_functions(include_valves=include_valves, db=db)
return await Functions.get_functions(include_valves=include_valves, db=db)
############################
@@ -145,12 +145,12 @@ async def sync_functions(
request: Request,
form_data: SyncFunctionsForm,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
try:
for function in form_data.functions:
function.content = replace_imports(function.content)
function_module, function_type, frontmatter = load_function_module_by_id(
function_module, function_type, frontmatter = await load_function_module_by_id(
function.id,
content=function.content,
)
@@ -163,7 +163,7 @@ async def sync_functions(
log.exception(f'Error validating valves for function {function.id}: {e}')
raise e
return Functions.sync_functions(user.id, form_data.functions, db=db)
return await Functions.sync_functions(user.id, form_data.functions, db=db)
except Exception as e:
log.exception(f'Failed to load a function: {e}')
raise HTTPException(
@@ -182,7 +182,7 @@ async def create_new_function(
request: Request,
form_data: FunctionForm,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if not form_data.id.isidentifier():
raise HTTPException(
@@ -192,11 +192,11 @@ async def create_new_function(
form_data.id = form_data.id.lower()
function = Functions.get_function_by_id(form_data.id, db=db)
function = await Functions.get_function_by_id(form_data.id, db=db)
if function is None:
try:
form_data.content = replace_imports(form_data.content)
function_module, function_type, frontmatter = load_function_module_by_id(
function_module, function_type, frontmatter = await load_function_module_by_id(
form_data.id,
content=form_data.content,
)
@@ -205,13 +205,13 @@ async def create_new_function(
FUNCTIONS = request.app.state.FUNCTIONS
FUNCTIONS[form_data.id] = function_module
function = Functions.insert_new_function(user.id, function_type, form_data, db=db)
function = await Functions.insert_new_function(user.id, function_type, form_data, db=db)
function_cache_dir = CACHE_DIR / 'functions' / form_data.id
function_cache_dir.mkdir(parents=True, exist_ok=True)
if function_type == 'filter' and getattr(function_module, 'toggle', None):
Functions.update_function_metadata_by_id(form_data.id, {'toggle': True}, db=db)
await Functions.update_function_metadata_by_id(form_data.id, {'toggle': True}, db=db)
if function:
return function
@@ -239,8 +239,8 @@ async def create_new_function(
@router.get('/id/{id}', response_model=Optional[FunctionModel])
async def get_function_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
function = Functions.get_function_by_id(id, db=db)
async def get_function_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
function = await Functions.get_function_by_id(id, db=db)
if function:
return function
@@ -257,10 +257,10 @@ async def get_function_by_id(id: str, user=Depends(get_admin_user), db: Session
@router.post('/id/{id}/toggle', response_model=Optional[FunctionModel])
async def toggle_function_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
function = Functions.get_function_by_id(id, db=db)
async def toggle_function_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
function = await Functions.get_function_by_id(id, db=db)
if function:
function = Functions.update_function_by_id(id, {'is_active': not function.is_active}, db=db)
function = await Functions.update_function_by_id(id, {'is_active': not function.is_active}, db=db)
if function:
return function
@@ -282,10 +282,10 @@ async def toggle_function_by_id(id: str, user=Depends(get_admin_user), db: Sessi
@router.post('/id/{id}/toggle/global', response_model=Optional[FunctionModel])
async def toggle_global_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
function = Functions.get_function_by_id(id, db=db)
async def toggle_global_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
function = await Functions.get_function_by_id(id, db=db)
if function:
function = Functions.update_function_by_id(id, {'is_global': not function.is_global}, db=db)
function = await Functions.update_function_by_id(id, {'is_global': not function.is_global}, db=db)
if function:
return function
@@ -312,11 +312,11 @@ async def update_function_by_id(
id: str,
form_data: FunctionForm,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
try:
form_data.content = replace_imports(form_data.content)
function_module, function_type, frontmatter = load_function_module_by_id(id, content=form_data.content)
function_module, function_type, frontmatter = await load_function_module_by_id(id, content=form_data.content)
form_data.meta.manifest = frontmatter
FUNCTIONS = request.app.state.FUNCTIONS
@@ -325,10 +325,10 @@ async def update_function_by_id(
updated = {**form_data.model_dump(exclude={'id'}), 'type': function_type}
log.debug(updated)
function = Functions.update_function_by_id(id, updated, db=db)
function = await Functions.update_function_by_id(id, updated, db=db)
if function_type == 'filter' and getattr(function_module, 'toggle', None):
Functions.update_function_metadata_by_id(id, {'toggle': True}, db=db)
await Functions.update_function_metadata_by_id(id, {'toggle': True}, db=db)
if function:
return function
@@ -355,9 +355,9 @@ async def delete_function_by_id(
request: Request,
id: str,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
result = Functions.delete_function_by_id(id, db=db)
result = await Functions.delete_function_by_id(id, db=db)
if result:
FUNCTIONS = request.app.state.FUNCTIONS
@@ -373,11 +373,11 @@ async def delete_function_by_id(
@router.get('/id/{id}/valves', response_model=Optional[dict])
async def get_function_valves_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
function = Functions.get_function_by_id(id, db=db)
async def get_function_valves_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
function = await Functions.get_function_by_id(id, db=db)
if function:
try:
valves = Functions.get_function_valves_by_id(id, db=db)
valves = await Functions.get_function_valves_by_id(id, db=db)
return valves
except Exception as e:
raise HTTPException(
@@ -401,11 +401,11 @@ async def get_function_valves_spec_by_id(
request: Request,
id: str,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
function = Functions.get_function_by_id(id, db=db)
function = await Functions.get_function_by_id(id, db=db)
if function:
function_module, function_type, frontmatter = get_function_module_from_cache(request, id)
function_module, function_type, frontmatter = await get_function_module_from_cache(request, id)
if hasattr(function_module, 'Valves'):
Valves = function_module.Valves
@@ -432,11 +432,11 @@ async def update_function_valves_by_id(
id: str,
form_data: dict,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
function = Functions.get_function_by_id(id, db=db)
function = await Functions.get_function_by_id(id, db=db)
if function:
function_module, function_type, frontmatter = get_function_module_from_cache(request, id)
function_module, function_type, frontmatter = await get_function_module_from_cache(request, id)
if hasattr(function_module, 'Valves'):
Valves = function_module.Valves
@@ -446,7 +446,7 @@ async def update_function_valves_by_id(
valves = Valves(**form_data)
valves_dict = valves.model_dump(exclude_unset=True)
Functions.update_function_valves_by_id(id, valves_dict, db=db)
await Functions.update_function_valves_by_id(id, valves_dict, db=db)
return valves_dict
except Exception as e:
log.exception(f'Error updating function values by id {id}: {e}')
@@ -473,11 +473,11 @@ async def update_function_valves_by_id(
@router.get('/id/{id}/valves/user', response_model=Optional[dict])
async def get_function_user_valves_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
function = Functions.get_function_by_id(id, db=db)
async def get_function_user_valves_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
function = await Functions.get_function_by_id(id, db=db)
if function:
try:
user_valves = Functions.get_user_valves_by_id_and_user_id(id, user.id, db=db)
user_valves = await Functions.get_user_valves_by_id_and_user_id(id, user.id, db=db)
return user_valves
except Exception as e:
raise HTTPException(
@@ -496,11 +496,11 @@ async def get_function_user_valves_spec_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
function = Functions.get_function_by_id(id, db=db)
function = await Functions.get_function_by_id(id, db=db)
if function:
function_module, function_type, frontmatter = get_function_module_from_cache(request, id)
function_module, function_type, frontmatter = await get_function_module_from_cache(request, id)
if hasattr(function_module, 'UserValves'):
UserValves = function_module.UserValves
@@ -522,12 +522,12 @@ async def update_function_user_valves_by_id(
id: str,
form_data: dict,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
function = Functions.get_function_by_id(id, db=db)
function = await Functions.get_function_by_id(id, db=db)
if function:
function_module, function_type, frontmatter = get_function_module_from_cache(request, id)
function_module, function_type, frontmatter = await get_function_module_from_cache(request, id)
if hasattr(function_module, 'UserValves'):
UserValves = function_module.UserValves
@@ -536,7 +536,7 @@ async def update_function_user_valves_by_id(
form_data = {k: v for k, v in form_data.items() if v is not None}
user_valves = UserValves(**form_data)
user_valves_dict = user_valves.model_dump(exclude_unset=True)
Functions.update_user_valves_by_id_and_user_id(id, user.id, user_valves_dict, db=db)
await Functions.update_user_valves_by_id_and_user_id(id, user.id, user_valves_dict, db=db)
return user_valves_dict
except Exception as e:
log.exception(f'Error updating function user valves by id {id}: {e}')
+31 -31
View File
@@ -17,8 +17,8 @@ from open_webui.config import CACHE_DIR
from open_webui.constants import ERROR_MESSAGES
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.utils.auth import get_admin_user, get_verified_user
@@ -35,7 +35,7 @@ router = APIRouter()
async def get_groups(
share: Optional[bool] = None,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
filter = {}
@@ -45,7 +45,7 @@ async def get_groups(
if share is not None:
filter['share'] = share
groups = Groups.get_groups(filter=filter, db=db)
groups = await Groups.get_groups(filter=filter, db=db)
return groups
@@ -59,14 +59,14 @@ async def get_groups(
async def create_new_group(
form_data: GroupForm,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
try:
group = Groups.insert_new_group(user.id, form_data, db=db)
group = await Groups.insert_new_group(user.id, form_data, db=db)
if group:
return GroupResponse(
**group.model_dump(),
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
)
else:
raise HTTPException(
@@ -87,12 +87,12 @@ async def create_new_group(
@router.get('/id/{id}', response_model=Optional[GroupResponse])
async def get_group_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
group = Groups.get_group_by_id(id, db=db)
async def get_group_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
group = await Groups.get_group_by_id(id, db=db)
if group:
return GroupResponse(
**group.model_dump(),
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
)
else:
raise HTTPException(
@@ -102,12 +102,12 @@ async def get_group_by_id(id: str, user=Depends(get_admin_user), db: Session = D
@router.get('/id/{id}/info', response_model=Optional[GroupInfoResponse])
async def get_group_info_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
group = Groups.get_group_by_id(id, db=db)
async def get_group_info_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
group = await Groups.get_group_by_id(id, db=db)
if group:
return GroupInfoResponse(
**group.model_dump(),
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
)
else:
raise HTTPException(
@@ -127,13 +127,13 @@ class GroupExportResponse(GroupResponse):
@router.get('/id/{id}/export', response_model=Optional[GroupExportResponse])
async def export_group_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
group = Groups.get_group_by_id(id, db=db)
async def export_group_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
group = await Groups.get_group_by_id(id, db=db)
if group:
return GroupExportResponse(
**group.model_dump(),
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
user_ids=Groups.get_group_user_ids_by_id(group.id, db=db),
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
user_ids=await Groups.get_group_user_ids_by_id(group.id, db=db),
)
else:
raise HTTPException(
@@ -148,9 +148,9 @@ async def export_group_by_id(id: str, user=Depends(get_admin_user), db: Session
@router.post('/id/{id}/users', response_model=list[UserInfoResponse])
async def get_users_in_group(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
async def get_users_in_group(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
try:
users = Users.get_users_by_group_id(id, db=db)
users = await Users.get_users_by_group_id(id, db=db)
return users
except Exception as e:
log.exception(f'Error adding users to group {id}: {e}')
@@ -170,14 +170,14 @@ async def update_group_by_id(
id: str,
form_data: GroupUpdateForm,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
try:
group = Groups.update_group_by_id(id, form_data, db=db)
group = await Groups.update_group_by_id(id, form_data, db=db)
if group:
return GroupResponse(
**group.model_dump(),
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
)
else:
raise HTTPException(
@@ -202,17 +202,17 @@ async def add_user_to_group(
id: str,
form_data: UserIdsForm,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
try:
if form_data.user_ids:
form_data.user_ids = Users.get_valid_user_ids(form_data.user_ids, db=db)
form_data.user_ids = await Users.get_valid_user_ids(form_data.user_ids, db=db)
group = Groups.add_users_to_group(id, form_data.user_ids, db=db)
group = await Groups.add_users_to_group(id, form_data.user_ids, db=db)
if group:
return GroupResponse(
**group.model_dump(),
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
)
else:
raise HTTPException(
@@ -232,14 +232,14 @@ async def remove_users_from_group(
id: str,
form_data: UserIdsForm,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
try:
group = Groups.remove_users_from_group(id, form_data.user_ids, db=db)
group = await Groups.remove_users_from_group(id, form_data.user_ids, db=db)
if group:
return GroupResponse(
**group.model_dump(),
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
)
else:
raise HTTPException(
@@ -260,9 +260,9 @@ async def remove_users_from_group(
@router.delete('/id/{id}/delete', response_model=bool)
async def delete_group_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
async def delete_group_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
try:
result = Groups.delete_group_by_id(id, db=db)
result = await Groups.delete_group_by_id(id, db=db)
if result:
return result
else:
+16 -16
View File
@@ -28,8 +28,8 @@ from open_webui.routers.files import upload_file_handler, get_file_content_by_id
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission
from open_webui.utils.headers import include_user_info_headers
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.utils.images.comfyui import (
ComfyUICreateImageForm,
ComfyUIEditImageForm,
@@ -341,7 +341,7 @@ async def verify_url(request: Request, user=Depends(get_admin_user)):
@router.get('/models')
def get_models(request: Request, user=Depends(get_verified_user)):
async def get_models(request: Request, user=Depends(get_verified_user)):
try:
if request.app.state.config.IMAGE_GENERATION_ENGINE == 'openai':
return [
@@ -456,7 +456,7 @@ def get_image_data(data: str, headers=None):
return None, None
def upload_image(request, image_data, content_type, metadata, user, db=None):
async def upload_image(request, image_data, content_type, metadata, user, db=None):
image_format = mimetypes.guess_extension(content_type)
file = UploadFile(
file=io.BytesIO(image_data),
@@ -465,7 +465,7 @@ def upload_image(request, image_data, content_type, metadata, user, db=None):
'content-type': content_type,
},
)
file_item = upload_file_handler(
file_item = await upload_file_handler(
request,
file=file,
metadata=metadata,
@@ -479,7 +479,7 @@ def upload_image(request, image_data, content_type, metadata, user, db=None):
message_id = metadata.get('message_id')
if chat_id and message_id:
Chats.insert_chat_files(
await Chats.insert_chat_files(
chat_id=chat_id,
message_id=message_id,
file_ids=[file_item.id],
@@ -499,7 +499,7 @@ async def generate_images(request: Request, form_data: CreateImageForm, user=Dep
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id, 'features.image_generation', request.app.state.config.USER_PERMISSIONS
):
raise HTTPException(
@@ -590,7 +590,7 @@ async def image_generations(
else:
image_data, content_type = get_image_data(image['b64_json'])
_, url = upload_image(request, image_data, content_type, {**data, **metadata}, user)
_, url = await upload_image(request, image_data, content_type, {**data, **metadata}, user)
images.append({'url': url})
return images
@@ -635,14 +635,14 @@ async def image_generations(
if model.endswith(':predict'):
for image in res['predictions']:
image_data, content_type = get_image_data(image['bytesBase64Encoded'])
_, url = upload_image(request, image_data, content_type, {**data, **metadata}, user)
_, url = await upload_image(request, image_data, content_type, {**data, **metadata}, user)
images.append({'url': url})
elif model.endswith(':generateContent'):
for image in res['candidates']:
for part in image['content']['parts']:
if part.get('inlineData', {}).get('data'):
image_data, content_type = get_image_data(part['inlineData']['data'])
_, url = upload_image(
_, url = await upload_image(
request,
image_data,
content_type,
@@ -695,7 +695,7 @@ async def image_generations(
headers = {'Authorization': f'Bearer {request.app.state.config.COMFYUI_API_KEY}'}
image_data, content_type = get_image_data(image['url'], headers)
_, url = upload_image(
_, url = await upload_image(
request,
image_data,
content_type,
@@ -742,7 +742,7 @@ async def image_generations(
for image in res['images']:
image_data, content_type = get_image_data(image)
_, url = upload_image(
_, url = await upload_image(
request,
image_data,
content_type,
@@ -832,7 +832,7 @@ async def image_edits(
except Exception as e:
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
def get_image_file_item(base64_string, param_name='image'):
async def get_image_file_item(base64_string, param_name='image'):
data = base64_string
header, encoded = data.split(',', 1)
mime_type = header.split(';')[0].lstrip('data:')
@@ -905,7 +905,7 @@ async def image_edits(
else:
image_data, content_type = get_image_data(image['b64_json'])
_, url = upload_image(request, image_data, content_type, {**data, **metadata}, user)
_, url = await upload_image(request, image_data, content_type, {**data, **metadata}, user)
images.append({'url': url})
return images
@@ -956,7 +956,7 @@ async def image_edits(
for part in image['content']['parts']:
if part.get('inlineData', {}).get('data'):
image_data, content_type = get_image_data(part['inlineData']['data'])
_, url = upload_image(
_, url = await upload_image(
request,
image_data,
content_type,
@@ -1036,7 +1036,7 @@ async def image_edits(
headers = {'Authorization': f'Bearer {request.app.state.config.IMAGES_EDIT_COMFYUI_API_KEY}'}
image_data, content_type = get_image_data(image_url, headers)
_, url = upload_image(
_, url = await upload_image(
request,
image_data,
content_type,
+85 -85
View File
@@ -8,8 +8,8 @@ import io
import zipfile
from urllib.parse import quote
from sqlalchemy.orm import Session
from open_webui.internal.db import get_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.internal.db import get_async_session
from open_webui.models.groups import Groups
from open_webui.models.knowledge import (
KnowledgeFileListResponse,
@@ -111,14 +111,14 @@ class KnowledgeAccessListResponse(BaseModel):
async def get_knowledge_bases(
page: Optional[int] = 1,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
page = max(page, 1)
limit = PAGE_ITEM_COUNT
skip = (page - 1) * limit
filter = {}
groups = Groups.get_groups_by_member_id(user.id, db=db)
groups = await Groups.get_groups_by_member_id(user.id, db=db)
user_group_ids = {group.id for group in groups}
if not user.role == 'admin' or not BYPASS_ADMIN_ACCESS_CONTROL:
@@ -127,11 +127,11 @@ async def get_knowledge_bases(
filter['user_id'] = user.id
result = Knowledges.search_knowledge_bases(user.id, filter=filter, skip=skip, limit=limit, db=db)
result = await Knowledges.search_knowledge_bases(user.id, filter=filter, skip=skip, limit=limit, db=db)
# Batch-fetch writable knowledge IDs in a single query instead of N has_access calls
knowledge_base_ids = [knowledge_base.id for knowledge_base in result.items]
writable_knowledge_base_ids = AccessGrants.get_accessible_resource_ids(
writable_knowledge_base_ids = await AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type='knowledge',
resource_ids=knowledge_base_ids,
@@ -162,7 +162,7 @@ async def search_knowledge_bases(
view_option: Optional[str] = None,
page: Optional[int] = 1,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
page = max(page, 1)
limit = PAGE_ITEM_COUNT
@@ -174,7 +174,7 @@ async def search_knowledge_bases(
if view_option:
filter['view_option'] = view_option
groups = Groups.get_groups_by_member_id(user.id, db=db)
groups = await Groups.get_groups_by_member_id(user.id, db=db)
user_group_ids = {group.id for group in groups}
if not user.role == 'admin' or not BYPASS_ADMIN_ACCESS_CONTROL:
@@ -183,11 +183,11 @@ async def search_knowledge_bases(
filter['user_id'] = user.id
result = Knowledges.search_knowledge_bases(user.id, filter=filter, skip=skip, limit=limit, db=db)
result = await Knowledges.search_knowledge_bases(user.id, filter=filter, skip=skip, limit=limit, db=db)
# Batch-fetch writable knowledge IDs in a single query instead of N has_access calls
knowledge_base_ids = [knowledge_base.id for knowledge_base in result.items]
writable_knowledge_base_ids = AccessGrants.get_accessible_resource_ids(
writable_knowledge_base_ids = await AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type='knowledge',
resource_ids=knowledge_base_ids,
@@ -217,7 +217,7 @@ async def search_knowledge_files(
query: Optional[str] = None,
page: Optional[int] = 1,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
page = max(page, 1)
limit = PAGE_ITEM_COUNT
@@ -227,13 +227,13 @@ async def search_knowledge_files(
if query:
filter['query'] = query
groups = Groups.get_groups_by_member_id(user.id, db=db)
groups = await Groups.get_groups_by_member_id(user.id, db=db)
if groups:
filter['group_ids'] = [group.id for group in groups]
filter['user_id'] = user.id
return Knowledges.search_knowledge_files(filter=filter, skip=skip, limit=limit, db=db)
return await Knowledges.search_knowledge_files(filter=filter, skip=skip, limit=limit, db=db)
############################
@@ -247,11 +247,11 @@ async def create_new_knowledge(
form_data: KnowledgeForm,
user=Depends(get_verified_user),
):
# NOTE: We intentionally do NOT use Depends(get_session) here.
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
# Database operations (has_permission, filter_allowed_access_grants, insert_new_knowledge) manage their own sessions.
# This prevents holding a connection during embed_knowledge_base_metadata()
# which makes external embedding API calls (1-5+ seconds).
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id, 'workspace.knowledge', request.app.state.config.USER_PERMISSIONS
):
raise HTTPException(
@@ -259,7 +259,7 @@ async def create_new_knowledge(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -267,7 +267,7 @@ async def create_new_knowledge(
'sharing.public_knowledge',
)
knowledge = Knowledges.insert_new_knowledge(user.id, form_data)
knowledge = await Knowledges.insert_new_knowledge(user.id, form_data)
if knowledge:
# Embed knowledge base for semantic search
@@ -294,7 +294,7 @@ async def create_new_knowledge(
async def reindex_knowledge_files(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin':
raise HTTPException(
@@ -302,13 +302,13 @@ async def reindex_knowledge_files(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
knowledge_bases = Knowledges.get_knowledge_bases(db=db)
knowledge_bases = await Knowledges.get_knowledge_bases(db=db)
log.info(f'Starting reindexing for {len(knowledge_bases)} knowledge bases')
for knowledge_base in knowledge_bases:
try:
files = Knowledges.get_files_by_id(knowledge_base.id, db=db)
files = await Knowledges.get_files_by_id(knowledge_base.id, db=db)
try:
if VECTOR_DB_CLIENT.has_collection(collection_name=knowledge_base.id):
VECTOR_DB_CLIENT.delete_collection(collection_name=knowledge_base.id)
@@ -357,12 +357,12 @@ async def reindex_knowledge_base_metadata_embeddings(
):
"""Batch embed all existing knowledge bases. Admin only.
NOTE: We intentionally do NOT use Depends(get_session) here.
NOTE: We intentionally do NOT use Depends(get_async_session) here.
This endpoint loops through ALL knowledge bases and calls embed_knowledge_base_metadata()
for each one, making N external embedding API calls. Holding a session during
this entire operation would exhaust the connection pool.
"""
knowledge_bases = Knowledges.get_knowledge_bases()
knowledge_bases = await Knowledges.get_knowledge_bases()
log.info(f'Reindexing embeddings for {len(knowledge_bases)} knowledge bases')
success_count = 0
@@ -385,14 +385,14 @@ class KnowledgeFilesResponse(KnowledgeResponse):
@router.get('/{id}', response_model=Optional[KnowledgeFilesResponse])
async def get_knowledge_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
async def get_knowledge_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
if knowledge:
if (
user.role == 'admin'
or knowledge.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='knowledge',
resource_id=knowledge.id,
@@ -405,7 +405,7 @@ async def get_knowledge_by_id(id: str, user=Depends(get_verified_user), db: Sess
write_access=(
user.id == knowledge.user_id
or (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='knowledge',
resource_id=knowledge.id,
@@ -438,11 +438,11 @@ async def update_knowledge_by_id(
form_data: KnowledgeForm,
user=Depends(get_verified_user),
):
# NOTE: We intentionally do NOT use Depends(get_session) here.
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
# Database operations manage their own short-lived sessions internally.
# This prevents holding a connection during embed_knowledge_base_metadata()
# which makes external embedding API calls (1-5+ seconds).
knowledge = Knowledges.get_knowledge_by_id(id=id)
knowledge = await Knowledges.get_knowledge_by_id(id=id)
if not knowledge:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -451,7 +451,7 @@ async def update_knowledge_by_id(
# Is the user the original creator, in a group with write access, or an admin
if (
knowledge.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='knowledge',
resource_id=knowledge.id,
@@ -464,7 +464,7 @@ async def update_knowledge_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -472,7 +472,7 @@ async def update_knowledge_by_id(
'sharing.public_knowledge',
)
knowledge = Knowledges.update_knowledge_by_id(id=id, form_data=form_data)
knowledge = await Knowledges.update_knowledge_by_id(id=id, form_data=form_data)
if knowledge:
# Re-embed knowledge base for semantic search
await embed_knowledge_base_metadata(
@@ -483,7 +483,7 @@ async def update_knowledge_by_id(
)
return KnowledgeFilesResponse(
**knowledge.model_dump(),
files=Knowledges.get_file_metadatas_by_id(knowledge.id),
files=await Knowledges.get_file_metadatas_by_id(knowledge.id),
)
else:
raise HTTPException(
@@ -507,9 +507,9 @@ async def update_knowledge_access_by_id(
id: str,
form_data: KnowledgeAccessGrantsForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
if not knowledge:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -518,7 +518,7 @@ async def update_knowledge_access_by_id(
if (
knowledge.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='knowledge',
resource_id=knowledge.id,
@@ -532,7 +532,7 @@ async def update_knowledge_access_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -540,11 +540,11 @@ async def update_knowledge_access_by_id(
'sharing.public_knowledge',
)
AccessGrants.set_access_grants('knowledge', id, form_data.access_grants, db=db)
await AccessGrants.set_access_grants('knowledge', id, form_data.access_grants, db=db)
return KnowledgeFilesResponse(
**Knowledges.get_knowledge_by_id(id=id, db=db).model_dump(),
files=Knowledges.get_file_metadatas_by_id(id, db=db),
**await Knowledges.get_knowledge_by_id(id=id, db=db).model_dump(),
files=await Knowledges.get_file_metadatas_by_id(id, db=db),
)
@@ -562,9 +562,9 @@ async def get_knowledge_files_by_id(
direction: Optional[str] = None,
page: Optional[int] = 1,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
if not knowledge:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -574,7 +574,7 @@ async def get_knowledge_files_by_id(
if not (
user.role == 'admin'
or knowledge.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='knowledge',
resource_id=knowledge.id,
@@ -602,7 +602,7 @@ async def get_knowledge_files_by_id(
if direction:
filter['direction'] = direction
return Knowledges.search_files_by_id(id, user.id, filter=filter, skip=skip, limit=limit, db=db)
return await Knowledges.search_files_by_id(id, user.id, filter=filter, skip=skip, limit=limit, db=db)
############################
@@ -615,14 +615,14 @@ class KnowledgeFileIdForm(BaseModel):
@router.post('/{id}/file/add', response_model=Optional[KnowledgeFilesResponse])
def add_file_to_knowledge_by_id(
async def add_file_to_knowledge_by_id(
request: Request,
id: str,
form_data: KnowledgeFileIdForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
if not knowledge:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -631,7 +631,7 @@ def add_file_to_knowledge_by_id(
if (
knowledge.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='knowledge',
resource_id=knowledge.id,
@@ -645,7 +645,7 @@ def add_file_to_knowledge_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
file = Files.get_file_by_id(form_data.file_id, db=db)
file = await Files.get_file_by_id(form_data.file_id, db=db)
if not file:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -667,7 +667,7 @@ def add_file_to_knowledge_by_id(
)
# Add file to knowledge base
Knowledges.add_file_to_knowledge_by_id(knowledge_id=id, file_id=form_data.file_id, user_id=user.id, db=db)
await Knowledges.add_file_to_knowledge_by_id(knowledge_id=id, file_id=form_data.file_id, user_id=user.id, db=db)
except Exception as e:
log.debug(e)
raise HTTPException(
@@ -678,7 +678,7 @@ def add_file_to_knowledge_by_id(
if knowledge:
return KnowledgeFilesResponse(
**knowledge.model_dump(),
files=Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
files=await Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
)
else:
raise HTTPException(
@@ -688,14 +688,14 @@ def add_file_to_knowledge_by_id(
@router.post('/{id}/file/update', response_model=Optional[KnowledgeFilesResponse])
def update_file_from_knowledge_by_id(
async def update_file_from_knowledge_by_id(
request: Request,
id: str,
form_data: KnowledgeFileIdForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
if not knowledge:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -704,7 +704,7 @@ def update_file_from_knowledge_by_id(
if (
knowledge.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='knowledge',
resource_id=knowledge.id,
@@ -718,7 +718,7 @@ def update_file_from_knowledge_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
file = Files.get_file_by_id(form_data.file_id, db=db)
file = await Files.get_file_by_id(form_data.file_id, db=db)
if not file:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -726,7 +726,7 @@ def update_file_from_knowledge_by_id(
)
# Validate the file actually belongs to this knowledge base
if not Knowledges.has_file(knowledge_id=id, file_id=form_data.file_id, db=db):
if not await Knowledges.has_file(knowledge_id=id, file_id=form_data.file_id, db=db):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.NOT_FOUND,
@@ -752,7 +752,7 @@ def update_file_from_knowledge_by_id(
if knowledge:
return KnowledgeFilesResponse(
**knowledge.model_dump(),
files=Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
files=await Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
)
else:
raise HTTPException(
@@ -767,14 +767,14 @@ def update_file_from_knowledge_by_id(
@router.post('/{id}/file/remove', response_model=Optional[KnowledgeFilesResponse])
def remove_file_from_knowledge_by_id(
async def remove_file_from_knowledge_by_id(
id: str,
form_data: KnowledgeFileIdForm,
delete_file: bool = Query(True),
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
if not knowledge:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -783,7 +783,7 @@ def remove_file_from_knowledge_by_id(
if (
knowledge.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='knowledge',
resource_id=knowledge.id,
@@ -797,7 +797,7 @@ def remove_file_from_knowledge_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
file = Files.get_file_by_id(form_data.file_id, db=db)
file = await Files.get_file_by_id(form_data.file_id, db=db)
if not file:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -805,13 +805,13 @@ def remove_file_from_knowledge_by_id(
)
# Validate the file actually belongs to this knowledge base
if not Knowledges.has_file(knowledge_id=id, file_id=form_data.file_id, db=db):
if not await Knowledges.has_file(knowledge_id=id, file_id=form_data.file_id, db=db):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.NOT_FOUND,
)
Knowledges.remove_file_from_knowledge_by_id(knowledge_id=id, file_id=form_data.file_id, db=db)
await Knowledges.remove_file_from_knowledge_by_id(knowledge_id=id, file_id=form_data.file_id, db=db)
# Remove content from the vector database
try:
@@ -839,12 +839,12 @@ def remove_file_from_knowledge_by_id(
pass
# Delete file from database
Files.delete_file_by_id(form_data.file_id, db=db)
await Files.delete_file_by_id(form_data.file_id, db=db)
if knowledge:
return KnowledgeFilesResponse(
**knowledge.model_dump(),
files=Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
files=await Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
)
else:
raise HTTPException(
@@ -859,8 +859,8 @@ def remove_file_from_knowledge_by_id(
@router.delete('/{id}/delete', response_model=bool)
async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
if not knowledge:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -869,7 +869,7 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: S
if (
knowledge.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='knowledge',
resource_id=knowledge.id,
@@ -886,7 +886,7 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: S
log.info(f'Deleting knowledge base: {id} (name: {knowledge.name})')
# Get all models
models = Models.get_all_models(db=db)
models = await Models.get_all_models(db=db)
log.info(f'Found {len(models)} models to check for knowledge base {id}')
# Update models that reference this knowledge base
@@ -910,7 +910,7 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: S
access_grants=model.access_grants,
is_active=model.is_active,
)
Models.update_model_by_id(model.id, model_form, db=db)
await Models.update_model_by_id(model.id, model_form, db=db)
# Clean up vector DB
try:
@@ -922,7 +922,7 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: S
# Remove knowledge base embedding
remove_knowledge_base_metadata_embedding(id)
result = Knowledges.delete_knowledge_by_id(id=id, db=db)
result = await Knowledges.delete_knowledge_by_id(id=id, db=db)
return result
@@ -932,8 +932,8 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: S
@router.post('/{id}/reset', response_model=Optional[KnowledgeResponse])
async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
if not knowledge:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -942,7 +942,7 @@ async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user), db: Se
if (
knowledge.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='knowledge',
resource_id=knowledge.id,
@@ -977,12 +977,12 @@ async def add_files_to_knowledge_batch(
id: str,
form_data: list[KnowledgeFileIdForm],
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""
Add multiple files to a knowledge base
"""
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
if not knowledge:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -991,7 +991,7 @@ async def add_files_to_knowledge_batch(
if (
knowledge.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='knowledge',
resource_id=knowledge.id,
@@ -1008,7 +1008,7 @@ async def add_files_to_knowledge_batch(
# Batch-fetch all files to avoid N+1 queries
log.info(f'files/batch/add - {len(form_data)} files')
file_ids = [form.file_id for form in form_data]
files = Files.get_files_by_ids(file_ids, db=db)
files = await Files.get_files_by_ids(file_ids, db=db)
# Verify all requested files were found
found_ids = {file.id for file in files}
@@ -1034,14 +1034,14 @@ async def add_files_to_knowledge_batch(
# Only add files that were successfully processed
successful_file_ids = [r.file_id for r in result.results if r.status == 'completed']
for file_id in successful_file_ids:
Knowledges.add_file_to_knowledge_by_id(knowledge_id=id, file_id=file_id, user_id=user.id, db=db)
await Knowledges.add_file_to_knowledge_by_id(knowledge_id=id, file_id=file_id, user_id=user.id, db=db)
# If there were any errors, include them in the response
if result.errors:
error_details = [f'{err.file_id}: {err.error}' for err in result.errors]
return KnowledgeFilesResponse(
**knowledge.model_dump(),
files=Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
files=await Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
warnings={
'message': 'Some files failed to process',
'errors': error_details,
@@ -1050,7 +1050,7 @@ async def add_files_to_knowledge_batch(
return KnowledgeFilesResponse(
**knowledge.model_dump(),
files=Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
files=await Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
)
@@ -1060,20 +1060,20 @@ async def add_files_to_knowledge_batch(
@router.get('/{id}/export')
async def export_knowledge_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
async def export_knowledge_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
"""
Export a knowledge base as a zip file containing .txt files.
Admin only.
"""
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
if not knowledge:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
files = Knowledges.get_files_by_id(id, db=db)
files = await Knowledges.get_files_by_id(id, db=db)
# Create zip file in memory
zip_buffer = io.BytesIO()
+23 -23
View File
@@ -7,8 +7,8 @@ from typing import Optional
from open_webui.models.memories import Memories, MemoryModel
from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT
from open_webui.utils.auth import get_verified_user
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.utils.access_control import has_permission
from open_webui.constants import ERROR_MESSAGES
@@ -29,7 +29,7 @@ router = APIRouter()
async def get_memories(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if not request.app.state.config.ENABLE_MEMORIES:
raise HTTPException(
@@ -37,13 +37,13 @@ async def get_memories(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
return Memories.get_memories_by_user_id(user.id, db=db)
return await Memories.get_memories_by_user_id(user.id, db=db)
############################
@@ -65,7 +65,7 @@ async def add_memory(
form_data: AddMemoryForm,
user=Depends(get_verified_user),
):
# NOTE: We intentionally do NOT use Depends(get_session) here.
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
# Database operations (insert_new_memory) manage their own short-lived sessions.
# This prevents holding a connection during EMBEDDING_FUNCTION()
# which makes external embedding API calls (1-5+ seconds).
@@ -75,13 +75,13 @@ async def add_memory(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
memory = Memories.insert_new_memory(user.id, form_data.content)
memory = await Memories.insert_new_memory(user.id, form_data.content)
vector = await request.app.state.EMBEDDING_FUNCTION(memory.content, user=user)
@@ -116,7 +116,7 @@ async def query_memory(
form_data: QueryMemoryForm,
user=Depends(get_verified_user),
):
# NOTE: We intentionally do NOT use Depends(get_session) here.
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
# Database operations (get_memories_by_user_id) manage their own short-lived sessions.
# This prevents holding a connection during EMBEDDING_FUNCTION()
# which makes external embedding API calls (1-5+ seconds).
@@ -126,13 +126,13 @@ async def query_memory(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
memories = Memories.get_memories_by_user_id(user.id)
memories = await Memories.get_memories_by_user_id(user.id)
if not memories:
raise HTTPException(status_code=404, detail='No memories found for user')
@@ -157,7 +157,7 @@ async def reset_memory_from_vector_db(
):
"""Reset user's memory vector embeddings.
CRITICAL: We intentionally do NOT use Depends(get_session) here.
CRITICAL: We intentionally do NOT use Depends(get_async_session) here.
This endpoint generates embeddings for ALL user memories in parallel using
asyncio.gather(). A user with 100 memories would trigger 100 embedding API
calls simultaneously. With a session held, this could block a connection
@@ -169,7 +169,7 @@ async def reset_memory_from_vector_db(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@@ -177,7 +177,7 @@ async def reset_memory_from_vector_db(
VECTOR_DB_CLIENT.delete_collection(f'user-memory-{user.id}')
memories = Memories.get_memories_by_user_id(user.id)
memories = await Memories.get_memories_by_user_id(user.id)
# Generate vectors in parallel
vectors = await asyncio.gather(
@@ -212,7 +212,7 @@ async def reset_memory_from_vector_db(
async def delete_memory_by_user_id(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if not request.app.state.config.ENABLE_MEMORIES:
raise HTTPException(
@@ -220,13 +220,13 @@ async def delete_memory_by_user_id(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
result = Memories.delete_memories_by_user_id(user.id, db=db)
result = await Memories.delete_memories_by_user_id(user.id, db=db)
if result:
try:
@@ -250,7 +250,7 @@ async def update_memory_by_id(
form_data: MemoryUpdateModel,
user=Depends(get_verified_user),
):
# NOTE: We intentionally do NOT use Depends(get_session) here.
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
# Database operations (update_memory_by_id_and_user_id) manage their own
# short-lived sessions. This prevents holding a connection during
# EMBEDDING_FUNCTION() which makes external API calls (1-5+ seconds).
@@ -260,13 +260,13 @@ async def update_memory_by_id(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
memory = Memories.update_memory_by_id_and_user_id(memory_id, user.id, form_data.content)
memory = await Memories.update_memory_by_id_and_user_id(memory_id, user.id, form_data.content)
if memory is None:
raise HTTPException(status_code=404, detail='Memory not found')
@@ -301,7 +301,7 @@ async def delete_memory_by_id(
memory_id: str,
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if not request.app.state.config.ENABLE_MEMORIES:
raise HTTPException(
@@ -309,13 +309,13 @@ async def delete_memory_by_id(
detail=ERROR_MESSAGES.NOT_FOUND,
)
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
result = Memories.delete_memory_by_id_and_user_id(memory_id, user.id, db=db)
result = await Memories.delete_memory_by_id_and_user_id(memory_id, user.id, db=db)
if result:
VECTOR_DB_CLIENT.delete(collection_name=f'user-memory-{user.id}', ids=[memory_id])
+56 -56
View File
@@ -35,8 +35,8 @@ from fastapi.responses import FileResponse, StreamingResponse
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL, STATIC_DIR
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -66,7 +66,7 @@ async def get_models(
direction: Optional[str] = None,
page: Optional[int] = 1,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
limit = PAGE_ITEM_COUNT
@@ -86,7 +86,7 @@ async def get_models(
filter['direction'] = direction
# Pre-fetch user group IDs once - used for both filter and write_access check
groups = Groups.get_groups_by_member_id(user.id, db=db)
groups = await Groups.get_groups_by_member_id(user.id, db=db)
user_group_ids = {group.id for group in groups}
if not user.role == 'admin' or not BYPASS_ADMIN_ACCESS_CONTROL:
@@ -95,11 +95,11 @@ async def get_models(
filter['user_id'] = user.id
result = Models.search_models(user.id, filter=filter, skip=skip, limit=limit, db=db)
result = await Models.search_models(user.id, filter=filter, skip=skip, limit=limit, db=db)
# Batch-fetch writable model IDs in a single query instead of N has_access calls
model_ids = [model.id for model in result.items]
writable_model_ids = AccessGrants.get_accessible_resource_ids(
writable_model_ids = await AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type='model',
resource_ids=model_ids,
@@ -130,8 +130,8 @@ async def get_models(
@router.get('/base', response_model=list[ModelResponse])
async def get_base_models(user=Depends(get_admin_user), db: Session = Depends(get_session)):
return Models.get_base_models(db=db)
async def get_base_models(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
return await Models.get_base_models(db=db)
###########################
@@ -140,11 +140,11 @@ async def get_base_models(user=Depends(get_admin_user), db: Session = Depends(ge
@router.get('/tags', response_model=list[str])
async def get_model_tags(user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def get_model_tags(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
models = Models.get_models(db=db)
models = await Models.get_models(db=db)
else:
models = Models.get_models_by_user_id(user.id, db=db)
models = await Models.get_models_by_user_id(user.id, db=db)
tags_set = set()
for model in models:
@@ -172,9 +172,9 @@ async def create_new_model(
request: Request,
form_data: ModelForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id, 'workspace.models', request.app.state.config.USER_PERMISSIONS, db=db
):
raise HTTPException(
@@ -182,7 +182,7 @@ async def create_new_model(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
model = Models.get_model_by_id(form_data.id, db=db)
model = await Models.get_model_by_id(form_data.id, db=db)
if model:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -196,7 +196,7 @@ async def create_new_model(
)
else:
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -204,7 +204,7 @@ async def create_new_model(
'sharing.public_models',
)
model = Models.insert_new_model(form_data, user.id, db=db)
model = await Models.insert_new_model(form_data, user.id, db=db)
if model:
return model
else:
@@ -223,9 +223,9 @@ async def create_new_model(
async def export_models(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id,
'workspace.models_export',
request.app.state.config.USER_PERMISSIONS,
@@ -237,9 +237,9 @@ async def export_models(
)
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
return Models.get_models(db=db)
return await Models.get_models(db=db)
else:
return Models.get_models_by_user_id(user.id, db=db)
return await Models.get_models_by_user_id(user.id, db=db)
############################
@@ -256,9 +256,9 @@ async def import_models(
request: Request,
user=Depends(get_verified_user),
form_data: ModelsImportForm = (...),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id,
'workspace.models_import',
request.app.state.config.USER_PERMISSIONS,
@@ -278,7 +278,7 @@ async def import_models(
if model_data.get('id') and is_valid_model_id(model_data.get('id'))
]
existing_models = {
model.id: model for model in (Models.get_models_by_ids(model_ids, db=db) if model_ids else [])
model.id: model for model in (await Models.get_models_by_ids(model_ids, db=db) if model_ids else [])
}
for model_data in data:
@@ -293,13 +293,13 @@ async def import_models(
model_data['params'] = model_data.get('params', {})
updated_model = ModelForm(**{**existing_model.model_dump(), **model_data})
Models.update_model_by_id(model_id, updated_model, db=db)
await Models.update_model_by_id(model_id, updated_model, db=db)
else:
# Insert new model
model_data['meta'] = model_data.get('meta', {})
model_data['params'] = model_data.get('params', {})
new_model = ModelForm(**model_data)
Models.insert_new_model(user_id=user.id, form_data=new_model, db=db)
await Models.insert_new_model(user_id=user.id, form_data=new_model, db=db)
return True
else:
raise HTTPException(status_code=400, detail='Invalid JSON format')
@@ -322,9 +322,9 @@ async def sync_models(
request: Request,
form_data: SyncModelsForm,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
return Models.sync_models(user.id, form_data.models, db=db)
return await Models.sync_models(user.id, form_data.models, db=db)
###########################
@@ -338,13 +338,13 @@ class ModelIdForm(BaseModel):
# Note: We're not using the typical url path param here, but instead using a query parameter to allow '/' in the id
@router.get('/model', response_model=Optional[ModelAccessResponse])
async def get_model_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
model = Models.get_model_by_id(id, db=db)
async def get_model_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
model = await Models.get_model_by_id(id, db=db)
if model:
if (
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
or model.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='model',
resource_id=model.id,
@@ -357,7 +357,7 @@ async def get_model_by_id(id: str, user=Depends(get_verified_user), db: Session
write_access=(
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
or user.id == model.user_id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='model',
resource_id=model.id,
@@ -384,8 +384,8 @@ async def get_model_by_id(id: str, user=Depends(get_verified_user), db: Session
@router.get('/model/profile/image')
def get_model_profile_image(id: str, user=Depends(get_verified_user)):
model = Models.get_model_by_id(id)
async def get_model_profile_image(id: str, user=Depends(get_verified_user)):
model = await Models.get_model_by_id(id)
if model:
etag = f'"{model.updated_at}"' if model.updated_at else None
@@ -426,13 +426,13 @@ def get_model_profile_image(id: str, user=Depends(get_verified_user)):
@router.post('/model/toggle', response_model=Optional[ModelResponse])
async def toggle_model_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
model = Models.get_model_by_id(id, db=db)
async def toggle_model_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
model = await Models.get_model_by_id(id, db=db)
if model:
if (
user.role == 'admin'
or model.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='model',
resource_id=model.id,
@@ -440,7 +440,7 @@ async def toggle_model_by_id(id: str, user=Depends(get_verified_user), db: Sessi
db=db,
)
):
model = Models.toggle_model_by_id(id, db=db)
model = await Models.toggle_model_by_id(id, db=db)
if model:
return model
@@ -471,9 +471,9 @@ async def update_model_by_id(
request: Request,
form_data: ModelForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
model = Models.get_model_by_id(form_data.id, db=db)
model = await Models.get_model_by_id(form_data.id, db=db)
if not model:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -482,7 +482,7 @@ async def update_model_by_id(
if (
model.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='model',
resource_id=model.id,
@@ -496,7 +496,7 @@ async def update_model_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -504,7 +504,7 @@ async def update_model_by_id(
'sharing.public_models',
)
model = Models.update_model_by_id(form_data.id, ModelForm(**form_data.model_dump()), db=db)
model = await Models.update_model_by_id(form_data.id, ModelForm(**form_data.model_dump()), db=db)
return model
@@ -524,9 +524,9 @@ async def update_model_access_by_id(
request: Request,
form_data: ModelAccessGrantsForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
model = Models.get_model_by_id(form_data.id, db=db)
model = await Models.get_model_by_id(form_data.id, db=db)
# Non-preset models (e.g. direct Ollama/OpenAI models) may not have a DB
# entry yet. Create a minimal one so access grants can be stored.
@@ -536,7 +536,7 @@ async def update_model_access_by_id(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
model = Models.insert_new_model(
model = await Models.insert_new_model(
ModelForm(
id=form_data.id,
name=form_data.name or form_data.id,
@@ -554,7 +554,7 @@ async def update_model_access_by_id(
if (
model.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='model',
resource_id=model.id,
@@ -568,7 +568,7 @@ async def update_model_access_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -576,11 +576,11 @@ async def update_model_access_by_id(
'sharing.public_models',
)
AccessGrants.set_access_grants('model', form_data.id, form_data.access_grants, db=db)
await AccessGrants.set_access_grants('model', form_data.id, form_data.access_grants, db=db)
Models.update_model_updated_at_by_id(form_data.id, db=db)
await Models.update_model_updated_at_by_id(form_data.id, db=db)
return Models.get_model_by_id(form_data.id, db=db)
return await Models.get_model_by_id(form_data.id, db=db)
############################
@@ -592,9 +592,9 @@ async def update_model_access_by_id(
async def delete_model_by_id(
form_data: ModelIdForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
model = Models.get_model_by_id(form_data.id, db=db)
model = await Models.get_model_by_id(form_data.id, db=db)
if not model:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -604,7 +604,7 @@ async def delete_model_by_id(
if (
user.role != 'admin'
and model.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='model',
resource_id=model.id,
@@ -617,11 +617,11 @@ async def delete_model_by_id(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
result = Models.delete_model_by_id(form_data.id, db=db)
result = await Models.delete_model_by_id(form_data.id, db=db)
return result
@router.delete('/delete/all', response_model=bool)
async def delete_all_models(user=Depends(get_admin_user), db: Session = Depends(get_session)):
result = Models.delete_all_models(db=db)
async def delete_all_models(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
result = await Models.delete_all_models(db=db)
return result
+37 -37
View File
@@ -34,8 +34,8 @@ from open_webui.utils.access_control import (
filter_allowed_access_grants,
)
from open_webui.models.access_grants import AccessGrants
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -68,9 +68,9 @@ async def get_notes(
request: Request,
page: Optional[int] = None,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
):
raise HTTPException(
@@ -84,12 +84,12 @@ async def get_notes(
limit = 60
skip = (page - 1) * limit
notes = Notes.get_notes_by_user_id(user.id, 'read', skip=skip, limit=limit, db=db)
notes = await Notes.get_notes_by_user_id(user.id, 'read', skip=skip, limit=limit, db=db)
if not notes:
return []
user_ids = list(set(note.user_id for note in notes))
users = {user.id: user for user in Users.get_users_by_user_ids(user_ids, db=db)}
users = {user.id: user for user in await Users.get_users_by_user_ids(user_ids, db=db)}
return [
NoteUserResponse(
@@ -114,9 +114,9 @@ async def search_notes(
direction: Optional[str] = None,
page: Optional[int] = 1,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
):
raise HTTPException(
@@ -143,13 +143,13 @@ async def search_notes(
filter['direction'] = direction
if not user.role == 'admin' or not BYPASS_ADMIN_ACCESS_CONTROL:
groups = Groups.get_groups_by_member_id(user.id, db=db)
groups = await Groups.get_groups_by_member_id(user.id, db=db)
if groups:
filter['group_ids'] = [group.id for group in groups]
filter['user_id'] = user.id
result = Notes.search_notes(user.id, filter, skip=skip, limit=limit, db=db)
result = await Notes.search_notes(user.id, filter, skip=skip, limit=limit, db=db)
for note in result.items:
note.data = _truncate_note_data(note.data)
return result
@@ -165,9 +165,9 @@ async def create_new_note(
request: Request,
form_data: NoteForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
):
raise HTTPException(
@@ -175,7 +175,7 @@ async def create_new_note(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -185,7 +185,7 @@ async def create_new_note(
)
try:
note = Notes.insert_new_note(user.id, form_data, db=db)
note = await Notes.insert_new_note(user.id, form_data, db=db)
return note
except Exception as e:
log.exception(e)
@@ -206,9 +206,9 @@ async def get_note_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
):
raise HTTPException(
@@ -216,14 +216,14 @@ async def get_note_by_id(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
note = Notes.get_note_by_id(id, db=db)
note = await Notes.get_note_by_id(id, db=db)
if not note:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
if user.role != 'admin' and (
user.id != note.user_id
and (
not AccessGrants.has_access(
not await AccessGrants.has_access(
user_id=user.id,
resource_type='note',
resource_id=note.id,
@@ -237,7 +237,7 @@ async def get_note_by_id(
write_access = (
user.role == 'admin'
or (user.id == note.user_id)
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='note',
resource_id=note.id,
@@ -261,9 +261,9 @@ async def update_note_by_id(
id: str,
form_data: NoteForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
):
raise HTTPException(
@@ -271,13 +271,13 @@ async def update_note_by_id(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
note = Notes.get_note_by_id(id, db=db)
note = await Notes.get_note_by_id(id, db=db)
if not note:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
if user.role != 'admin' and (
user.id != note.user_id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='note',
resource_id=note.id,
@@ -287,7 +287,7 @@ async def update_note_by_id(
):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -297,7 +297,7 @@ async def update_note_by_id(
)
try:
note = Notes.update_note_by_id(id, form_data, db=db)
note = await Notes.update_note_by_id(id, form_data, db=db)
await sio.emit(
'note-events',
note.model_dump(),
@@ -325,9 +325,9 @@ async def update_note_access_by_id(
id: str,
form_data: NoteAccessGrantsForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
):
raise HTTPException(
@@ -335,13 +335,13 @@ async def update_note_access_by_id(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
note = Notes.get_note_by_id(id, db=db)
note = await Notes.get_note_by_id(id, db=db)
if not note:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
if user.role != 'admin' and (
user.id != note.user_id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='note',
resource_id=note.id,
@@ -351,7 +351,7 @@ async def update_note_access_by_id(
):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -359,9 +359,9 @@ async def update_note_access_by_id(
'sharing.public_notes',
)
AccessGrants.set_access_grants('note', id, form_data.access_grants, db=db)
await AccessGrants.set_access_grants('note', id, form_data.access_grants, db=db)
return Notes.get_note_by_id(id, db=db)
return await Notes.get_note_by_id(id, db=db)
############################
@@ -374,9 +374,9 @@ async def delete_note_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
):
raise HTTPException(
@@ -384,13 +384,13 @@ async def delete_note_by_id(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
note = Notes.get_note_by_id(id, db=db)
note = await Notes.get_note_by_id(id, db=db)
if not note:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
if user.role != 'admin' and (
user.id != note.user_id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='note',
resource_id=note.id,
@@ -401,7 +401,7 @@ async def delete_note_by_id(
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
try:
note = Notes.delete_note_by_id(id, db=db)
note = await Notes.delete_note_by_id(id, db=db)
return True
except Exception as e:
log.exception(e)
+31 -31
View File
@@ -39,9 +39,9 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, ConfigDict, validator
from sqlalchemy.orm import Session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.internal.db import get_session
from open_webui.internal.db import get_async_session
from open_webui.models.models import Models
@@ -398,11 +398,11 @@ async def get_all_models(request: Request, user: UserModel = None):
async def get_filtered_models(models, user, db=None):
# Filter models based on user access control
model_ids = [model['model'] for model in models.get('models', [])]
model_infos = {model_info.id: model_info for model_info in Models.get_models_by_ids(model_ids, db=db)}
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id, db=db)}
model_infos = {model_info.id: model_info for model_info in await Models.get_models_by_ids(model_ids, db=db)}
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
# Batch-fetch accessible resource IDs in a single query instead of N has_access calls
accessible_model_ids = AccessGrants.get_accessible_resource_ids(
accessible_model_ids = await AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type='model',
resource_ids=list(model_infos.keys()),
@@ -800,7 +800,7 @@ async def show_model_info(request: Request, form_data: ModelNameForm, user=Depen
model = form_data.get('model')
# Enforce per-model access control
check_model_access(user, Models.get_model_by_id(model), BYPASS_MODEL_ACCESS_CONTROL)
await check_model_access(user, await Models.get_model_by_id(model), BYPASS_MODEL_ACCESS_CONTROL)
await get_all_models(request, user=user)
models = request.app.state.OLLAMA_MODELS
@@ -850,7 +850,7 @@ async def embed(
log.info(f'generate_ollama_batch_embeddings {form_data}')
# Enforce per-model access control
check_model_access(user, Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
await check_model_access(user, await Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
if url_idx is None:
model = form_data.model
@@ -909,7 +909,7 @@ async def embeddings(
log.info(f'generate_ollama_embeddings {form_data}')
# Enforce per-model access control
check_model_access(user, Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
await check_model_access(user, await Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
if url_idx is None:
model = form_data.model
@@ -974,7 +974,7 @@ async def generate_completion(
raise HTTPException(status_code=503, detail='Ollama API is disabled')
# Enforce per-model access control
check_model_access(user, Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
await check_model_access(user, await Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
if url_idx is None:
await get_all_models(request, user=user)
@@ -1064,7 +1064,7 @@ async def generate_chat_completion(
if not request.app.state.config.ENABLE_OLLAMA_API:
raise HTTPException(status_code=503, detail='Ollama API is disabled')
# NOTE: We intentionally do NOT use Depends(get_session) here.
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
# Database operations (get_model_by_id, AccessGrants.has_access) manage their own short-lived sessions.
# This prevents holding a connection during the entire LLM call (30-60+ seconds),
# which would exhaust the connection pool under concurrent load.
@@ -1093,7 +1093,7 @@ async def generate_chat_completion(
del payload['metadata']
model_id = payload['model']
model_info = Models.get_model_by_id(model_id)
model_info = await Models.get_model_by_id(model_id)
if model_info:
if model_info.base_model_id:
@@ -1111,9 +1111,9 @@ async def generate_chat_completion(
if not bypass_system_prompt:
payload = apply_system_prompt_to_body(system, payload, metadata, user)
check_model_access(user, model_info, bypass_filter)
await check_model_access(user, model_info, bypass_filter)
else:
check_model_access(user, None, bypass_filter)
await check_model_access(user, None, bypass_filter)
url, url_idx = await get_ollama_url(request, payload['model'], url_idx)
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
@@ -1171,7 +1171,7 @@ async def generate_openai_completion(
url_idx: Optional[int] = None,
user=Depends(get_verified_user),
):
# NOTE: We intentionally do NOT use Depends(get_session) here.
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
# Database operations (get_model_by_id, AccessGrants.has_access) manage their own short-lived sessions.
# This prevents holding a connection during the entire LLM call (30-60+ seconds),
# which would exhaust the connection pool under concurrent load.
@@ -1191,7 +1191,7 @@ async def generate_openai_completion(
del payload['metadata']
model_id = form_data.model
model_info = Models.get_model_by_id(model_id)
model_info = await Models.get_model_by_id(model_id)
if model_info:
if model_info.base_model_id:
payload['model'] = model_info.base_model_id
@@ -1200,9 +1200,9 @@ async def generate_openai_completion(
if params:
payload = apply_model_params_to_body_openai(params, payload)
check_model_access(user, model_info)
await check_model_access(user, model_info)
else:
check_model_access(user, None)
await check_model_access(user, None)
url, url_idx = await get_ollama_url(request, payload['model'], url_idx)
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
@@ -1233,7 +1233,7 @@ async def generate_openai_chat_completion(
url_idx: Optional[int] = None,
user=Depends(get_verified_user),
):
# NOTE: We intentionally do NOT use Depends(get_session) here.
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
# Database operations (get_model_by_id, AccessGrants.has_access) manage their own short-lived sessions.
# This prevents holding a connection during the entire LLM call (30-60+ seconds),
# which would exhaust the connection pool under concurrent load.
@@ -1253,7 +1253,7 @@ async def generate_openai_chat_completion(
del payload['metadata']
model_id = completion_form.model
model_info = Models.get_model_by_id(model_id)
model_info = await Models.get_model_by_id(model_id)
if model_info:
if model_info.base_model_id:
payload['model'] = model_info.base_model_id
@@ -1266,9 +1266,9 @@ async def generate_openai_chat_completion(
payload = apply_model_params_to_body_openai(params, payload)
payload = apply_system_prompt_to_body(system, payload, metadata, user)
check_model_access(user, model_info)
await check_model_access(user, model_info)
else:
check_model_access(user, None)
await check_model_access(user, None)
url, url_idx = await get_ollama_url(request, payload['model'], url_idx)
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
@@ -1313,14 +1313,14 @@ async def generate_anthropic_messages(
payload = {**form_data}
model_id = payload.get('model', '')
model_info = Models.get_model_by_id(model_id)
model_info = await Models.get_model_by_id(model_id)
if model_info:
if model_info.base_model_id:
payload['model'] = model_info.base_model_id
check_model_access(user, model_info)
await check_model_access(user, model_info)
else:
check_model_access(user, None)
await check_model_access(user, None)
url, url_idx = await get_ollama_url(request, payload['model'], url_idx)
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
@@ -1371,17 +1371,17 @@ async def generate_responses(
payload = form_data.model_dump()
model_id = form_data.model
model_info = Models.get_model_by_id(model_id)
model_info = await Models.get_model_by_id(model_id)
if model_info:
if model_info.base_model_id:
payload['model'] = model_info.base_model_id
# Check if user has access to the model
if user.role == 'user':
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id)}
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id)}
if not (
user.id == model_info.user_id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='model',
resource_id=model_info.id,
@@ -1426,7 +1426,7 @@ async def get_openai_models(
request: Request,
url_idx: Optional[int] = None,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
models = []
if url_idx is None:
@@ -1458,11 +1458,11 @@ async def get_openai_models(
if user.role == 'user' and not BYPASS_MODEL_ACCESS_CONTROL:
# Filter models based on user access control
model_ids = [model['id'] for model in models]
model_infos = {model_info.id: model_info for model_info in Models.get_models_by_ids(model_ids, db=db)}
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id, db=db)}
model_infos = {model_info.id: model_info for model_info in await Models.get_models_by_ids(model_ids, db=db)}
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
# Batch-fetch accessible resource IDs in a single query instead of N has_access calls
accessible_model_ids = AccessGrants.get_accessible_resource_ids(
accessible_model_ids = await AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type='model',
resource_ids=list(model_infos.keys()),
+10 -10
View File
@@ -21,9 +21,9 @@ from fastapi.responses import (
)
from pydantic import BaseModel, ConfigDict
from sqlalchemy.orm import Session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.internal.db import get_session
from open_webui.internal.db import get_async_session
from open_webui.models.models import Models
from open_webui.models.access_grants import AccessGrants
@@ -450,11 +450,11 @@ async def get_all_models_responses(request: Request, user: UserModel) -> list:
async def get_filtered_models(models, user, db=None):
# Filter models based on user access control
model_ids = [model['id'] for model in models.get('data', [])]
model_infos = {model_info.id: model_info for model_info in Models.get_models_by_ids(model_ids, db=db)}
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id, db=db)}
model_infos = {model_info.id: model_info for model_info in await Models.get_models_by_ids(model_ids, db=db)}
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
# Batch-fetch accessible resource IDs in a single query instead of N has_access calls
accessible_model_ids = AccessGrants.get_accessible_resource_ids(
accessible_model_ids = await AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type='model',
resource_ids=list(model_infos.keys()),
@@ -1025,7 +1025,7 @@ async def generate_chat_completion(
user=Depends(get_verified_user),
bypass_system_prompt: bool = False,
):
# NOTE: We intentionally do NOT use Depends(get_session) here.
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
# Database operations (get_model_by_id, AccessGrants.has_access) manage their own short-lived sessions.
# This prevents holding a connection during the entire LLM call (30-60+ seconds),
# which would exhaust the connection pool under concurrent load.
@@ -1043,7 +1043,7 @@ async def generate_chat_completion(
metadata = payload.pop('metadata', None)
model_id = form_data.get('model')
model_info = Models.get_model_by_id(model_id)
model_info = await Models.get_model_by_id(model_id)
# Check model info and override the payload
if model_info:
@@ -1063,9 +1063,9 @@ async def generate_chat_completion(
if not bypass_system_prompt:
payload = apply_system_prompt_to_body(system, payload, metadata, user)
check_model_access(user, model_info, bypass_filter)
await check_model_access(user, model_info, bypass_filter)
else:
check_model_access(user, None, bypass_filter)
await check_model_access(user, None, bypass_filter)
# Check if model is already in app state cache to avoid expensive get_all_models() call
models = request.app.state.OPENAI_MODELS
@@ -1344,7 +1344,7 @@ async def responses(
model_id = form_data.model
# Enforce per-model access control
check_model_access(user, Models.get_model_by_id(model_id), BYPASS_MODEL_ACCESS_CONTROL)
await check_model_access(user, await Models.get_model_by_id(model_id), BYPASS_MODEL_ACCESS_CONTROL)
body = json.dumps(payload)
+71 -71
View File
@@ -20,8 +20,8 @@ from open_webui.constants import ERROR_MESSAGES
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
from open_webui.internal.db import get_async_session
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel
@@ -48,21 +48,21 @@ PAGE_ITEM_COUNT = 30
@router.get('/', response_model=list[PromptModel])
async def get_prompts(user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def get_prompts(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
prompts = Prompts.get_prompts(db=db)
prompts = await Prompts.get_prompts(db=db)
else:
prompts = Prompts.get_prompts_by_user_id(user.id, 'read', db=db)
prompts = await Prompts.get_prompts_by_user_id(user.id, 'read', db=db)
return prompts
@router.get('/tags', response_model=list[str])
async def get_prompt_tags(user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def get_prompt_tags(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
return Prompts.get_tags(db=db)
return await Prompts.get_tags(db=db)
else:
prompts = Prompts.get_prompts_by_user_id(user.id, 'read', db=db)
prompts = await Prompts.get_prompts_by_user_id(user.id, 'read', db=db)
tags = set()
for prompt in prompts:
if prompt.tags:
@@ -79,7 +79,7 @@ async def get_prompt_list(
direction: Optional[str] = None,
page: Optional[int] = 1,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
limit = PAGE_ITEM_COUNT
@@ -99,7 +99,7 @@ async def get_prompt_list(
filter['direction'] = direction
# Pre-fetch user group IDs once - used for both filter and write_access check
groups = Groups.get_groups_by_member_id(user.id, db=db)
groups = await Groups.get_groups_by_member_id(user.id, db=db)
user_group_ids = {group.id for group in groups}
if not (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL):
@@ -108,11 +108,11 @@ async def get_prompt_list(
filter['user_id'] = user.id
result = Prompts.search_prompts(user.id, filter=filter, skip=skip, limit=limit, db=db)
result = await Prompts.search_prompts(user.id, filter=filter, skip=skip, limit=limit, db=db)
# Batch-fetch writable prompt IDs in a single query instead of N has_access calls
prompt_ids = [prompt.id for prompt in result.items]
writable_prompt_ids = AccessGrants.get_accessible_resource_ids(
writable_prompt_ids = await AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type='prompt',
resource_ids=prompt_ids,
@@ -147,16 +147,16 @@ async def create_new_prompt(
request: Request,
form_data: PromptForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not (
has_permission(
await has_permission(
user.id,
'workspace.prompts',
request.app.state.config.USER_PERMISSIONS,
db=db,
)
or has_permission(
or await has_permission(
user.id,
'workspace.prompts_import',
request.app.state.config.USER_PERMISSIONS,
@@ -168,7 +168,7 @@ async def create_new_prompt(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -176,9 +176,9 @@ async def create_new_prompt(
'sharing.public_prompts',
)
prompt = Prompts.get_prompt_by_command(form_data.command, db=db)
prompt = await Prompts.get_prompt_by_command(form_data.command, db=db)
if prompt is None:
prompt = Prompts.insert_new_prompt(user.id, form_data, db=db)
prompt = await Prompts.insert_new_prompt(user.id, form_data, db=db)
if prompt:
return prompt
@@ -198,14 +198,14 @@ async def create_new_prompt(
@router.get('/command/{command}', response_model=Optional[PromptAccessResponse])
async def get_prompt_by_command(command: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
prompt = Prompts.get_prompt_by_command(command, db=db)
async def get_prompt_by_command(command: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
prompt = await Prompts.get_prompt_by_command(command, db=db)
if prompt:
if (
user.role == 'admin'
or prompt.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -218,7 +218,7 @@ async def get_prompt_by_command(command: str, user=Depends(get_verified_user), d
write_access=(
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
or user.id == prompt.user_id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -240,14 +240,14 @@ async def get_prompt_by_command(command: str, user=Depends(get_verified_user), d
@router.get('/id/{prompt_id}', response_model=Optional[PromptAccessResponse])
async def get_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
async def get_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
if prompt:
if (
user.role == 'admin'
or prompt.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -260,7 +260,7 @@ async def get_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), db:
write_access=(
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
or user.id == prompt.user_id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -287,9 +287,9 @@ async def update_prompt_by_id(
prompt_id: str,
form_data: PromptForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
if not prompt:
raise HTTPException(
@@ -300,7 +300,7 @@ async def update_prompt_by_id(
# Is the user the original creator, in a group with write access, or an admin
if (
prompt.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -316,14 +316,14 @@ async def update_prompt_by_id(
# Check for command collision if command is being changed
if form_data.command != prompt.command:
existing_prompt = Prompts.get_prompt_by_command(form_data.command, db=db)
existing_prompt = await Prompts.get_prompt_by_command(form_data.command, db=db)
if existing_prompt and existing_prompt.id != prompt.id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Command '/{form_data.command}' is already in use by another prompt",
)
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -332,7 +332,7 @@ async def update_prompt_by_id(
)
# Use the ID from the found prompt
updated_prompt = Prompts.update_prompt_by_id(prompt.id, form_data, user.id, db=db)
updated_prompt = await Prompts.update_prompt_by_id(prompt.id, form_data, user.id, db=db)
if updated_prompt:
return updated_prompt
else:
@@ -352,10 +352,10 @@ async def update_prompt_metadata(
prompt_id: str,
form_data: PromptMetadataForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Update prompt name and command only (no history created)."""
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
if not prompt:
raise HTTPException(
@@ -365,7 +365,7 @@ async def update_prompt_metadata(
if (
prompt.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -381,14 +381,14 @@ async def update_prompt_metadata(
# Check for command collision if command is being changed
if form_data.command != prompt.command:
existing_prompt = Prompts.get_prompt_by_command(form_data.command, db=db)
existing_prompt = await Prompts.get_prompt_by_command(form_data.command, db=db)
if existing_prompt and existing_prompt.id != prompt.id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Command '/{form_data.command}' is already in use",
)
updated_prompt = Prompts.update_prompt_metadata(prompt.id, form_data.name, form_data.command, form_data.tags, db=db)
updated_prompt = await Prompts.update_prompt_metadata(prompt.id, form_data.name, form_data.command, form_data.tags, db=db)
if updated_prompt:
return updated_prompt
else:
@@ -403,9 +403,9 @@ async def set_prompt_version(
prompt_id: str,
form_data: PromptVersionUpdateForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
if not prompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -414,7 +414,7 @@ async def set_prompt_version(
if (
prompt.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -428,7 +428,7 @@ async def set_prompt_version(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
updated_prompt = Prompts.update_prompt_version(prompt.id, form_data.version_id, db=db)
updated_prompt = await Prompts.update_prompt_version(prompt.id, form_data.version_id, db=db)
if updated_prompt:
return updated_prompt
else:
@@ -453,9 +453,9 @@ async def update_prompt_access_by_id(
prompt_id: str,
form_data: PromptAccessGrantsForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
if not prompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -464,7 +464,7 @@ async def update_prompt_access_by_id(
if (
prompt.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -478,7 +478,7 @@ async def update_prompt_access_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -486,9 +486,9 @@ async def update_prompt_access_by_id(
'sharing.public_prompts',
)
AccessGrants.set_access_grants('prompt', prompt_id, form_data.access_grants, db=db)
await AccessGrants.set_access_grants('prompt', prompt_id, form_data.access_grants, db=db)
return Prompts.get_prompt_by_id(prompt_id, db=db)
return await Prompts.get_prompt_by_id(prompt_id, db=db)
############################
@@ -497,8 +497,8 @@ async def update_prompt_access_by_id(
@router.post('/id/{prompt_id}/toggle', response_model=Optional[PromptModel])
async def toggle_prompt_active(prompt_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
async def toggle_prompt_active(prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
if not prompt:
raise HTTPException(
@@ -508,7 +508,7 @@ async def toggle_prompt_active(prompt_id: str, user=Depends(get_verified_user),
if (
prompt.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -522,7 +522,7 @@ async def toggle_prompt_active(prompt_id: str, user=Depends(get_verified_user),
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
result = Prompts.toggle_prompt_active(prompt.id, db=db)
result = await Prompts.toggle_prompt_active(prompt.id, db=db)
if result:
return result
raise HTTPException(
@@ -537,8 +537,8 @@ async def toggle_prompt_active(prompt_id: str, user=Depends(get_verified_user),
@router.delete('/id/{prompt_id}/delete', response_model=bool)
async def delete_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
async def delete_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
if not prompt:
raise HTTPException(
@@ -548,7 +548,7 @@ async def delete_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), d
if (
prompt.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -562,7 +562,7 @@ async def delete_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), d
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
result = Prompts.delete_prompt_by_id(prompt.id, db=db)
result = await Prompts.delete_prompt_by_id(prompt.id, db=db)
return result
@@ -576,12 +576,12 @@ async def get_prompt_history(
prompt_id: str,
page: int = 0,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get version history for a prompt."""
PAGE_SIZE = 20
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
if not prompt:
raise HTTPException(
@@ -593,7 +593,7 @@ async def get_prompt_history(
if not (
user.role == 'admin'
or prompt.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -606,7 +606,7 @@ async def get_prompt_history(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
history = PromptHistories.get_history_by_prompt_id(prompt.id, limit=PAGE_SIZE, offset=page * PAGE_SIZE, db=db)
history = await PromptHistories.get_history_by_prompt_id(prompt.id, limit=PAGE_SIZE, offset=page * PAGE_SIZE, db=db)
return history
@@ -615,10 +615,10 @@ async def get_prompt_history_entry(
prompt_id: str,
history_id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get a specific version from history."""
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
if not prompt:
raise HTTPException(
@@ -630,7 +630,7 @@ async def get_prompt_history_entry(
if not (
user.role == 'admin'
or prompt.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -643,7 +643,7 @@ async def get_prompt_history_entry(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
history_entry = PromptHistories.get_history_entry_by_id(history_id, db=db)
history_entry = await PromptHistories.get_history_entry_by_id(history_id, db=db)
if not history_entry or history_entry.prompt_id != prompt.id:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -658,10 +658,10 @@ async def delete_prompt_history_entry(
prompt_id: str,
history_id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Delete a history entry. Cannot delete the active production version."""
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
if not prompt:
raise HTTPException(
@@ -673,7 +673,7 @@ async def delete_prompt_history_entry(
if not (
user.role == 'admin'
or prompt.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -693,7 +693,7 @@ async def delete_prompt_history_entry(
detail='Cannot delete the active production version',
)
success = PromptHistories.delete_history_entry(history_id, db=db)
success = await PromptHistories.delete_history_entry(history_id, db=db)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -709,10 +709,10 @@ async def get_prompt_diff(
from_id: str,
to_id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get diff between two versions."""
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
if not prompt:
raise HTTPException(
@@ -724,7 +724,7 @@ async def get_prompt_diff(
if not (
user.role == 'admin'
or prompt.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='prompt',
resource_id=prompt.id,
@@ -737,7 +737,7 @@ async def get_prompt_diff(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
diff = PromptHistories.compute_diff(from_id, to_id, db=db)
diff = await PromptHistories.compute_diff(from_id, to_id, db=db)
if not diff:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
+28 -28
View File
@@ -40,8 +40,8 @@ from open_webui.models.files import FileModel, FileUpdateForm, Files
from open_webui.utils.access_control.files import has_access_to_file
from open_webui.models.knowledge import Knowledges
from open_webui.storage.provider import Storage
from open_webui.internal.db import get_session, get_db
from sqlalchemy.orm import Session
from open_webui.internal.db import get_async_session, get_db
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT
@@ -1526,11 +1526,11 @@ class ProcessFileForm(BaseModel):
@router.post('/process/file')
def process_file(
async def process_file(
request: Request,
form_data: ProcessFileForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""
Process a file and save its content to the vector database.
@@ -1539,9 +1539,9 @@ def process_file(
The session is committed before external API calls, and updates use a fresh session.
"""
if user.role == 'admin':
file = Files.get_file_by_id(form_data.file_id, db=db)
file = await Files.get_file_by_id(form_data.file_id, db=db)
else:
file = Files.get_file_by_id_and_user_id(form_data.file_id, user.id, db=db)
file = await Files.get_file_by_id_and_user_id(form_data.file_id, user.id, db=db)
if file:
try:
@@ -1674,7 +1674,7 @@ def process_file(
text_content = ' '.join([doc.page_content for doc in docs])
log.debug(f'text_content: {text_content}')
Files.update_file_data_by_id(
await Files.update_file_data_by_id(
file.id,
{'content': text_content},
db=db,
@@ -1682,8 +1682,8 @@ def process_file(
hash = calculate_sha256_string(text_content)
if request.app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL:
Files.update_file_data_by_id(file.id, {'status': 'completed'}, db=db)
Files.update_file_hash_by_id(file.id, hash, db=db)
await Files.update_file_data_by_id(file.id, {'status': 'completed'}, db=db)
await Files.update_file_hash_by_id(file.id, hash, db=db)
return {
'status': True,
'collection_name': None,
@@ -1715,7 +1715,7 @@ def process_file(
if result:
# Fresh session for the final update.
with get_db() as session:
Files.update_file_metadata_by_id(
await Files.update_file_metadata_by_id(
file.id,
{
'collection_name': collection_name,
@@ -1723,12 +1723,12 @@ def process_file(
db=session,
)
Files.update_file_data_by_id(
await Files.update_file_data_by_id(
file.id,
{'status': 'completed'},
db=session,
)
Files.update_file_hash_by_id(file.id, hash, db=session)
await Files.update_file_hash_by_id(file.id, hash, db=session)
return {
'status': True,
@@ -1745,13 +1745,13 @@ def process_file(
log.exception(e)
# Fresh session for error status update.
with get_db() as session:
Files.update_file_data_by_id(
await Files.update_file_data_by_id(
file.id,
{'status': 'failed'},
db=session,
)
# Clear the hash so the file can be re-uploaded after fixing the issue
Files.update_file_hash_by_id(file.id, None, db=session)
await Files.update_file_hash_by_id(file.id, None, db=session)
if 'No pandoc was found' in str(e):
raise HTTPException(
@@ -2175,7 +2175,7 @@ async def process_web_search(request: Request, form_data: SearchForm, user=Depen
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id, 'features.web_search', request.app.state.config.USER_PERMISSIONS
):
raise HTTPException(
@@ -2327,7 +2327,7 @@ async def process_web_search(request: Request, form_data: SearchForm, user=Depen
)
def _validate_collection_access(collection_names: list[str], user) -> None:
async def _validate_collection_access(collection_names: list[str], user) -> None:
"""
Prevent users from querying collections they don't own.
Enforces ownership on user-memory-* and file-* collections.
@@ -2344,7 +2344,7 @@ def _validate_collection_access(collection_names: list[str], user) -> None:
)
elif name.startswith('file-'):
file_id = name[len('file-') :]
if not has_access_to_file(
if not await has_access_to_file(
file_id=file_id,
access_type='read',
user=user,
@@ -2370,7 +2370,7 @@ async def query_doc_handler(
form_data: QueryDocForm,
user=Depends(get_verified_user),
):
_validate_collection_access([form_data.collection_name], user)
await _validate_collection_access([form_data.collection_name], user)
try:
if request.app.state.config.ENABLE_RAG_HYBRID_SEARCH and (form_data.hybrid is None or form_data.hybrid):
@@ -2435,7 +2435,7 @@ async def query_collection_handler(
form_data: QueryCollectionsForm,
user=Depends(get_verified_user),
):
_validate_collection_access(form_data.collection_names, user)
await _validate_collection_access(form_data.collection_names, user)
try:
if request.app.state.config.ENABLE_RAG_HYBRID_SEARCH and (form_data.hybrid is None or form_data.hybrid):
@@ -2496,14 +2496,14 @@ class DeleteForm(BaseModel):
@router.post('/delete')
def delete_entries_from_collection(
async def delete_entries_from_collection(
form_data: DeleteForm,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
try:
if VECTOR_DB_CLIENT.has_collection(collection_name=form_data.collection_name):
file = Files.get_file_by_id(form_data.file_id, db=db)
file = await Files.get_file_by_id(form_data.file_id, db=db)
if not file:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -2524,13 +2524,13 @@ def delete_entries_from_collection(
@router.post('/reset/db')
def reset_vector_db(user=Depends(get_admin_user), db: Session = Depends(get_session)):
async def reset_vector_db(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
VECTOR_DB_CLIENT.reset()
Knowledges.delete_all_knowledge(db=db)
await Knowledges.delete_all_knowledge(db=db)
@router.post('/reset/uploads')
def reset_upload_dir(user=Depends(get_admin_user)) -> bool:
async def reset_upload_dir(user=Depends(get_admin_user)) -> bool:
folder = f'{UPLOAD_DIR}'
try:
# Check if the directory exists
@@ -2585,7 +2585,7 @@ async def process_files_batch(
"""
Process a batch of files and save them to the vector database.
NOTE: We intentionally do NOT use Depends(get_session) here.
NOTE: We intentionally do NOT use Depends(get_async_session) here.
The save_docs_to_vector_db() call makes external embedding API calls which
can take 5-60+ seconds for batch operations. Database operations after
embedding (Files.update_file_by_id) manage their own short-lived sessions.
@@ -2603,7 +2603,7 @@ async def process_files_batch(
for file in form_data.files:
try:
# Ownership check: verify the requesting user owns the file or is an admin
db_file = Files.get_file_by_id(file.id, db=db)
db_file = await Files.get_file_by_id(file.id, db=db)
if not db_file:
file_errors.append(
BatchProcessFilesResult(
@@ -2665,7 +2665,7 @@ async def process_files_batch(
# Update all files with collection name
for file_update, file_result in zip(file_updates, file_results):
Files.update_file_by_id(id=file_result.file_id, form_data=file_update, db=db)
await Files.update_file_by_id(id=file_result.file_id, form_data=file_update, db=db)
file_result.status = 'completed'
except Exception as e:
+70 -70
View File
@@ -30,8 +30,8 @@ from open_webui.config import OAUTH_PROVIDERS
from open_webui.env import SCIM_AUTH_PROVIDER
from sqlalchemy.orm import Session
from open_webui.internal.db import get_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.internal.db import get_async_session
log = logging.getLogger(__name__)
@@ -326,18 +326,18 @@ def get_scim_provider() -> str:
return SCIM_AUTH_PROVIDER
def find_user_by_external_id(external_id: str, db=None) -> Optional[UserModel]:
async def find_user_by_external_id(external_id: str, db=None) -> Optional[UserModel]:
"""Find a user by SCIM externalId, falling back to OAuth sub match."""
provider = get_scim_provider()
user = Users.get_user_by_scim_external_id(provider, external_id, db=db)
user = await Users.get_user_by_scim_external_id(provider, external_id, db=db)
if user:
return user
# Fallback: check if externalId matches an existing OAuth sub (account linking)
return Users.get_user_by_oauth_sub(provider, external_id, db=db)
return await Users.get_user_by_oauth_sub(provider, external_id, db=db)
def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
async def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
"""Convert internal User model to SCIM User"""
# Parse display name into name components
name_parts = user.name.split(' ', 1) if user.name else ['', '']
@@ -345,7 +345,7 @@ def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
family_name = name_parts[1] if len(name_parts) > 1 else ''
# Get user's groups
user_groups = Groups.get_groups_by_member_id(user.id, db=db)
user_groups = await Groups.get_groups_by_member_id(user.id, db=db)
groups = [
{
'value': group.id,
@@ -379,12 +379,12 @@ def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
)
def group_to_scim(group: GroupModel, request: Request, db=None) -> SCIMGroup:
async def group_to_scim(group: GroupModel, request: Request, db=None) -> SCIMGroup:
"""Convert internal Group model to SCIM Group"""
member_ids = Groups.get_group_user_ids_by_id(group.id, db) or []
member_ids = await Groups.get_group_user_ids_by_id(group.id, db) or []
# Batch-fetch all users to avoid N+1 queries
users = Users.get_users_by_user_ids(member_ids, db=db) if member_ids else []
users = await Users.get_users_by_user_ids(member_ids, db=db) if member_ids else []
members = [
SCIMGroupMember(
value=user.id,
@@ -512,7 +512,7 @@ async def get_users(
count: int = Query(20),
filter: Optional[str] = None,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""List SCIM Users"""
# Clamp per SCIM 2.0 spec (RFC 7644 §3.4.2.4):
@@ -527,25 +527,25 @@ async def get_users(
# Simple filter parsing - supports userName eq, externalId eq
if 'userName eq' in filter:
email = filter.split('"')[1]
user = Users.get_user_by_email(email, db=db)
user = await Users.get_user_by_email(email, db=db)
users_list = [user] if user else []
total = 1 if user else 0
elif 'externalId eq' in filter:
external_id = filter.split('"')[1]
user = find_user_by_external_id(external_id, db=db)
user = await find_user_by_external_id(external_id, db=db)
users_list = [user] if user else []
total = 1 if user else 0
else:
response = Users.get_users(skip=skip, limit=limit, db=db)
response = await Users.get_users(skip=skip, limit=limit, db=db)
users_list = response['users']
total = response['total']
else:
response = Users.get_users(skip=skip, limit=limit, db=db)
response = await Users.get_users(skip=skip, limit=limit, db=db)
users_list = response['users']
total = response['total']
# Convert to SCIM format
scim_users = [user_to_scim(user, request, db=db) for user in users_list]
scim_users = [await user_to_scim(user, request, db=db) for user in users_list]
return SCIMListResponse(
totalResults=total,
@@ -560,14 +560,14 @@ async def get_user(
user_id: str,
request: Request,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get SCIM User by ID"""
user = Users.get_user_by_id(user_id, db=db)
user = await Users.get_user_by_id(user_id, db=db)
if not user:
return scim_error(status_code=status.HTTP_404_NOT_FOUND, detail=f'User {user_id} not found')
return user_to_scim(user, request, db=db)
return await user_to_scim(user, request, db=db)
@router.post('/Users', response_model=SCIMUser, status_code=status.HTTP_201_CREATED)
@@ -575,12 +575,12 @@ async def create_user(
request: Request,
user_data: SCIMUserCreateRequest,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Create SCIM User"""
# Check for duplicate by externalId
if user_data.externalId:
existing_user = find_user_by_external_id(user_data.externalId, db=db)
existing_user = await find_user_by_external_id(user_data.externalId, db=db)
if existing_user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
@@ -596,7 +596,7 @@ async def create_user(
email = email.lower()
# Check for duplicate by email
existing_user = Users.get_user_by_email(email, db=db)
existing_user = await Users.get_user_by_email(email, db=db)
if existing_user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
@@ -619,7 +619,7 @@ async def create_user(
if user_data.photos and len(user_data.photos) > 0:
profile_image = user_data.photos[0].value
new_user = Users.insert_new_user(
new_user = await Users.insert_new_user(
id=user_id,
name=name,
email=email,
@@ -637,10 +637,10 @@ async def create_user(
# Store externalId in the scim field
if user_data.externalId:
provider = get_scim_provider()
Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
new_user = Users.get_user_by_id(user_id, db=db)
await Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
new_user = await Users.get_user_by_id(user_id, db=db)
return user_to_scim(new_user, request, db=db)
return await user_to_scim(new_user, request, db=db)
@router.put('/Users/{user_id}', response_model=SCIMUser)
@@ -649,10 +649,10 @@ async def update_user(
request: Request,
user_data: SCIMUserUpdateRequest,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Update SCIM User (full update)"""
user = Users.get_user_by_id(user_id, db=db)
user = await Users.get_user_by_id(user_id, db=db)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -682,7 +682,7 @@ async def update_user(
if user_data.photos and len(user_data.photos) > 0:
update_data['profile_image_url'] = user_data.photos[0].value
updated_user = Users.update_user_by_id(user_id, update_data, db=db)
updated_user = await Users.update_user_by_id(user_id, update_data, db=db)
if not updated_user:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -692,10 +692,10 @@ async def update_user(
# Update externalId in the scim field
if user_data.externalId:
provider = get_scim_provider()
Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
updated_user = Users.get_user_by_id(user_id, db=db)
await Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
updated_user = await Users.get_user_by_id(user_id, db=db)
return user_to_scim(updated_user, request, db=db)
return await user_to_scim(updated_user, request, db=db)
@router.patch('/Users/{user_id}', response_model=SCIMUser)
@@ -704,10 +704,10 @@ async def patch_user(
request: Request,
patch_data: SCIMPatchRequest,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Update SCIM User (partial update)"""
user = Users.get_user_by_id(user_id, db=db)
user = await Users.get_user_by_id(user_id, db=db)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -734,11 +734,11 @@ async def patch_user(
update_data['name'] = value
elif path == 'externalId':
provider = get_scim_provider()
Users.update_user_scim_by_id(user_id, provider, value, db=db)
await Users.update_user_scim_by_id(user_id, provider, value, db=db)
# Update user
if update_data:
updated_user = Users.update_user_by_id(user_id, update_data, db=db)
updated_user = await Users.update_user_by_id(user_id, update_data, db=db)
if not updated_user:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -747,7 +747,7 @@ async def patch_user(
else:
updated_user = user
return user_to_scim(updated_user, request, db=db)
return await user_to_scim(updated_user, request, db=db)
@router.delete('/Users/{user_id}', status_code=status.HTTP_204_NO_CONTENT)
@@ -755,17 +755,17 @@ async def delete_user(
user_id: str,
request: Request,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Delete SCIM User"""
user = Users.get_user_by_id(user_id, db=db)
user = await Users.get_user_by_id(user_id, db=db)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f'User {user_id} not found',
)
success = Users.delete_user_by_id(user_id, db=db)
success = await Users.delete_user_by_id(user_id, db=db)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -783,7 +783,7 @@ async def get_groups(
count: int = Query(20),
filter: Optional[str] = None,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""List SCIM Groups"""
# Clamp per SCIM 2.0 spec (RFC 7644 §3.4.2.4):
@@ -795,13 +795,13 @@ async def get_groups(
if filter:
if 'displayName eq' in filter:
display_name = filter.split('"')[1]
group = Groups.get_group_by_name(display_name, db=db)
group = await Groups.get_group_by_name(display_name, db=db)
groups_list = [group] if group else []
else:
# Unrecognized filter — fall back to all groups
groups_list = Groups.get_all_groups(db=db)
groups_list = await Groups.get_all_groups(db=db)
else:
groups_list = Groups.get_all_groups(db=db)
groups_list = await Groups.get_all_groups(db=db)
# Apply pagination
total = len(groups_list)
@@ -810,7 +810,7 @@ async def get_groups(
paginated_groups = groups_list[start:end]
# Convert to SCIM format
scim_groups = [group_to_scim(group, request, db=db) for group in paginated_groups]
scim_groups = [await group_to_scim(group, request, db=db) for group in paginated_groups]
return SCIMListResponse(
totalResults=total,
@@ -825,17 +825,17 @@ async def get_group(
group_id: str,
request: Request,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Get SCIM Group by ID"""
group = Groups.get_group_by_id(group_id, db=db)
group = await Groups.get_group_by_id(group_id, db=db)
if not group:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f'Group {group_id} not found',
)
return group_to_scim(group, request, db=db)
return await group_to_scim(group, request, db=db)
@router.post('/Groups', response_model=SCIMGroup, status_code=status.HTTP_201_CREATED)
@@ -843,7 +843,7 @@ async def create_group(
request: Request,
group_data: SCIMGroupCreateRequest,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Create SCIM Group"""
# Extract member IDs
@@ -861,14 +861,14 @@ async def create_group(
)
# Need to get the creating user's ID - we'll use the first admin
admin_user = Users.get_super_admin_user(db=db)
admin_user = await Users.get_super_admin_user(db=db)
if not admin_user:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='No admin user found',
)
new_group = Groups.insert_new_group(admin_user.id, form, db=db)
new_group = await Groups.insert_new_group(admin_user.id, form, db=db)
if not new_group:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -884,12 +884,12 @@ async def create_group(
description=new_group.description,
)
Groups.update_group_by_id(new_group.id, update_form, db=db)
Groups.set_group_user_ids_by_id(new_group.id, member_ids, db=db)
await Groups.update_group_by_id(new_group.id, update_form, db=db)
await Groups.set_group_user_ids_by_id(new_group.id, member_ids, db=db)
new_group = Groups.get_group_by_id(new_group.id, db=db)
new_group = await Groups.get_group_by_id(new_group.id, db=db)
return group_to_scim(new_group, request, db=db)
return await group_to_scim(new_group, request, db=db)
@router.put('/Groups/{group_id}', response_model=SCIMGroup)
@@ -898,10 +898,10 @@ async def update_group(
request: Request,
group_data: SCIMGroupUpdateRequest,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Update SCIM Group (full update)"""
group = Groups.get_group_by_id(group_id, db=db)
group = await Groups.get_group_by_id(group_id, db=db)
if not group:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -919,17 +919,17 @@ async def update_group(
# Handle members if provided
if group_data.members is not None:
member_ids = [member.value for member in group_data.members]
Groups.set_group_user_ids_by_id(group_id, member_ids, db=db)
await Groups.set_group_user_ids_by_id(group_id, member_ids, db=db)
# Update group
updated_group = Groups.update_group_by_id(group_id, update_form, db=db)
updated_group = await Groups.update_group_by_id(group_id, update_form, db=db)
if not updated_group:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='Failed to update group',
)
return group_to_scim(updated_group, request, db=db)
return await group_to_scim(updated_group, request, db=db)
@router.patch('/Groups/{group_id}', response_model=SCIMGroup)
@@ -938,10 +938,10 @@ async def patch_group(
request: Request,
patch_data: SCIMPatchRequest,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Update SCIM Group (partial update)"""
group = Groups.get_group_by_id(group_id, db=db)
group = await Groups.get_group_by_id(group_id, db=db)
if not group:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -965,7 +965,7 @@ async def patch_group(
update_form.name = value
elif path == 'members':
# Replace all members
Groups.set_group_user_ids_by_id(group_id, [member['value'] for member in value], db=db)
await Groups.set_group_user_ids_by_id(group_id, [member['value'] for member in value], db=db)
elif op == 'add':
if path == 'members':
@@ -973,22 +973,22 @@ async def patch_group(
if isinstance(value, list):
for member in value:
if isinstance(member, dict) and 'value' in member:
Groups.add_users_to_group(group_id, [member['value']], db=db)
await Groups.add_users_to_group(group_id, [member['value']], db=db)
elif op == 'remove':
if path and path.startswith('members[value eq'):
# Remove specific member
member_id = path.split('"')[1]
Groups.remove_users_from_group(group_id, [member_id], db=db)
await Groups.remove_users_from_group(group_id, [member_id], db=db)
# Update group
updated_group = Groups.update_group_by_id(group_id, update_form, db=db)
updated_group = await Groups.update_group_by_id(group_id, update_form, db=db)
if not updated_group:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='Failed to update group',
)
return group_to_scim(updated_group, request, db=db)
return await group_to_scim(updated_group, request, db=db)
@router.delete('/Groups/{group_id}', status_code=status.HTTP_204_NO_CONTENT)
@@ -996,17 +996,17 @@ async def delete_group(
group_id: str,
request: Request,
_: bool = Depends(get_scim_auth),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
"""Delete SCIM Group"""
group = Groups.get_group_by_id(group_id, db=db)
group = await Groups.get_group_by_id(group_id, db=db)
if not group:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f'Group {group_id} not found',
)
success = Groups.delete_group_by_id(group_id, db=db)
success = await Groups.delete_group_by_id(group_id, db=db)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+41 -41
View File
@@ -5,9 +5,9 @@ from open_webui.models.groups import Groups
from pydantic import BaseModel
from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy.orm import Session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.internal.db import get_session
from open_webui.internal.db import get_async_session
from open_webui.models.skills import (
SkillForm,
SkillModel,
@@ -40,18 +40,18 @@ router = APIRouter()
async def get_skills(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
skills = Skills.get_skills(db=db)
skills = await Skills.get_skills(db=db)
else:
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id, db=db)}
all_skills = Skills.get_skills(db=db)
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
all_skills = await Skills.get_skills(db=db)
skills = [
skill
for skill in all_skills
if skill.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='skill',
resource_id=skill.id,
@@ -75,7 +75,7 @@ async def get_skill_list(
view_option: Optional[str] = None,
page: Optional[int] = 1,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
limit = PAGE_ITEM_COUNT
@@ -89,13 +89,13 @@ async def get_skill_list(
filter['view_option'] = view_option
if not (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL):
groups = Groups.get_groups_by_member_id(user.id, db=db)
groups = await Groups.get_groups_by_member_id(user.id, db=db)
if groups:
filter['group_ids'] = [group.id for group in groups]
filter['user_id'] = user.id
result = Skills.search_skills(user.id, filter=filter, skip=skip, limit=limit, db=db)
result = await Skills.search_skills(user.id, filter=filter, skip=skip, limit=limit, db=db)
return SkillAccessListResponse(
items=[
@@ -104,7 +104,7 @@ async def get_skill_list(
write_access=(
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
or user.id == skill.user_id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='skill',
resource_id=skill.id,
@@ -128,9 +128,9 @@ async def get_skill_list(
async def export_skills(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id,
'workspace.skills',
request.app.state.config.USER_PERMISSIONS,
@@ -142,9 +142,9 @@ async def export_skills(
)
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
return Skills.get_skills(db=db)
return await Skills.get_skills(db=db)
else:
return Skills.get_skills_by_user_id(user.id, 'read', db=db)
return await Skills.get_skills_by_user_id(user.id, 'read', db=db)
############################
@@ -157,9 +157,9 @@ async def create_new_skill(
request: Request,
form_data: SkillForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id, 'workspace.skills', request.app.state.config.USER_PERMISSIONS, db=db
):
raise HTTPException(
@@ -169,7 +169,7 @@ async def create_new_skill(
form_data.id = form_data.id.lower().replace(' ', '-')
existing = Skills.get_skill_by_id(form_data.id, db=db)
existing = await Skills.get_skill_by_id(form_data.id, db=db)
if existing is not None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -177,7 +177,7 @@ async def create_new_skill(
)
try:
skill = Skills.insert_new_skill(user.id, form_data, db=db)
skill = await Skills.insert_new_skill(user.id, form_data, db=db)
if skill:
return skill
else:
@@ -199,14 +199,14 @@ async def create_new_skill(
@router.get('/id/{id}', response_model=Optional[SkillAccessResponse])
async def get_skill_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
skill = Skills.get_skill_by_id(id, db=db)
async def get_skill_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
skill = await Skills.get_skill_by_id(id, db=db)
if skill:
if (
user.role == 'admin'
or skill.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='skill',
resource_id=skill.id,
@@ -219,7 +219,7 @@ async def get_skill_by_id(id: str, user=Depends(get_verified_user), db: Session
write_access=(
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
or user.id == skill.user_id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='skill',
resource_id=skill.id,
@@ -251,9 +251,9 @@ async def update_skill_by_id(
id: str,
form_data: SkillForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
skill = Skills.get_skill_by_id(id, db=db)
skill = await Skills.get_skill_by_id(id, db=db)
if not skill:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -262,7 +262,7 @@ async def update_skill_by_id(
if (
skill.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='skill',
resource_id=skill.id,
@@ -281,7 +281,7 @@ async def update_skill_by_id(
**form_data.model_dump(exclude={'id'}),
}
skill = Skills.update_skill_by_id(id, updated, db=db)
skill = await Skills.update_skill_by_id(id, updated, db=db)
if skill:
return skill
@@ -312,9 +312,9 @@ async def update_skill_access_by_id(
id: str,
form_data: SkillAccessGrantsForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
skill = Skills.get_skill_by_id(id, db=db)
skill = await Skills.get_skill_by_id(id, db=db)
if not skill:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -323,7 +323,7 @@ async def update_skill_access_by_id(
if (
skill.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='skill',
resource_id=skill.id,
@@ -337,7 +337,7 @@ async def update_skill_access_by_id(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -345,9 +345,9 @@ async def update_skill_access_by_id(
'sharing.public_skills',
)
AccessGrants.set_access_grants('skill', id, form_data.access_grants, db=db)
await AccessGrants.set_access_grants('skill', id, form_data.access_grants, db=db)
return Skills.get_skill_by_id(id, db=db)
return await Skills.get_skill_by_id(id, db=db)
############################
@@ -356,13 +356,13 @@ async def update_skill_access_by_id(
@router.post('/id/{id}/toggle', response_model=Optional[SkillModel])
async def toggle_skill_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
skill = Skills.get_skill_by_id(id, db=db)
async def toggle_skill_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
skill = await Skills.get_skill_by_id(id, db=db)
if skill:
if (
user.role == 'admin'
or skill.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='skill',
resource_id=skill.id,
@@ -370,7 +370,7 @@ async def toggle_skill_by_id(id: str, user=Depends(get_verified_user), db: Sessi
db=db,
)
):
skill = Skills.toggle_skill_by_id(id, db=db)
skill = await Skills.toggle_skill_by_id(id, db=db)
if skill:
return skill
@@ -401,9 +401,9 @@ async def delete_skill_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
skill = Skills.get_skill_by_id(id, db=db)
skill = await Skills.get_skill_by_id(id, db=db)
if not skill:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -412,7 +412,7 @@ async def delete_skill_by_id(
if (
skill.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='skill',
resource_id=skill.id,
@@ -426,5 +426,5 @@ async def delete_skill_by_id(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
result = Skills.delete_skill_by_id(id, db=db)
result = await Skills.delete_skill_by_id(id, db=db)
return result
+7 -7
View File
@@ -52,7 +52,7 @@ def _sanitize_proxy_path(path: str) -> str | None:
async def list_terminal_servers(request: Request, user=Depends(get_verified_user)):
"""Return terminal servers the authenticated user has access to."""
connections = request.app.state.config.TERMINAL_SERVER_CONNECTIONS or []
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id)}
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id)}
return [
{
@@ -61,7 +61,7 @@ async def list_terminal_servers(request: Request, user=Depends(get_verified_user
'name': connection.get('name', ''),
}
for connection in connections
if connection.get('enabled', True) and has_connection_access(user, connection, user_group_ids)
if connection.get('enabled', True) and await has_connection_access(user, connection, user_group_ids)
]
@@ -82,8 +82,8 @@ async def proxy_terminal(
if connection is None:
return JSONResponse({'error': f"Terminal server '{server_id}' not found"}, status_code=404)
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id)}
if not has_connection_access(user, connection, user_group_ids):
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id)}
if not await has_connection_access(user, connection, user_group_ids):
return JSONResponse({'error': 'Access denied'}, status_code=403)
base_url = (connection.get('url') or '').rstrip('/')
@@ -208,7 +208,7 @@ async def _resolve_authenticated_connection(ws: WebSocket, server_id: str):
if data is None or 'id' not in data:
await ws.close(code=4001, reason='Invalid token')
return None
user = Users.get_user_by_id(data['id'])
user = await Users.get_user_by_id(data['id'])
if user is None:
await ws.close(code=4001, reason='User not found')
return None
@@ -227,8 +227,8 @@ async def _resolve_authenticated_connection(ws: WebSocket, server_id: str):
await ws.close(code=4004, reason='Terminal server not found')
return None
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id)}
if not has_connection_access(user, connection, user_group_ids):
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id)}
if not await has_connection_access(user, connection, user_group_ids):
await ws.close(code=4003, reason='Access denied')
return None
+69 -69
View File
@@ -8,8 +8,8 @@ from open_webui.env import AIOHTTP_CLIENT_TIMEOUT
from open_webui.models.groups import Groups
from pydantic import BaseModel, HttpUrl
from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy.orm import Session
from open_webui.internal.db import get_session
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.internal.db import get_async_session
from open_webui.models.oauth_sessions import OAuthSessions
@@ -46,11 +46,11 @@ log = logging.getLogger(__name__)
router = APIRouter()
def get_tool_module(request, tool_id, load_from_db=True):
async def get_tool_module(request, tool_id, load_from_db=True):
"""
Get the tool module by its ID.
"""
tool_module, _ = get_tool_module_from_cache(request, tool_id, load_from_db)
tool_module, _ = await get_tool_module_from_cache(request, tool_id, load_from_db)
return tool_module
@@ -65,12 +65,12 @@ def get_tool_module(request, tool_id, load_from_db=True):
async def get_tools(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
tools = []
# Local Tools
for tool in Tools.get_tools(defer_content=True, db=db):
for tool in await Tools.get_tools(defer_content=True, db=db):
tool_module = request.app.state.TOOLS.get(tool.id) if hasattr(request.app.state, 'TOOLS') else None
tools.append(
ToolUserResponse(
@@ -159,7 +159,7 @@ async def get_tools(
# Admin can see all tools
return tools
else:
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id, db=db)}
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
tools = [
tool
for tool in tools
@@ -173,7 +173,7 @@ async def get_tools(
db=db,
)
if str(tool.id).startswith('server:')
else AccessGrants.has_access(
else await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tool.id,
@@ -192,13 +192,13 @@ async def get_tools(
@router.get('/list', response_model=list[ToolAccessResponse])
async def get_tool_list(user=Depends(get_verified_user), db: Session = Depends(get_session)):
async def get_tool_list(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
tools = Tools.get_tools(defer_content=True, db=db)
tools = await Tools.get_tools(defer_content=True, db=db)
else:
tools = Tools.get_tools_by_user_id(user.id, 'read', defer_content=True, db=db)
tools = await Tools.get_tools_by_user_id(user.id, 'read', defer_content=True, db=db)
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id, db=db)}
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
result = []
for tool in tools:
@@ -298,9 +298,9 @@ async def load_tool_from_url(request: Request, form_data: LoadUrlForm, user=Depe
async def export_tools(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not has_permission(
if user.role != 'admin' and not await has_permission(
user.id,
'workspace.tools_export',
request.app.state.config.USER_PERMISSIONS,
@@ -312,9 +312,9 @@ async def export_tools(
)
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
return Tools.get_tools(db=db)
return await Tools.get_tools(db=db)
else:
return Tools.get_tools_by_user_id(user.id, 'read', db=db)
return await Tools.get_tools_by_user_id(user.id, 'read', db=db)
############################
@@ -327,11 +327,11 @@ async def create_new_tools(
request: Request,
form_data: ToolForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not (
has_permission(user.id, 'workspace.tools', request.app.state.config.USER_PERMISSIONS, db=db)
or has_permission(
await has_permission(user.id, 'workspace.tools', request.app.state.config.USER_PERMISSIONS, db=db)
or await has_permission(
user.id,
'workspace.tools_import',
request.app.state.config.USER_PERMISSIONS,
@@ -351,10 +351,10 @@ async def create_new_tools(
form_data.id = form_data.id.lower()
tools = Tools.get_tool_by_id(form_data.id, db=db)
tools = await Tools.get_tool_by_id(form_data.id, db=db)
if tools is None:
try:
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -363,14 +363,14 @@ async def create_new_tools(
)
form_data.content = replace_imports(form_data.content)
tool_module, frontmatter = load_tool_module_by_id(form_data.id, content=form_data.content)
tool_module, frontmatter = await load_tool_module_by_id(form_data.id, content=form_data.content)
form_data.meta.manifest = frontmatter
TOOLS = request.app.state.TOOLS
TOOLS[form_data.id] = tool_module
specs = get_tool_specs(TOOLS[form_data.id])
tools = Tools.insert_new_tool(user.id, form_data, specs, db=db)
tools = await Tools.insert_new_tool(user.id, form_data, specs, db=db)
tool_cache_dir = CACHE_DIR / 'tools' / form_data.id
tool_cache_dir.mkdir(parents=True, exist_ok=True)
@@ -401,14 +401,14 @@ async def create_new_tools(
@router.get('/id/{id}', response_model=Optional[ToolAccessResponse])
async def get_tools_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
tools = Tools.get_tool_by_id(id, db=db)
async def get_tools_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
tools = await Tools.get_tool_by_id(id, db=db)
if tools:
if (
user.role == 'admin'
or tools.user_id == user.id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tools.id,
@@ -421,7 +421,7 @@ async def get_tools_by_id(id: str, user=Depends(get_verified_user), db: Session
write_access=(
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
or user.id == tools.user_id
or AccessGrants.has_access(
or await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tools.id,
@@ -453,9 +453,9 @@ async def update_tools_by_id(
id: str,
form_data: ToolForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
tools = Tools.get_tool_by_id(id, db=db)
tools = await Tools.get_tool_by_id(id, db=db)
if not tools:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -465,7 +465,7 @@ async def update_tools_by_id(
# Is the user the original creator, in a group with write access, or an admin
if (
tools.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tools.id,
@@ -481,7 +481,7 @@ async def update_tools_by_id(
try:
form_data.content = replace_imports(form_data.content)
tool_module, frontmatter = load_tool_module_by_id(id, content=form_data.content)
tool_module, frontmatter = await load_tool_module_by_id(id, content=form_data.content)
form_data.meta.manifest = frontmatter
TOOLS = request.app.state.TOOLS
@@ -489,7 +489,7 @@ async def update_tools_by_id(
specs = get_tool_specs(TOOLS[id])
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -503,7 +503,7 @@ async def update_tools_by_id(
}
log.debug(updated)
tools = Tools.update_tool_by_id(id, updated, db=db)
tools = await Tools.update_tool_by_id(id, updated, db=db)
if tools:
return tools
@@ -535,9 +535,9 @@ async def update_tool_access_by_id(
id: str,
form_data: ToolAccessGrantsForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
tools = Tools.get_tool_by_id(id, db=db)
tools = await Tools.get_tool_by_id(id, db=db)
if not tools:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -546,7 +546,7 @@ async def update_tool_access_by_id(
if (
tools.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tools.id,
@@ -560,7 +560,7 @@ async def update_tool_access_by_id(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
form_data.access_grants = filter_allowed_access_grants(
form_data.access_grants = await filter_allowed_access_grants(
request.app.state.config.USER_PERMISSIONS,
user.id,
user.role,
@@ -568,9 +568,9 @@ async def update_tool_access_by_id(
'sharing.public_tools',
)
AccessGrants.set_access_grants('tool', id, form_data.access_grants, db=db)
await AccessGrants.set_access_grants('tool', id, form_data.access_grants, db=db)
return Tools.get_tool_by_id(id, db=db)
return await Tools.get_tool_by_id(id, db=db)
############################
@@ -583,9 +583,9 @@ async def delete_tools_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
tools = Tools.get_tool_by_id(id, db=db)
tools = await Tools.get_tool_by_id(id, db=db)
if not tools:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -594,7 +594,7 @@ async def delete_tools_by_id(
if (
tools.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tools.id,
@@ -608,7 +608,7 @@ async def delete_tools_by_id(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
result = Tools.delete_tool_by_id(id, db=db)
result = await Tools.delete_tool_by_id(id, db=db)
if result:
TOOLS = request.app.state.TOOLS
if id in TOOLS:
@@ -623,8 +623,8 @@ async def delete_tools_by_id(
@router.get('/id/{id}/valves', response_model=Optional[dict])
async def get_tools_valves_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
tools = Tools.get_tool_by_id(id, db=db)
async def get_tools_valves_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
tools = await Tools.get_tool_by_id(id, db=db)
if not tools:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -633,7 +633,7 @@ async def get_tools_valves_by_id(id: str, user=Depends(get_verified_user), db: S
if (
tools.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tools.id,
@@ -648,7 +648,7 @@ async def get_tools_valves_by_id(id: str, user=Depends(get_verified_user), db: S
)
try:
valves = Tools.get_tool_valves_by_id(id, db=db)
valves = await Tools.get_tool_valves_by_id(id, db=db)
return valves
except Exception as e:
raise HTTPException(
@@ -667,9 +667,9 @@ async def get_tools_valves_spec_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
tools = Tools.get_tool_by_id(id, db=db)
tools = await Tools.get_tool_by_id(id, db=db)
if not tools:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -678,7 +678,7 @@ async def get_tools_valves_spec_by_id(
if (
tools.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tools.id,
@@ -695,7 +695,7 @@ async def get_tools_valves_spec_by_id(
if id in request.app.state.TOOLS:
tools_module = request.app.state.TOOLS[id]
else:
tools_module, _ = load_tool_module_by_id(id)
tools_module, _ = await load_tool_module_by_id(id)
request.app.state.TOOLS[id] = tools_module
if hasattr(tools_module, 'Valves'):
@@ -718,9 +718,9 @@ async def update_tools_valves_by_id(
id: str,
form_data: dict,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
tools = Tools.get_tool_by_id(id, db=db)
tools = await Tools.get_tool_by_id(id, db=db)
if not tools:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -729,7 +729,7 @@ async def update_tools_valves_by_id(
if (
tools.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tools.id,
@@ -746,7 +746,7 @@ async def update_tools_valves_by_id(
if id in request.app.state.TOOLS:
tools_module = request.app.state.TOOLS[id]
else:
tools_module, _ = load_tool_module_by_id(id)
tools_module, _ = await load_tool_module_by_id(id)
request.app.state.TOOLS[id] = tools_module
if not hasattr(tools_module, 'Valves'):
@@ -760,7 +760,7 @@ async def update_tools_valves_by_id(
form_data = {k: v for k, v in form_data.items() if v is not None}
valves = Valves(**form_data)
valves_dict = valves.model_dump(exclude_unset=True)
Tools.update_tool_valves_by_id(id, valves_dict, db=db)
await Tools.update_tool_valves_by_id(id, valves_dict, db=db)
return valves_dict
except Exception as e:
log.exception(f'Failed to update tool valves by id {id}: {e}')
@@ -776,8 +776,8 @@ async def update_tools_valves_by_id(
@router.get('/id/{id}/valves/user', response_model=Optional[dict])
async def get_tools_user_valves_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
tools = Tools.get_tool_by_id(id, db=db)
async def get_tools_user_valves_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
tools = await Tools.get_tool_by_id(id, db=db)
if not tools:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -786,7 +786,7 @@ async def get_tools_user_valves_by_id(id: str, user=Depends(get_verified_user),
if (
tools.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tools.id,
@@ -801,7 +801,7 @@ async def get_tools_user_valves_by_id(id: str, user=Depends(get_verified_user),
)
try:
user_valves = Tools.get_user_valves_by_id_and_user_id(id, user.id, db=db)
user_valves = await Tools.get_user_valves_by_id_and_user_id(id, user.id, db=db)
return user_valves
except Exception as e:
raise HTTPException(
@@ -815,9 +815,9 @@ async def get_tools_user_valves_spec_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
tools = Tools.get_tool_by_id(id, db=db)
tools = await Tools.get_tool_by_id(id, db=db)
if not tools:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -826,7 +826,7 @@ async def get_tools_user_valves_spec_by_id(
if (
tools.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tools.id,
@@ -843,7 +843,7 @@ async def get_tools_user_valves_spec_by_id(
if id in request.app.state.TOOLS:
tools_module = request.app.state.TOOLS[id]
else:
tools_module, _ = load_tool_module_by_id(id)
tools_module, _ = await load_tool_module_by_id(id)
request.app.state.TOOLS[id] = tools_module
if hasattr(tools_module, 'UserValves'):
@@ -861,9 +861,9 @@ async def update_tools_user_valves_by_id(
id: str,
form_data: dict,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
tools = Tools.get_tool_by_id(id, db=db)
tools = await Tools.get_tool_by_id(id, db=db)
if not tools:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -872,7 +872,7 @@ async def update_tools_user_valves_by_id(
if (
tools.user_id != user.id
and not AccessGrants.has_access(
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tools.id,
@@ -889,7 +889,7 @@ async def update_tools_user_valves_by_id(
if id in request.app.state.TOOLS:
tools_module = request.app.state.TOOLS[id]
else:
tools_module, _ = load_tool_module_by_id(id)
tools_module, _ = await load_tool_module_by_id(id)
request.app.state.TOOLS[id] = tools_module
if hasattr(tools_module, 'UserValves'):
@@ -899,7 +899,7 @@ async def update_tools_user_valves_by_id(
form_data = {k: v for k, v in form_data.items() if v is not None}
user_valves = UserValves(**form_data)
user_valves_dict = user_valves.model_dump(exclude_unset=True)
Tools.update_user_valves_by_id_and_user_id(id, user.id, user_valves_dict, db=db)
await Tools.update_user_valves_by_id_and_user_id(id, user.id, user_valves_dict, db=db)
return user_valves_dict
except Exception as e:
log.exception(f'Failed to update user valves by id {id}: {e}')
+55 -55
View File
@@ -1,6 +1,6 @@
import logging
from typing import Optional
from sqlalchemy.orm import Session
from sqlalchemy.ext.asyncio import AsyncSession
import base64
import io
@@ -30,7 +30,7 @@ from open_webui.models.users import (
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import STATIC_DIR
from open_webui.internal.db import get_session
from open_webui.internal.db import get_async_session
from open_webui.utils.auth import (
@@ -63,7 +63,7 @@ async def get_users(
direction: Optional[str] = None,
page: Optional[int] = 1,
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
limit = PAGE_ITEM_COUNT
@@ -80,14 +80,14 @@ async def get_users(
filter['direction'] = direction
result = Users.get_users(filter=filter, skip=skip, limit=limit, db=db)
result = await Users.get_users(filter=filter, skip=skip, limit=limit, db=db)
users = result['users']
total = result['total']
# Fetch groups for all users in a single query to avoid N+1
user_ids = [user.id for user in users]
user_groups = Groups.get_groups_by_member_ids(user_ids, db=db)
user_groups = await Groups.get_groups_by_member_ids(user_ids, db=db)
return {
'users': [
@@ -106,9 +106,9 @@ async def get_users(
@router.get('/all', response_model=UserInfoListResponse)
async def get_all_users(
user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
return Users.get_users(db=db)
return await Users.get_users(db=db)
@router.get('/search', response_model=UserInfoListResponse)
@@ -118,7 +118,7 @@ async def search_users(
direction: Optional[str] = None,
page: Optional[int] = 1,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
limit = PAGE_ITEM_COUNT
@@ -133,7 +133,7 @@ async def search_users(
if direction:
filter['direction'] = direction
return Users.get_users(filter=filter, skip=skip, limit=limit, db=db)
return await Users.get_users(filter=filter, skip=skip, limit=limit, db=db)
############################
@@ -142,8 +142,8 @@ async def search_users(
@router.get('/groups')
async def get_user_groups(user=Depends(get_verified_user), db: Session = Depends(get_session)):
return Groups.get_groups_by_member_id(user.id, db=db)
async def get_user_groups(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
return await Groups.get_groups_by_member_id(user.id, db=db)
############################
@@ -155,9 +155,9 @@ async def get_user_groups(user=Depends(get_verified_user), db: Session = Depends
async def get_user_permissisions(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
user_permissions = get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
user_permissions = await get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
return user_permissions
@@ -272,8 +272,8 @@ async def update_default_user_permissions(request: Request, form_data: UserPermi
@router.get('/user/settings', response_model=Optional[UserSettings])
async def get_user_settings_by_session_user(user=Depends(get_verified_user), db: Session = Depends(get_session)):
user = Users.get_user_by_id(user.id, db=db)
async def get_user_settings_by_session_user(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
user = await Users.get_user_by_id(user.id, db=db)
if user:
return user.settings
else:
@@ -293,7 +293,7 @@ async def update_user_settings_by_session_user(
request: Request,
form_data: UserSettings,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
updated_user_settings = form_data.model_dump()
ui_settings = updated_user_settings.get('ui')
@@ -301,7 +301,7 @@ async def update_user_settings_by_session_user(
user.role != 'admin'
and ui_settings is not None
and 'toolServers' in ui_settings.keys()
and not has_permission(
and not await has_permission(
user.id,
'features.direct_tool_servers',
request.app.state.config.USER_PERMISSIONS,
@@ -310,7 +310,7 @@ async def update_user_settings_by_session_user(
# If the user is not an admin and does not have permission to use tool servers, remove the key
updated_user_settings['ui'].pop('toolServers', None)
user = Users.update_user_settings_by_id(user.id, updated_user_settings, db=db)
user = await Users.update_user_settings_by_id(user.id, updated_user_settings, db=db)
if user:
return user.settings
else:
@@ -329,14 +329,14 @@ async def update_user_settings_by_session_user(
async def get_user_status_by_session_user(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if not request.app.state.config.ENABLE_USER_STATUS:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACTION_PROHIBITED,
)
user = Users.get_user_by_id(user.id, db=db)
user = await Users.get_user_by_id(user.id, db=db)
if user:
return user
else:
@@ -356,16 +356,16 @@ async def update_user_status_by_session_user(
request: Request,
form_data: UserStatus,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
if not request.app.state.config.ENABLE_USER_STATUS:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACTION_PROHIBITED,
)
user = Users.get_user_by_id(user.id, db=db)
user = await Users.get_user_by_id(user.id, db=db)
if user:
user = Users.update_user_status_by_id(user.id, form_data, db=db)
user = await Users.update_user_status_by_id(user.id, form_data, db=db)
return user
else:
raise HTTPException(
@@ -380,8 +380,8 @@ async def update_user_status_by_session_user(
@router.get('/user/info', response_model=Optional[dict])
async def get_user_info_by_session_user(user=Depends(get_verified_user), db: Session = Depends(get_session)):
user = Users.get_user_by_id(user.id, db=db)
async def get_user_info_by_session_user(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
user = await Users.get_user_by_id(user.id, db=db)
if user:
return user.info
else:
@@ -398,14 +398,14 @@ async def get_user_info_by_session_user(user=Depends(get_verified_user), db: Ses
@router.post('/user/info/update', response_model=Optional[dict])
async def update_user_info_by_session_user(
form_data: dict, user=Depends(get_verified_user), db: Session = Depends(get_session)
form_data: dict, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
user = Users.get_user_by_id(user.id, db=db)
user = await Users.get_user_by_id(user.id, db=db)
if user:
if user.info is None:
user.info = {}
user = Users.update_user_by_id(user.id, {'info': {**user.info, **form_data}}, db=db)
user = await Users.update_user_by_id(user.id, {'info': {**user.info, **form_data}}, db=db)
if user:
return user.info
else:
@@ -435,12 +435,12 @@ class UserActiveResponse(UserStatus):
@router.get('/{user_id}', response_model=UserActiveResponse)
async def get_user_by_id(user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
async def get_user_by_id(user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
# Check if user_id is a shared chat
# If it is, get the user_id from the chat
if user_id.startswith('shared-'):
chat_id = user_id.replace('shared-', '')
chat = Chats.get_chat_by_id(chat_id)
chat = await Chats.get_chat_by_id(chat_id)
if chat:
user_id = chat.user_id
else:
@@ -449,14 +449,14 @@ async def get_user_by_id(user_id: str, user=Depends(get_admin_user), db: Session
detail=ERROR_MESSAGES.USER_NOT_FOUND,
)
user = Users.get_user_by_id(user_id, db=db)
user = await Users.get_user_by_id(user_id, db=db)
if user:
groups = Groups.get_groups_by_member_id(user_id, db=db)
groups = await Groups.get_groups_by_member_id(user_id, db=db)
return UserActiveResponse(
**{
**user.model_dump(),
'groups': [{'id': group.id, 'name': group.name} for group in groups],
'is_active': Users.is_user_active(user_id, db=db),
'is_active': await Users.is_user_active(user_id, db=db),
}
)
else:
@@ -467,15 +467,15 @@ async def get_user_by_id(user_id: str, user=Depends(get_admin_user), db: Session
@router.get('/{user_id}/info', response_model=UserInfoResponse)
async def get_user_info_by_id(user_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
user = Users.get_user_by_id(user_id, db=db)
async def get_user_info_by_id(user_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
user = await Users.get_user_by_id(user_id, db=db)
if user:
groups = Groups.get_groups_by_member_id(user_id, db=db)
groups = await Groups.get_groups_by_member_id(user_id, db=db)
return UserInfoResponse(
**{
**user.model_dump(),
'groups': [{'id': group.id, 'name': group.name} for group in groups],
'is_active': Users.is_user_active(user_id, db=db),
'is_active': await Users.is_user_active(user_id, db=db),
}
)
else:
@@ -486,8 +486,8 @@ async def get_user_info_by_id(user_id: str, user=Depends(get_verified_user), db:
@router.get('/{user_id}/oauth/sessions')
async def get_user_oauth_sessions_by_id(user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
sessions = OAuthSessions.get_sessions_by_user_id(user_id, db=db)
async def get_user_oauth_sessions_by_id(user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
sessions = await OAuthSessions.get_sessions_by_user_id(user_id, db=db)
if sessions and len(sessions) > 0:
return sessions
else:
@@ -503,8 +503,8 @@ async def get_user_oauth_sessions_by_id(user_id: str, user=Depends(get_admin_use
@router.get('/{user_id}/profile/image')
def get_user_profile_image_by_id(user_id: str, user=Depends(get_verified_user)):
user = Users.get_user_by_id(user_id)
async def get_user_profile_image_by_id(user_id: str, user=Depends(get_verified_user)):
user = await Users.get_user_by_id(user_id)
if user:
if user.profile_image_url:
# check if it's url or base64
@@ -542,10 +542,10 @@ def get_user_profile_image_by_id(user_id: str, user=Depends(get_verified_user)):
@router.get('/{user_id}/active', response_model=dict)
async def get_user_active_status_by_id(
user_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)
user_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
):
return {
'active': Users.is_user_active(user_id, db=db),
'active': await Users.is_user_active(user_id, db=db),
}
@@ -559,11 +559,11 @@ async def update_user_by_id(
user_id: str,
form_data: UserUpdateForm,
session_user=Depends(get_admin_user),
db: Session = Depends(get_session),
db: AsyncSession = Depends(get_async_session),
):
# Prevent modification of the primary admin user by other admins
try:
first_user = Users.get_first_user(db=db)
first_user = await Users.get_first_user(db=db)
if first_user:
if user_id == first_user.id:
if session_user.id != user_id:
@@ -587,11 +587,11 @@ async def update_user_by_id(
detail='Could not verify primary admin status.',
)
user = Users.get_user_by_id(user_id, db=db)
user = await Users.get_user_by_id(user_id, db=db)
if user:
if form_data.email.lower() != user.email:
email_user = Users.get_user_by_email(form_data.email.lower(), db=db)
email_user = await Users.get_user_by_email(form_data.email.lower(), db=db)
if email_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -605,10 +605,10 @@ async def update_user_by_id(
raise HTTPException(400, detail=str(e))
hashed = get_password_hash(form_data.password)
Auths.update_user_password_by_id(user_id, hashed, db=db)
await Auths.update_user_password_by_id(user_id, hashed, db=db)
Auths.update_email_by_id(user_id, form_data.email.lower(), db=db)
updated_user = Users.update_user_by_id(
await Auths.update_email_by_id(user_id, form_data.email.lower(), db=db)
updated_user = await Users.update_user_by_id(
user_id,
{
'role': form_data.role,
@@ -639,10 +639,10 @@ async def update_user_by_id(
@router.delete('/{user_id}', response_model=bool)
async def delete_user_by_id(user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
async def delete_user_by_id(user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
# Prevent deletion of the primary admin user
try:
first_user = Users.get_first_user(db=db)
first_user = await Users.get_first_user(db=db)
if first_user and user_id == first_user.id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
@@ -656,7 +656,7 @@ async def delete_user_by_id(user_id: str, user=Depends(get_admin_user), db: Sess
)
if user.id != user_id:
result = Auths.delete_auth_by_id(user_id, db=db)
result = await Auths.delete_auth_by_id(user_id, db=db)
if result:
return True
@@ -679,5 +679,5 @@ async def delete_user_by_id(user_id: str, user=Depends(get_admin_user), db: Sess
@router.get('/{user_id}/groups')
async def get_user_groups_by_id(user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
return Groups.get_groups_by_member_id(user_id, db=db)
async def get_user_groups_by_id(user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
return await Groups.get_groups_by_member_id(user_id, db=db)