diff --git a/app/services/user_service.py b/app/services/user_service.py index 4640d29..8e8656b 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -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