flask_more_smorest.perms
Permissions module for Flask-More-Smorest.
This module provides the permissions system including the Api with auth, BasePermsModel with permission checks, user models, and PermsBlueprintMixin.
Quick Start:
from flask_more_smorest.perms import init_fms from flask_more_smorest.perms.models.defaults import User
# Register user models init_fms(user=User)
# Use the UserBlueprint user_bp = UserBlueprint(register=False) api.register_blueprint(user_bp)
# Note: no global singleton user_bp is provided; create explicitly.
Functions
Clear all user model registrations and helper functions. |
- class flask_more_smorest.perms.AbstractDomain(**kwargs)[source]
Abstract Domain model for multi-domain support.
This is an abstract base class - it does NOT create a database table. Subclasses must define concrete fields and table configuration.
Domains represent distinct contexts within an application (e.g., organizations, tenants, or projects) where roles can be scoped.
Subclassing example:
from flask_more_smorest.perms import AbstractDomain class CustomDomain(AbstractDomain): __tablename__ = "domain" name: Mapped[str] = mapped_column(db.String(255), nullable=False) display_name: Mapped[str] = mapped_column(db.String(255), nullable=False) active: Mapped[bool] = mapped_column(db.Boolean, default=True, nullable=False) # Optional: custom fields organization_id: Mapped[str] = mapped_column(db.String(50)) settings: Mapped[dict] = mapped_column(db.JSON, default={})
- class flask_more_smorest.perms.AbstractToken(**kwargs)[source]
Abstract Token model for API authentication.
This is an abstract base class - it does NOT create a database table. Subclasses must define concrete fields and table configuration.
Permission checks are delegated to the owning user by default (via UserOwnershipMixin). Override _can_read/_can_write/_can_create to customize.
Subclassing example:
from flask_more_smorest.perms import AbstractToken class CustomToken(AbstractToken): __tablename__ = "token" token: Mapped[str] = mapped_column(db.String(1024), nullable=False) description: Mapped[str | None] = mapped_column(db.String(64), nullable=True) expires_at: Mapped[sa.DateTime | None] = mapped_column(sa.DateTime(), nullable=True) revoked: Mapped[bool] = mapped_column(db.Boolean(), nullable=False, default=False) revoked_at: Mapped[sa.DateTime | None] = mapped_column(sa.DateTime(), nullable=True) # Optional: custom fields last_used_at: Mapped[sa.DateTime | None] = mapped_column(sa.DateTime(), nullable=True) ip_address: Mapped[str | None] = mapped_column(db.String(45), nullable=True)
- class flask_more_smorest.perms.AbstractUser(**kwargs)[source]
Abstract User model with email/password auth, roles, and domain support.
This is an abstract base class - it does NOT create a database table. Subclasses must define concrete fields and table configuration.
Features (all inherited): - Email/password authentication - Roles management via UserRole relationship - Settings management via UserSetting relationship - Token management via Token relationship - Permission checks (_can_read, _can_write, _can_create) - Admin properties (is_admin, is_superadmin) - Role checking (has_role, list_roles)
Subclassing example:
from flask_more_smorest.perms import AbstractUser, init_fms class CustomUser(AbstractUser): # Optional: custom fields only bio: Mapped[str | None] = mapped_column(sa.String(500)) def _can_write(self, user) -> bool: return super()._can_write(user) # Register with the system init_fms(user=CustomUser)
- roles = <_RelationshipDeclared at 0x7a2d88ffbc50; no key>
- settings = <_RelationshipDeclared at 0x7a2d88ffb1b0; no key>
- tokens = <_RelationshipDeclared at 0x7a2d88c35e50; no key>
- classmethod get_current_user()[source]
Get the current authenticated user of this User subclass.
This provides zero-boilerplate typed access to the current user. Uses the application’s configured authentication (JWT or custom getter).
- Return type:
- Returns:
Current user instance of this User subclass if authenticated, None otherwise
Example
>>> user = AbstractUser.get_current_user() >>> user = MyCustomUser.get_current_user()
- normalize_email(email)[source]
Normalize email to lowercase for case-insensitive lookups.
Emails are automatically converted to lowercase when set, ensuring: - Case-insensitive login (user@example.com == USER@EXAMPLE.COM) - Prevention of duplicate registrations with different cases - Efficient database queries using the email index - Consistent email storage throughout the application
- has_role(role, domain_name=None)[source]
Check if user has specified role, optionally scoped to domain.
- Parameters:
- Return type:
- Returns:
True if user has the role, False otherwise
Example
>>> user.has_role("ADMIN") True >>> user.has_role("ADMIN", domain_name="main") True
- has_domain_access(domain_id)[source]
Check if user has access to a specific domain.
Users have access to a domain if they have any role associated with that domain, or if they have a wildcard role (*). Superadmins automatically have access.
- Parameters:
domain_id (
UUID|None) – Domain UUID to check access for, or None for global access- Return type:
- Returns:
True if user has access to the domain, False otherwise
Example
>>> user.has_domain_access(domain_id) True >>> user.has_domain_access(None) # Global access check True
- class flask_more_smorest.perms.AbstractUserRole(domain_id=None, role=None, **kwargs)[source]
Abstract UserRole model with domain scoping for multi-domain applications.
This is an abstract base class - it does NOT create a database table. Subclasses must define concrete fields and table configuration.
Supports custom role enums by accepting any string/enum value:
from enum import Enum class CustomRole(str, Enum): SUPERADMIN = "SUPERADMIN" ADMIN = "ADMIN" MANAGER = "MANAGER" USER = "USER" class CustomUserRole(AbstractUserRole): __tablename__ = "user_role" user_id: Mapped[uuid.UUID] = mapped_column( sa.Uuid(as_uuid=True), db.ForeignKey("user.id"), nullable=False ) domain_id: Mapped[uuid.UUID | None] = mapped_column( sa.Uuid(as_uuid=True), db.ForeignKey("domain.id"), nullable=True, default=None, ) _role: Mapped[str] = mapped_column("role", sa.String(50), nullable=False) # Create roles with custom enum values role = CustomUserRole(user=user, role=CustomRole.MANAGER)
- user = <_RelationshipDeclared at 0x7a2d88c35f90; no key>
- domain = <_RelationshipDeclared at 0x7a2d887d34d0; no key>
- class flask_more_smorest.perms.AbstractUserSetting(**kwargs)[source]
Abstract UserSetting model for key-value storage.
This is an abstract base class - it does NOT create a database table. Subclasses must define concrete fields and table configuration.
Permission checks are delegated to the owning user by default (via UserOwnershipMixin). Override _can_read/_can_write/_can_create to customize.
Subclassing example:
from flask_more_smorest.perms import AbstractUserSetting class CustomUserSetting(AbstractUserSetting): __tablename__ = "user_setting" key: Mapped[str] = mapped_column(db.String(80), nullable=False) value: Mapped[str | None] = mapped_column(db.String(1024), nullable=True) __table_args__ = (db.UniqueConstraint("user_id", "key"),) # Optional: custom fields metadata: Mapped[dict] = mapped_column(db.JSON, default={}) encrypted: Mapped[bool] = mapped_column(db.Boolean, default=False)
- class flask_more_smorest.perms.Api(app=None, *, spec_kwargs=None)[source]
Extended Api with JWT authentication and permission checking.
This class extends Flask-Smorest’s Api to automatically: - Configure JWT authentication in OpenAPI spec - Enforce authentication on non-public endpoints - Check admin permissions on admin-only endpoints - Customize schema naming for OpenAPI
Example
>>> from flask import Flask >>> from flask_more_smorest.perms import Api >>> >>> app = Flask(__name__) >>> api = Api(app)
- class flask_more_smorest.perms.BasePermsModel(**kwargs)[source]
Base model with permission checking.
- perms_disabled
Disable permission checks (default: False)
Example
>>> class Article(BasePermsModel): ... title: Mapped[str] = mapped_column(sa.String(200)) ... def _can_write(self, user) -> bool: ... return user is not None and self.user_id == user.id
- perms_disabled = False
- classmethod bypass_perms(cls)[source]
Temporarily disable permission checking for this model class.
Example
>>> with Article.bypass_perms(): ... article.delete() # No permission check
- classmethod get_by(**kwargs)[source]
Get resource by field values with permission check.
- Return type:
Optional[Self]- Returns:
Instance if found and can_read() is True None if not found None if found but can_read() is False and RETURN_404_ON_ACCESS_DENIED is True
- Raises:
ForbiddenError – If found but can_read() is False
- class flask_more_smorest.perms.HasUserMixin[source]
Adds user_id foreign key and user relationship to a model.
- Configuration:
__user_field_name__: Custom alias for user_id (default: “user_id”)__user_relationship_name__: Custom alias for user (default: “user”)__user_id_nullable__: Allow NULL owner IDs (default: False)__user_backref_name__: Custom backref on User modelNone(default): Auto-generate as{tablename}s(e.g., “articles”)Custom string: Use specified name
"": Skip backref creation
Example
>>> class Article(BasePermsModel, HasUserMixin): ... __user_backref_name__ = "written_articles" ... title: Mapped[str] = mapped_column(sa.String(200)) >>> user.written_articles # Custom backref
- classmethod __init_subclass__(**kwargs)[source]
Configure user field and relationship aliases on subclass creation.
- user_id = <sqlalchemy.orm.properties.MappedColumn object>
- user
Relationship to the registered User model.
Uses lazy resolution via lambda to support custom User models registered through init_fms(). The lambda is evaluated during mapper configuration, allowing get_user_model() to return the correct registered User class.
- class flask_more_smorest.perms.PermsBlueprint(name, import_name, model=None, schema=None, model_import_name=None, schema_import_name=None, res_id='id', res_id_param=None, methods=[CRUDMethod.INDEX, CRUDMethod.GET, CRUDMethod.POST, CRUDMethod.PATCH, CRUDMethod.DELETE], skip_methods=None, default_page_size=20, db_session=None, static_folder=None, static_url_path=None, template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, root_path=None, cli_group=None)[source]
CRUD Blueprint with permission annotations.
Combines CRUDBlueprint functionality with PermsBlueprintMixin to provide automatic CRUD operations with permission checking support.
- class flask_more_smorest.perms.PermsBlueprintMixin[source]
Blueprint mixin with added annotations for public and admin endpoints.
This mixin extends Flask-Smorest’s Blueprint to provide additional decorators for marking endpoints with special access levels: - public_endpoint: Accessible without authentication - admin_endpoint: Requires admin privileges
Example
>>> class MyBlueprint(Blueprint, PermsBlueprintMixin): ... pass >>> bp = MyBlueprint('items', __name__) >>> @bp.route('/') >>> @bp.public_endpoint >>> def list_items(): ... return []
- public_endpoint(func)[source]
Decorator to mark an endpoint as public.
Public endpoints do not require authentication and can be accessed by anyone.
- Parameters:
func (
Callable) – The endpoint function to mark as public- Return type:
- Returns:
The decorated function with public annotation
Example
>>> @bp.route('/health') >>> @bp.public_endpoint >>> def health_check(): ... return {'status': 'ok'}
- admin_endpoint(func)[source]
Decorator to mark an endpoint as admin only.
Admin endpoints require the user to have admin privileges. The Api class enforces this during request handling.
- Parameters:
func (
Callable) – The endpoint function to mark as admin only- Return type:
- Returns:
The decorated function with admin annotation
Example
>>> @bp.route('/users/<uuid:user_id>') >>> @bp.admin_endpoint >>> def delete_user(user_id): ... # Only admins can delete users ... pass
- class flask_more_smorest.perms.ProfileMixin[source]
Adds profile fields: first_name, last_name, display_name, avatar_url.
Property:
full_namereturns combined first/last name.- property full_name: str
Get formatted full name.
- Returns:
Full name as “first last”, or just first or last if one is missing
- class flask_more_smorest.perms.SoftDeleteMixin[source]
Soft delete with deleted_at timestamp and helper methods.
Methods:
soft_delete()marks as deleted,restore()clears. Property:is_deletedreturns True if deleted_at is not None.- property is_deleted: bool
Check if record is soft deleted.
- Returns:
True if record has been soft deleted
- class flask_more_smorest.perms.TimestampMixin[source]
Adds authentication-related timestamps: last_login_at, email_verified_at.
- class flask_more_smorest.perms.UserBlueprint(name='users', import_name='flask_more_smorest.perms.user_blueprint', model=None, schema=None, url_prefix='/api/users/', methods=None, skip_methods=None, register=False, **kwargs)[source]
Blueprint for User CRUD operations with authentication endpoints.
This blueprint extends CRUDBlueprint to provide: - Standard CRUD operations for User model (GET, POST, PATCH, DELETE) - Public login endpoint (POST /login/) - Current user profile endpoint (GET /me/)
When the User model has PUBLIC_REGISTRATION=True, the POST endpoint is automatically made public to allow unauthenticated user registration.
- Parameters:
name (
str) – Blueprint name (default: “users”)import_name (
str) – Import name (default: __name__)model (
type[BaseModel] |str|None) – Model class or string (default: User from registry)schema (
type[Schema] |str|None) – Schema class or string (default: UserSchema)url_prefix (
str|None) – URL prefix for all routes (default: “/api/users/”)methods (
list[CRUDMethod] |Mapping[CRUDMethod,MethodConfig|bool] |None) – CRUD methods to enable (default: all methods)skip_methods (
list[CRUDMethod] |None) – CRUD methods to disable (default: None)register (
bool) – If True, register the model with init_fms (default: False)**kwargs (
Any) – Additional arguments passed to CRUDBlueprint
Example
>>> user_bp = UserBlueprint() >>> app.register_blueprint(user_bp)
>>> # With custom configuration >>> user_bp = UserBlueprint( ... url_prefix="/api/v2/users/", ... skip_methods=[CRUDMethod.DELETE] ... )
>>> # Register custom user model >>> user_bp = UserBlueprint(model=MyUser, register=True)
>>> # Enable public registration >>> class PublicUser(User): ... PUBLIC_REGISTRATION = True >>> public_bp = UserBlueprint(model=PublicUser)
- class flask_more_smorest.perms.UserOwnershipMixin[source]
User-owned resources with configurable permission delegation.
Two modes:
Simple Ownership (default,
__delegate_to_user__ = False): - Comparesuser_id == current_user.id- Use for: Notes, posts, commentsDelegated Permissions (
__delegate_to_user__ = True): - Callsself.user._can_write(current_user)- Use for: Tokens, settings, API keys
- __delegate_to_user__
Delegate to user’s permission methods (default: False)
- __user_id_nullable__
Allow NULL owner IDs (default: False)
Example
>>> class Token(UserOwnershipMixin, BasePermsModel): ... __delegate_to_user__ = True ... token: Mapped[str] = mapped_column(sa.String(500)) >>> # Delegates to user's permission methods
- flask_more_smorest.perms.clear_registration()[source]
Clear all user model registrations and helper functions.
- Return type:
- flask_more_smorest.perms.get_current_user(user_type=None)[source]
- Overloads:
→ AbstractUser | None
user_type (type[UserT]) → UserT | None
Get the current authenticated user.
Resolution order: 1. Registered custom getter (via init_fms) 2. Default: JWT-based authentication (built-in)
- flask_more_smorest.perms.get_current_user_func()[source]
Get the registered get_current_user function.
- Return type:
Callable[[],AbstractUser|None] |None- Returns:
Registered function or None
- flask_more_smorest.perms.init_fms(user=None, role=None, token=None, domain=None, setting=None, get_current_user=None)[source]
Initialize Flask-More-Smorest perms integration.
This is the primary integration point. It registers models and helper functions in a single call. Missing models are filled with defaults.
The first call initializes models + helpers. Later calls may only update helper functions (model changes are rejected).
- Return type:
- flask_more_smorest.perms.init_jwt(app)[source]
Initialize JWTManager with user lookup callbacks.
- Parameters:
app (
Flask) – Flask application to initialize JWT for- Raises:
RuntimeError – If JWT_SECRET_KEY is not set in production (when DEBUG and TESTING are both False)
- Return type:
- flask_more_smorest.perms.is_current_user_admin()[source]
Check if the current user is an admin.
- Return type:
- flask_more_smorest.perms.is_current_user_superadmin()[source]
Check if the current user is a superadmin.
- Return type:
Modules
Extended Flask-Smorest API with authentication and permission support. |
|
Base permission-aware model for Flask-More-Smorest. |
|
Reusable mixins for User models and other models in Flask-More-Smorest. |
|
User-related models for Flask-More-Smorest. |
|
Blueprint Mixin to support method annotation for access control. |
|
User Blueprint with authentication endpoints. |
|
User context helpers for flask-more-smorest. |
|
User model registry for Flask-More-Smorest. |