Skip to content

UserMod

Bases: DTMixin, UuidPK, SQLModel

Source code in fastbase/models/UserMod.py
class UserMod(DTMixin, UuidPK, SQLModel):
    email: str = Field(max_length=190, unique=True)
    username: str = Field(max_length=190, unique=True)
    display: str = Field(max_length=199)
    timezone: str | None = Field(max_length=190, default='+0000')

    role: str = Field(max_length=20, default='user')
    groups: list[str] = Field(sa_column=Column(ARRAY(String)), default=[])
    permissions: list[str] = Field(sa_column=Column(ARRAY(String)), default=[])

    is_verified: bool = Field(default=True)                     # TODO: Optional verification
    is_active: bool = Field(default=True)                       # TODO: Optional activation
    banned_at: datetime | None = Field(sa_column=Column(DateTime(timezone=True), nullable=True, index=True))

    def __repr__(self):
        return modstr(self, 'email')

    @property
    def is_super(self) -> bool:
        return self.role == 'super'

    # TESTME: Untested
    @classmethod
    async def get_by_email(cls, session: AsyncSession, email: str) -> Type[Self]:
        raise NotImplementedError()

    # TESTME: Untested
    @classmethod
    async def exists(cls, session: AsyncSession, email: EmailStr) -> bool:
        """Check if a user exists"""
        stmt = select(cls.id).where(cls.email == email)
        execdata = await session.exec(stmt)
        if _ := execdata.first():
            return True

    async def has(self, data: str) -> bool:
        raise NotImplementedError()

    async def attach_group(self, session: AsyncSession, recipient: Self, name: str,
                           *, caching: Callable[[str, list], None] | None = None,
                           async_callback: Callable[[str, list], Awaitable[None]] | None = None):
        """
        Attach a group to user. Prevents duplicates.
        :param session:     AsyncSession
        :param recipient:   The user to recieve the group
        :param name:        Group name
        :param caching:     Callback for caching data
        :param async_callback:    Async callback for generic use
        :raises PermissionsException:
        :return:            None
        """
        # async with AsyncSession(async_engine) as sess:
        # async with asynccontextmanager(get_session)() as sess:
        #     user = await User.get_by_email(sess, 'admin@gmail.com', skip_cache=True)
        def _attach(name_: str) -> list[str]:
            neg_ = f'-{name_}'
            groups_ = set(recipient.groups)

            if neg_ in groups_:
                groups_.remove(neg_)
            elif name_ not in groups_:
                groups_.add(name_)
            return list(groups_)

        if not await self.has('group.attach'):
            raise PermissionsException()

        if not name.strip():
            return

        if not self.is_super and self.email == recipient.email:
            raise AppException('ILLEGAL_ACTION: You cannot modify your own groups.')

        groups = _attach(name)
        recipient.groups = groups
        await session.commit()

        if caching:
            caching(recipient.email, groups)
        if async_callback:
            await async_callback(recipient.email, groups)

    async def detach_group(self, session: AsyncSession, recipient: Self, name: str,
                           *, caching: Callable[[str, list], None] | None = None,
                           async_callback: Callable[[str, list], Awaitable[None]] | None = None):
        """
        Remove a group from user. If the group is a starter group (can't be removed) then a negation is added.
        :param session:     AsyncSession
        :param recipient:   The user who's group is to be removed
        :param name:        Group name
        :param caching:     Callback for caching data
        :param async_callback:    Async callback for generic use
        :raises PermissionsException:
        :return:            None
        """
        def _detach(name_: str) -> list[str]:
            neg_ = f'-{name_}'
            groups_ = set(recipient.groups)

            if name_ in groups_:
                groups_.remove(name_)
            else:
                groups_.add(neg_)
            return list(groups_)

        if not await self.has('group.detach'):
            raise PermissionsException()

        if not name.strip():
            return

        if not self.is_super and self.email == recipient.email:
            raise AppException('ILLEGAL_ACTION: You cannot modify your own groups.')

        groups = _detach(name)
        recipient.groups = groups
        await session.commit()

        if caching:
            caching(recipient.email, list(groups))
        if async_callback:
            await async_callback(recipient.email, list(groups))

    async def attach_permission(self, session: AsyncSession, recipient: Self, perm: str,
                                *, caching: Callable[[str, list], None] | None = None,
                                async_callback: Callable[[str, list], Awaitable[None]] | None = None):
        """
        Attach a permission to user. Prevents duplicates.
        :param session:     AsyncSession
        :param recipient:   The user to recieve the group
        :param perm:        Permission
        :param caching:     Callback for caching data
        :param async_callback:    Async callback for generic use
        :raises PermissionsException:
        :return:            None
        """
        # async with AsyncSession(async_engine) as sess:
        # async with asynccontextmanager(get_session)() as sess:
        #     user = await User.get_by_email(sess, 'admin@gmail.com', skip_cache=True)
        def _attach(name_: str) -> list[str]:
            neg_ = f'-{name_}'
            perms_ = set(recipient.permissions)

            if neg_ in perms_:
                perms_.remove(neg_)
            elif name_ not in perms_:
                perms_.add(name_)
            return list(perms_)

        if not await self.has('permission.attach'):
            raise PermissionsException()

        if not perm.strip():
            return

        if not self.is_super and self.email == recipient.email:
            raise AppException('ILLEGAL_ACTION: You cannot modify your own permissions.')

        permissions = _attach(perm)
        recipient.permissions = permissions
        await session.commit()

        if caching:
            caching(recipient.email, permissions)
        if async_callback:
            await async_callback(recipient.email, permissions)

    async def detach_permission(self, session: AsyncSession, recipient: Self, perm: str,
                                *, caching: Callable[[str, list], None] | None = None,
                                async_callback: Callable[[str, list], Awaitable[None]] | None = None):
        """
        Remove a permission from user. If the permission is a starter group (can't be removed) then a negation is added.
        :param session:     AsyncSession
        :param recipient:   The user who's group is to be removed
        :param perm:        Permission
        :param caching:     Callback for caching data
        :param async_callback:    Async callback for generic use
        :raises PermissionsException:
        :return:            None
        """
        def _detach(perm_: str) -> list[str]:
            neg_ = f'-{perm_}'
            perms_ = set(recipient.permissions)

            if perm_ in perms_:
                perms_.remove(perm_)
            else:
                perms_.add(neg_)
            return list(perms_)

        if not await self.has('permission.detach'):
            raise PermissionsException()

        if not perm.strip():
            return

        if not self.is_super and self.email == recipient.email:
            raise AppException('ILLEGAL_ACTION: You cannot modify your own permissions.')

        permissions = _detach(perm)
        recipient.permissions = permissions
        await session.commit()

        if caching:
            caching(recipient.email, list(permissions))
        if async_callback:
            await async_callback(recipient.email, list(permissions))

    # TESTME: Untested
    async def ban(self, session: AsyncSession, recipient: Self,
                  *, caching: Callable[[str, datetime], None] | None = None,
                  async_callback: Callable[[str, datetime], Awaitable[None]] | None = None):
        """
        Ban user.
        :param session:     AsyncSession
        :param recipient:   The user to ban
        :param caching:     Callback for caching data
        :param async_callback:    Async callback for generic use
        :raises PermissionsException:
        :return:            None
        """
        if not await self.has('ban.attach'):
            raise PermissionsException()

        if recipient.banned_at is not None or self.email == recipient.email:
            return

        now = arrow.utcnow().datetime
        recipient.banned_at = now
        await session.commit()

        if caching:
            caching(recipient.email, now)
        if async_callback:
            await async_callback(recipient.email, now)

    # TESTME: Untested
    async def unban(self, session: AsyncSession, recipient: Self,
                    *, caching: Callable[[str, None], None] | None = None,
                    async_callback: Callable[[str, None], Awaitable[None]] | None = None):
        """
        Unban user.
        :param session:     AsyncSession
        :param recipient:   The user to unban
        :param caching:     Callback for caching data
        :param async_callback:    Async callback for generic use
        :raises PermissionsException:
        :return:            None
        """
        if not await self.has('ban.detach'):
            raise PermissionsException()

        if recipient.banned_at is None or self.email == recipient.email:
            return

        recipient.banned_at = None
        await session.commit()

        if caching:
            caching(recipient.email, None)
        if async_callback:
            await async_callback(recipient.email, None)

Attributes

banned_at: datetime | None = Field(sa_column=Column(DateTime(timezone=True), nullable=True, index=True)) class-attribute instance-attribute

display: str = Field(max_length=199) class-attribute instance-attribute

email: str = Field(max_length=190, unique=True) class-attribute instance-attribute

groups: list[str] = Field(sa_column=Column(ARRAY(String)), default=[]) class-attribute instance-attribute

is_active: bool = Field(default=True) class-attribute instance-attribute

is_super: bool property

is_verified: bool = Field(default=True) class-attribute instance-attribute

permissions: list[str] = Field(sa_column=Column(ARRAY(String)), default=[]) class-attribute instance-attribute

role: str = Field(max_length=20, default='user') class-attribute instance-attribute

timezone: str | None = Field(max_length=190, default='+0000') class-attribute instance-attribute

username: str = Field(max_length=190, unique=True) class-attribute instance-attribute

Functions

__repr__()

Source code in fastbase/models/UserMod.py
def __repr__(self):
    return modstr(self, 'email')

attach_group(session, recipient, name, *, caching=None, async_callback=None) async

Attach a group to user. Prevents duplicates.

PARAMETER DESCRIPTION
session

AsyncSession

TYPE: AsyncSession

recipient

The user to recieve the group

TYPE: Self

name

Group name

TYPE: str

caching

Callback for caching data

TYPE: Callable[[str, list], None] | None DEFAULT: None

async_callback

Async callback for generic use

TYPE: Callable[[str, list], Awaitable[None]] | None DEFAULT: None

RETURNS DESCRIPTION

None

RAISES DESCRIPTION
PermissionsException
Source code in fastbase/models/UserMod.py
async def attach_group(self, session: AsyncSession, recipient: Self, name: str,
                       *, caching: Callable[[str, list], None] | None = None,
                       async_callback: Callable[[str, list], Awaitable[None]] | None = None):
    """
    Attach a group to user. Prevents duplicates.
    :param session:     AsyncSession
    :param recipient:   The user to recieve the group
    :param name:        Group name
    :param caching:     Callback for caching data
    :param async_callback:    Async callback for generic use
    :raises PermissionsException:
    :return:            None
    """
    # async with AsyncSession(async_engine) as sess:
    # async with asynccontextmanager(get_session)() as sess:
    #     user = await User.get_by_email(sess, 'admin@gmail.com', skip_cache=True)
    def _attach(name_: str) -> list[str]:
        neg_ = f'-{name_}'
        groups_ = set(recipient.groups)

        if neg_ in groups_:
            groups_.remove(neg_)
        elif name_ not in groups_:
            groups_.add(name_)
        return list(groups_)

    if not await self.has('group.attach'):
        raise PermissionsException()

    if not name.strip():
        return

    if not self.is_super and self.email == recipient.email:
        raise AppException('ILLEGAL_ACTION: You cannot modify your own groups.')

    groups = _attach(name)
    recipient.groups = groups
    await session.commit()

    if caching:
        caching(recipient.email, groups)
    if async_callback:
        await async_callback(recipient.email, groups)

attach_permission(session, recipient, perm, *, caching=None, async_callback=None) async

Attach a permission to user. Prevents duplicates.

PARAMETER DESCRIPTION
session

AsyncSession

TYPE: AsyncSession

recipient

The user to recieve the group

TYPE: Self

perm

Permission

TYPE: str

caching

Callback for caching data

TYPE: Callable[[str, list], None] | None DEFAULT: None

async_callback

Async callback for generic use

TYPE: Callable[[str, list], Awaitable[None]] | None DEFAULT: None

RETURNS DESCRIPTION

None

RAISES DESCRIPTION
PermissionsException
Source code in fastbase/models/UserMod.py
async def attach_permission(self, session: AsyncSession, recipient: Self, perm: str,
                            *, caching: Callable[[str, list], None] | None = None,
                            async_callback: Callable[[str, list], Awaitable[None]] | None = None):
    """
    Attach a permission to user. Prevents duplicates.
    :param session:     AsyncSession
    :param recipient:   The user to recieve the group
    :param perm:        Permission
    :param caching:     Callback for caching data
    :param async_callback:    Async callback for generic use
    :raises PermissionsException:
    :return:            None
    """
    # async with AsyncSession(async_engine) as sess:
    # async with asynccontextmanager(get_session)() as sess:
    #     user = await User.get_by_email(sess, 'admin@gmail.com', skip_cache=True)
    def _attach(name_: str) -> list[str]:
        neg_ = f'-{name_}'
        perms_ = set(recipient.permissions)

        if neg_ in perms_:
            perms_.remove(neg_)
        elif name_ not in perms_:
            perms_.add(name_)
        return list(perms_)

    if not await self.has('permission.attach'):
        raise PermissionsException()

    if not perm.strip():
        return

    if not self.is_super and self.email == recipient.email:
        raise AppException('ILLEGAL_ACTION: You cannot modify your own permissions.')

    permissions = _attach(perm)
    recipient.permissions = permissions
    await session.commit()

    if caching:
        caching(recipient.email, permissions)
    if async_callback:
        await async_callback(recipient.email, permissions)

ban(session, recipient, *, caching=None, async_callback=None) async

Ban user.

PARAMETER DESCRIPTION
session

AsyncSession

TYPE: AsyncSession

recipient

The user to ban

TYPE: Self

caching

Callback for caching data

TYPE: Callable[[str, datetime], None] | None DEFAULT: None

async_callback

Async callback for generic use

TYPE: Callable[[str, datetime], Awaitable[None]] | None DEFAULT: None

RETURNS DESCRIPTION

None

RAISES DESCRIPTION
PermissionsException
Source code in fastbase/models/UserMod.py
async def ban(self, session: AsyncSession, recipient: Self,
              *, caching: Callable[[str, datetime], None] | None = None,
              async_callback: Callable[[str, datetime], Awaitable[None]] | None = None):
    """
    Ban user.
    :param session:     AsyncSession
    :param recipient:   The user to ban
    :param caching:     Callback for caching data
    :param async_callback:    Async callback for generic use
    :raises PermissionsException:
    :return:            None
    """
    if not await self.has('ban.attach'):
        raise PermissionsException()

    if recipient.banned_at is not None or self.email == recipient.email:
        return

    now = arrow.utcnow().datetime
    recipient.banned_at = now
    await session.commit()

    if caching:
        caching(recipient.email, now)
    if async_callback:
        await async_callback(recipient.email, now)

detach_group(session, recipient, name, *, caching=None, async_callback=None) async

Remove a group from user. If the group is a starter group (can't be removed) then a negation is added.

PARAMETER DESCRIPTION
session

AsyncSession

TYPE: AsyncSession

recipient

The user who's group is to be removed

TYPE: Self

name

Group name

TYPE: str

caching

Callback for caching data

TYPE: Callable[[str, list], None] | None DEFAULT: None

async_callback

Async callback for generic use

TYPE: Callable[[str, list], Awaitable[None]] | None DEFAULT: None

RETURNS DESCRIPTION

None

RAISES DESCRIPTION
PermissionsException
Source code in fastbase/models/UserMod.py
async def detach_group(self, session: AsyncSession, recipient: Self, name: str,
                       *, caching: Callable[[str, list], None] | None = None,
                       async_callback: Callable[[str, list], Awaitable[None]] | None = None):
    """
    Remove a group from user. If the group is a starter group (can't be removed) then a negation is added.
    :param session:     AsyncSession
    :param recipient:   The user who's group is to be removed
    :param name:        Group name
    :param caching:     Callback for caching data
    :param async_callback:    Async callback for generic use
    :raises PermissionsException:
    :return:            None
    """
    def _detach(name_: str) -> list[str]:
        neg_ = f'-{name_}'
        groups_ = set(recipient.groups)

        if name_ in groups_:
            groups_.remove(name_)
        else:
            groups_.add(neg_)
        return list(groups_)

    if not await self.has('group.detach'):
        raise PermissionsException()

    if not name.strip():
        return

    if not self.is_super and self.email == recipient.email:
        raise AppException('ILLEGAL_ACTION: You cannot modify your own groups.')

    groups = _detach(name)
    recipient.groups = groups
    await session.commit()

    if caching:
        caching(recipient.email, list(groups))
    if async_callback:
        await async_callback(recipient.email, list(groups))

detach_permission(session, recipient, perm, *, caching=None, async_callback=None) async

Remove a permission from user. If the permission is a starter group (can't be removed) then a negation is added.

PARAMETER DESCRIPTION
session

AsyncSession

TYPE: AsyncSession

recipient

The user who's group is to be removed

TYPE: Self

perm

Permission

TYPE: str

caching

Callback for caching data

TYPE: Callable[[str, list], None] | None DEFAULT: None

async_callback

Async callback for generic use

TYPE: Callable[[str, list], Awaitable[None]] | None DEFAULT: None

RETURNS DESCRIPTION

None

RAISES DESCRIPTION
PermissionsException
Source code in fastbase/models/UserMod.py
async def detach_permission(self, session: AsyncSession, recipient: Self, perm: str,
                            *, caching: Callable[[str, list], None] | None = None,
                            async_callback: Callable[[str, list], Awaitable[None]] | None = None):
    """
    Remove a permission from user. If the permission is a starter group (can't be removed) then a negation is added.
    :param session:     AsyncSession
    :param recipient:   The user who's group is to be removed
    :param perm:        Permission
    :param caching:     Callback for caching data
    :param async_callback:    Async callback for generic use
    :raises PermissionsException:
    :return:            None
    """
    def _detach(perm_: str) -> list[str]:
        neg_ = f'-{perm_}'
        perms_ = set(recipient.permissions)

        if perm_ in perms_:
            perms_.remove(perm_)
        else:
            perms_.add(neg_)
        return list(perms_)

    if not await self.has('permission.detach'):
        raise PermissionsException()

    if not perm.strip():
        return

    if not self.is_super and self.email == recipient.email:
        raise AppException('ILLEGAL_ACTION: You cannot modify your own permissions.')

    permissions = _detach(perm)
    recipient.permissions = permissions
    await session.commit()

    if caching:
        caching(recipient.email, list(permissions))
    if async_callback:
        await async_callback(recipient.email, list(permissions))

exists(session, email) async classmethod

Check if a user exists

Source code in fastbase/models/UserMod.py
@classmethod
async def exists(cls, session: AsyncSession, email: EmailStr) -> bool:
    """Check if a user exists"""
    stmt = select(cls.id).where(cls.email == email)
    execdata = await session.exec(stmt)
    if _ := execdata.first():
        return True

get_by_email(session, email) async classmethod

Source code in fastbase/models/UserMod.py
@classmethod
async def get_by_email(cls, session: AsyncSession, email: str) -> Type[Self]:
    raise NotImplementedError()

has(data) async

Source code in fastbase/models/UserMod.py
async def has(self, data: str) -> bool:
    raise NotImplementedError()

unban(session, recipient, *, caching=None, async_callback=None) async

Unban user.

PARAMETER DESCRIPTION
session

AsyncSession

TYPE: AsyncSession

recipient

The user to unban

TYPE: Self

caching

Callback for caching data

TYPE: Callable[[str, None], None] | None DEFAULT: None

async_callback

Async callback for generic use

TYPE: Callable[[str, None], Awaitable[None]] | None DEFAULT: None

RETURNS DESCRIPTION

None

RAISES DESCRIPTION
PermissionsException
Source code in fastbase/models/UserMod.py
async def unban(self, session: AsyncSession, recipient: Self,
                *, caching: Callable[[str, None], None] | None = None,
                async_callback: Callable[[str, None], Awaitable[None]] | None = None):
    """
    Unban user.
    :param session:     AsyncSession
    :param recipient:   The user to unban
    :param caching:     Callback for caching data
    :param async_callback:    Async callback for generic use
    :raises PermissionsException:
    :return:            None
    """
    if not await self.has('ban.detach'):
        raise PermissionsException()

    if recipient.banned_at is None or self.email == recipient.email:
        return

    recipient.banned_at = None
    await session.commit()

    if caching:
        caching(recipient.email, None)
    if async_callback:
        await async_callback(recipient.email, None)