Fix role_id sentinel bug: use _UNSET sentinel so None explicitly clears FK, missing param leaves unchanged

This commit is contained in:
2026-07-03 20:43:00 +00:00
parent 2deb7ed21c
commit d7691b3d70
+11 -5
View File
@@ -11,6 +11,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.core.auth import hash_password
from app.models.user import User, UserTenant
# Sentinel used to distinguish "not provided" from "explicitly set to None".
# When ``role_id`` is _UNSET the service leaves the existing value untouched.
# When ``role_id`` is None the service clears the FK (falls back to role string).
_UNSET: Any = object()
class UserService:
"""Handles user CRUD operations."""
@@ -108,14 +113,15 @@ class UserService:
user_id: uuid.UUID,
name: str | None = None,
role: str | None = None,
role_id: uuid.UUID | None = None,
role_id: uuid.UUID | None | Any = _UNSET,
is_active: bool | None = None,
) -> User | None:
"""Update a user.
``role_id`` accepts a UUID to link a custom Role, or None to leave
the existing value unchanged. Pass ``role_id`` explicitly as None
combined with the sentinel logic below to clear it.
``role_id`` uses a sentinel to distinguish three states:
- ``_UNSET`` (default): leave the existing role_id unchanged
- ``None``: clear the FK (fall back to the legacy ``role`` string)
- ``uuid.UUID``: link to a custom Role record
"""
q = select(User).where(User.id == user_id, User.tenant_id == tenant_id)
result = await db.execute(q)
@@ -127,7 +133,7 @@ class UserService:
user.name = name
if role is not None:
user.role = role
if role_id is not None:
if role_id is not _UNSET:
user.role_id = role_id
if is_active is not None:
user.is_active = is_active