flask_more_smorest
Flask-More-Smorest Extensions.
A powerful extension library for Flask-Smorest that provides automatic CRUD operations, enhanced blueprints with annotations, advanced query filtering capabilities, and extensible user management with custom model support.
- Quick Start Example:
>>> from flask import Flask >>> from flask_more_smorest import BaseModel, CRUDBlueprint, init_db >>> from flask_more_smorest.perms import Api >>> from flask_more_smorest.sqla import db >>> from sqlalchemy.orm import Mapped, mapped_column >>> >>> app = Flask(__name__) >>> app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db' >>> >>> class Product(BaseModel): ... name: Mapped[str] = mapped_column(db.String(100)) ... price: Mapped[float] = mapped_column(db.Float) >>> >>> init_db(app) >>> api = Api(app) >>> >>> # Create CRUD blueprint using model class directly >>> products_bp = CRUDBlueprint( ... 'products', __name__, ... model=Product, # Use class (preferred) ... schema=Product.Schema, # Auto-generated schema ... url_prefix='/api/products/' ... ) >>> api.register_blueprint(products_bp)
- User Authentication Example:
>>> from flask_more_smorest import User, UserBlueprint >>> from sqlalchemy.orm import Mapped, mapped_column >>> import sqlalchemy as sa >>> >>> # Extend User model with custom fields >>> class Employee(User): ... employee_id: Mapped[str] = mapped_column(sa.String(50)) ... department: Mapped[str] = mapped_column(sa.String(100)) ... ... def _can_write(self, current_user) -> bool: ... # Custom permission logic ... return self.is_admin or (current_user is not None and self.id == current_user.id) >>> >>> # Create authentication blueprint >>> auth_bp = UserBlueprint( ... model=Employee, ... schema=Employee.Schema ... ) >>> api.register_blueprint(auth_bp) >>> # Provides: POST /api/users/login/, GET /api/users/me/, and full CRUD
- class flask_more_smorest.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.BaseModel(**kwargs)[source]
Base model for all application models.
This base class provides: - Automatic UUID primary key generation - Automatic created_at and updated_at timestamps - Automatic Marshmallow schema generation via Model.Schema - Common CRUD operations (get, save, update, delete) - Lifecycle hooks (on_before_create, on_after_create, etc.)
All models should inherit from this class to get these features.
- id
UUID primary key (automatically generated)
- created_at
Timestamp of creation
- updated_at
Timestamp of last update
- Schema
Auto-generated Marshmallow schema class
Example
>>> class Article(BaseModel): ... title: Mapped[str] = mapped_column(sa.String(200)) ... content: Mapped[str] = mapped_column(sa.Text) ... >>> # Use auto-generated schema >>> article_bp = CRUDBlueprint( ... 'articles', __name__, ... model=Article, ... schema=Article.Schema # No need to define custom schema ... )
- __init__(**kwargs)[source]
Initialize the model.
- Parameters:
**kwargs (
object) – Field values to initialize the model with- Raises:
RuntimeError – If database session is not active
- property is_writable: bool
Return whether the instance is writable.
BaseModel does not enforce permissions, so instances are considered writable by default. Permission-aware subclasses can override this property.
- classmethod get_by(**kwargs)[source]
Get resource by field values.
Converts UUID strings to UUID objects automatically for UUID columns.
- Parameters:
**kwargs (
str|int|UUID|bool|None) – Field name and value pairs to filter by- Return type:
Optional[Self]- Returns:
The matching model instance, or None if not found
- Raises:
TypeError – If ID is not a valid UUID string or UUID object
Example
>>> user = User.get_by(email='test@example.com') >>> article = Article.get_by(id='123e4567-e89b-12d3-a456-426614174000')
- classmethod get_by_or_404(**kwargs)[source]
Get resource by field values or raise 404.
- Parameters:
**kwargs (
str|int|UUID|bool|None) – Field name and value pairs to filter by- Return type:
Self- Returns:
The matching model instance
- Raises:
NotFoundError – If no matching resource is found
TypeError – If ID field has invalid UUID format
ForbiddenError – If user doesn’t have read permission
Example
>>> user = User.get_by_or_404(email='test@example.com')
- classmethod get(id)[source]
Get resource by ID.
- Parameters:
- Return type:
Optional[Self]- Returns:
The matching model instance, or None if not found
Example
>>> user = User.get('123e4567-e89b-12d3-a456-426614174000')
- classmethod get_or_404(id)[source]
Get resource by ID or raise 404.
- Parameters:
- Return type:
Self- Returns:
The matching model instance
- Raises:
NotFoundError – If no matching resource is found
Example
>>> user = User.get_or_404('123e4567-e89b-12d3-a456-426614174000')
- classmethod check_exists(id)[source]
Check if resource exists and throw 404 otherwise.
- Parameters:
- Raises:
NotFoundError – If resource doesn’t exist
- Return type:
- classmethod bypass_perms(cls)[source]
No-op context manager for base class (overridden in perms model).
- save(commit=True)[source]
Save the record: add to session and optionally commit.
- Parameters:
commit (
bool) – Whether to commit the transaction (default: True)- Return type:
Self- Returns:
The saved model instance (self)
- Raises:
ForbiddenError – If user doesn’t have permission to create/modify
Example
>>> user = User(email='test@example.com') >>> user.save()
- commit(is_delete=False, *, is_create=None)[source]
Commit the session and call appropriate lifecycle hooks.
- update(commit=True, **kwargs)[source]
Update model fields using key-value pairs.
Supports updating relationships and recursively checks create permissions for nested objects.
- Parameters:
- Raises:
AttributeError – If field doesn’t exist on the model
ForbiddenError – If user doesn’t have permission to modify
- Return type:
Example
>>> user.update(email='new@example.com', is_active=False)
- delete(commit=True)[source]
Delete the record from the database.
- Parameters:
commit (
bool) – Whether to commit the transaction (default: True)- Raises:
ForbiddenError – If user doesn’t have permission to delete
- Return type:
Example
>>> user = User.get(user_id) >>> user.delete()
- get_clone()[source]
Return a copy of the object with a new ID.
Creates a detached copy of this instance with ID set to None, suitable for creating a duplicate record.
- Return type:
Self- Returns:
A new instance with the same field values but no ID
Example
>>> original = User.get(user_id) >>> clone = original.get_clone() >>> clone.save() # Creates new record
- on_before_create()[source]
Hook to be called before creating the object.
Override this method to add custom logic before object creation.
- Return type:
- on_after_create()[source]
Hook to be called after creating the object.
Override this method to add custom logic after object creation.
- Return type:
- on_before_update()[source]
Hook to be called before updating the object.
Override this method to add custom logic before object updates.
- Return type:
- on_after_update()[source]
Hook to be called after updating the object.
Override this method to add custom logic after object updates.
- Return type:
- on_before_delete()[source]
Hook to be called before deleting the object.
Override this method to add custom logic before object deletion.
- Return type:
- on_after_delete()[source]
Hook to be called after deleting the object.
Override this method to add custom logic after object deletion.
- Return type:
- class flask_more_smorest.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.BaseSchema(*args, **kwargs)[source]
Base schema for all Marshmallow schemas.
This schema extends SQLAlchemyAutoSchema with automatic view_args injection for URL parameters and adds an is_writable field for permission checking.
- is_writable
Read-only boolean field indicating if current user can write to the resource
Example
>>> class ArticleSchema(BaseSchema): ... class Meta: ... model = Article ... include_relationships = True
- pre_load(data, **kwargs)[source]
Pre-load hook to handle UUID conversion and view_args injection.
Automatically injects URL parameters from Flask’s request.view_args into the data being loaded, allowing schemas to access route parameters.
- Parameters:
- Return type:
- Returns:
The modified data dictionary with view_args injected
Example
Given a route /articles/<uuid:article_id> and a schema field article_id, the article_id from the URL will be automatically injected into the data if not already present.
- opts: LoadInstanceMixin.Opts = <marshmallow_sqlalchemy.schema.SQLAlchemyAutoSchemaOpts object>
- flask_more_smorest.BlueprintAccessMixin
alias of
PermsBlueprintMixin
- class flask_more_smorest.BlueprintOperationIdMixin(*args, **kwargs)[source]
Blueprint mixin that provides automatic operationId generation.
Extends Flask-Smorest’s Blueprint to automatically generate OpenAPI
operationIdvalues for routes based on the route pattern, HTTP method, class name (after suffix stripping) and response schema.Collection detection for GET requests (in priority order):
Manual
@bp.doc(operationId=…)override → used as-is.Explicit
operation_id=kwarg onroute()→ used as-is.Trailing slash in path → collection (
listXxx).many=Trueon response schema → collection (listXxx).Default → individual (
getXxx).
Examples:
bp = BlueprintOperationIdMixin('users', __name__) # Auto-generated: listUsers @bp.route('/users/') class User(MethodView): def get(self): ... # Custom full operationId for a function-based route @bp.route('/special', operation_id='getSpecialUsers') def special_users(): ... # Prefix for all methods on a MethodView (e.g. deprecation) @bp.route('/legacy', operation_id_prefix='_deprecated_') class Item(MethodView): def get(self): ... # → _deprecated_getItem def post(self): ... # → _deprecated_createItem # Suffix for versioning @bp.route('/v2/users/', operation_id_suffix='_v2') class User(MethodView): def get(self): ... # → listUsers_v2 # Combine prefix and suffix @bp.route('/old', operation_id_prefix='legacy_', operation_id_suffix='_v1') class OldEndpoint(MethodView): def get(self): ... # → legacy_getOldEndpoint_v1
- route(rule, *, operation_id=None, operation_id_prefix=None, operation_id_suffix=None, parameters=None, tags=None, **options)[source]
Override route() to capture operationId customisation options.
- Parameters:
rule (
str) – URL rule for the route.operation_id (
str|None) – Explicit full operationId (function-based routes) or override for every method on a MethodView.operation_id_prefix (
str|None) – Prefix prepended to the auto-generated operationId for every method on a MethodView.operation_id_suffix (
str|None) – Suffix appended to the auto-generated operationId for every method on a MethodView.parameters (
list|None) – OpenAPI path-level parameters (passed to parent).**options (
Any) – Additional Flask routing options.
- Return type:
Callable[[type[MethodView] |Callable],type[MethodView] |Callable]- Returns:
Decorator for the view class or function.
- flask_more_smorest.CRUDBlueprint
alias of
PermsBlueprint
- class flask_more_smorest.CRUDMethod(*values)[source]
Standard CRUD operations supported by CRUDBlueprint.
- INDEX = 'INDEX'
- GET = 'GET'
- POST = 'POST'
- PATCH = 'PATCH'
- DELETE = 'DELETE'
- class flask_more_smorest.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.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.TimestampMixin[source]
Adds authentication-related timestamps: last_login_at, email_verified_at.
- class flask_more_smorest.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.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.convert_snake_to_camel(word)[source]
Convert snake_case string to CamelCase.
- Parameters:
word (
str) – Snake case string to convert- Return type:
- Returns:
CamelCase version of the input string
Example
>>> convert_snake_to_camel("user_profile") 'UserProfile' >>> convert_snake_to_camel("simple") 'simple'
- flask_more_smorest.create_migration(message, directory='migrations')[source]
Create a new migration file.
Automatically detects changes in the database models and generates a migration script.
- Parameters:
- Raises:
RuntimeError – If migrations directory doesn’t exist
- Return type:
Example
>>> create_migration("Add user profile fields")
- flask_more_smorest.downgrade_database(revision, directory='migrations')[source]
Downgrade database to specified revision.
Reverts database migrations to the specified revision.
- Parameters:
- Return type:
Example
>>> downgrade_database("-1") # Downgrade one revision >>> downgrade_database("ae1027a6acf") # Downgrade to specific revision
- flask_more_smorest.generate_filter_schema(base_schema)[source]
Generate a filtering schema from a base schema.
This function creates a new schema class that can be used for filtering queries. It automatically converts certain field types to filter-friendly variants: - Date/DateTime fields become range filters with __from and __to suffixes - Numeric fields get __min and __max filters (equality removed for floats) - Enum fields get __in list filters - Adds optional pagination parameters (page, page_size) to allow validation
- Parameters:
base_schema (
type[Schema] |Schema) – The base Marshmallow schema class to derive filters from- Return type:
- Returns:
A new schema class suitable for filtering operations with all fields made optional and set to load_only
Example
>>> class UserSchema(Schema): ... name = fields.String() ... age = fields.Integer() ... created_at = fields.DateTime() >>> FilterSchema = generate_filter_schema(UserSchema) >>> # FilterSchema will have: name, age, age__min, age__max, >>> # created_at__from, created_at__to
- flask_more_smorest.get_statements_from_filters(kwargs, model)[source]
Convert query kwargs into SQLAlchemy filters based on the schema.
This function processes filtering parameters and converts them to SQLAlchemy WHERE clause conditions, supporting: - Range queries: field__from (>=) and field__to (<=) - Numeric ranges: field__min (>=) and field__max (<=) - Exact equality: field = value
All filter field names are validated against the model’s columns to prevent access to private attributes or non-existent fields.
- Parameters:
- Return type:
- Returns:
Set of SQLAlchemy filter conditions (BinaryExpression objects)
- Raises:
ValueError – If a filter field does not exist on the model
Example
>>> filters = {'age__min': 18, 'age__max': 65, 'is_active': True} >>> stmts = get_statements_from_filters(filters, User) >>> results = User.query.filter(*stmts).all()
- flask_more_smorest.init_db(app)[source]
Initialize the database with the Flask application.
This function binds the SQLAlchemy database instance to the Flask application, making it available throughout the application context.
- Parameters:
app (
Flask) – Flask application instance to initialize the database with- Return type:
Example
>>> from flask import Flask >>> from flask_more_smorest.sqla import init_db >>> >>> app = Flask(__name__) >>> app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db' >>> init_db(app)
- flask_more_smorest.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.init_migrations(app, directory='migrations')[source]
Initialize Alembic migration environment for the application.
Creates the migrations directory structure and configuration files needed for Alembic database migrations.
- Parameters:
- Return type:
Example
>>> from flask import Flask >>> from flask_more_smorest.sqla import init_migrations >>> >>> app = Flask(__name__) >>> init_migrations(app)
- flask_more_smorest.upgrade_database(revision='head', directory='migrations')[source]
Upgrade database to specified revision.
Applies database migrations up to the specified revision.
- Parameters:
- Return type:
Example
>>> upgrade_database() # Upgrade to latest >>> upgrade_database("ae1027a6acf") # Upgrade to specific revision
Modules
CRUD module for automatic RESTful API generation. |
|
Error handling module for Flask-More-Smorest. |
|
Permissions module for Flask-More-Smorest. |
|
Protocol definitions for Flask-More-Smorest. |
|
SQLAlchemy integration module. |
|
Testing helpers for Flask-More-Smorest. |
|
Utility functions for Flask-Smorest CRUD operations. |