API Reference
API Reference¶
This document provides a detailed reference for the SRF core API.
Views¶
BaseViewSet¶
Base class for all ViewSets.
from srf.views import BaseViewSet
from pydantic import BaseModel
class GenericAPIView(BaseViewSet):
"""Base class for ViewSet"""
# Common configurations for subclasses
schema: BaseModel = None # pydantic model
permission_classes = () # check_permissions treats as empty list when not declared
search_fields = [] # search fields, read by SearchFilter
filter_fields = {} # field mapping for filtering, read by FilterClass
ordering_fields = {} # field mapping for ordering, read by OrderingFactory
queryset = None # 设置 QuerySet / property,或重写 get_queryset()
def get_schema(self, request, *args, is_safe=False, **kwargs):
"""Returns self.schema by default; can be customized based on request method"""
return getattr(self, "schema", None)
def get_queryset(self):
"""Return a per-request QuerySet from self.queryset (clones with .all() when needed)."""
...
def filter_queryset(self, queryset):
"""Apply filter_class backends to queryset (used by list())."""
...
async def check_permissions(self, request):
"""Call has_permission on each permissions (optional); raise Forbidden if denied"""
...
async def check_object_permissions(self, request, obj):
"""Call has_object_permission on each entry; raise Forbidden if denied"""
...
async def get_object(self, request, id: int):
"""Lookup via get_queryset(), then check_object_permissions()"""
...
Mixins¶
CreateModelMixin¶
class CreateModelMixin:
"""Create mixin"""
async def create(self, request, *args, **kwargs):
"""Handle POST requests; call perform_create(sch_model)"""
...
async def perform_create(self, sch_model):
"""Perform creation (can be overridden)"""
...
RetrieveModelMixin¶
class RetrieveModelMixin:
"""Retrieve mixin"""
async def retrieve(self, request, pk, *args, **kwargs):
"""Handle GET /resource/<pk> requests"""
...
UpdateModelMixin¶
class UpdateModelMixin:
"""Update mixin"""
async def update(self, request, pk, *args, **kwargs):
"""Handle PUT/PATCH requests"""
...
async def perform_update(self, sch_model, orm_model):
"""Perform update (can be overridden)"""
...
DestroyModelMixin¶
class DestroyModelMixin:
"""Destroy mixin"""
async def destroy(self, request, pk, *args, **kwargs):
"""Handle DELETE requests; call perform_destroy(orm_model)"""
...
async def perform_destroy(self, orm_model):
"""Perform deletion (can be overridden)"""
...
ListModelMixin¶
class ListModelMixin:
"""List mixin"""
async def list(self, request, *args, **kwargs):
"""Handle GET /resource requests"""
...
Decorators¶
@action¶
from srf.views.decorators import action
@action(
*,
detail: bool = False, # detail-level action (needs pk)
methods: Sequence[str] = ("GET",), # HTTP methods
url_path: str | None = None, # default: "/<method name>"
url_name: str | None = None, # route name (default: method name)
)
Example:
@action(methods=["get"], detail=False, url_path="featured")
async def featured(self, request):
"""Collection-level action"""
pass
@action(methods=["post"], detail=True, url_path="publish")
async def publish(self, request, pk):
"""Detail-level action"""
pass
Routing¶
SanicRouter¶
from srf.route import SanicRouter
class SanicRouter:
"""Router manager"""
def __init__(self, bp: Blueprint = None, prefix: str = ""):
"""Initialize the router
Args:
bp: Sanic Blueprint instance
prefix: URL prefix
"""
pass
def register(self, path: str, view_cls, name: str = None):
"""Register ViewSet
Args:
path: URL path
view_cls: ViewSet class
name: route name prefix
"""
pass
def get_blueprint(self) -> Blueprint:
"""Get Blueprint"""
pass
Example:
router = SanicRouter(prefix="api")
router.register("products", ProductViewSet, name="products")
app.blueprint(router.get_blueprint())
Permissions¶
BasePermission¶
from srf.permission.permission import BasePermission
class BasePermission:
"""Base class for permissions"""
@staticmethod
def has_permission(request, view=None) -> bool:
"""View-level permission check"""
return True
@staticmethod
def has_object_permission(request, view=None, obj=None) -> bool:
"""Object-level permission check"""
return True
Built-in Permission Classes¶
from srf.permission.permission import (
AllowAny, # Always allow (default in DEFAULT_PERMISSION_CLASSES)
IsAuthenticated, # Requires login
IsRoleAdminUser, # Requires admin role
IsSafeMethodOnly # Allows only safe methods
)
Pagination¶
BasePagination¶
from srf.paginator import BasePagination
class BasePagination:
"""DRF-style base. Subclass and implement methods that raise NotImplementedError."""
@classmethod
def from_queryset(cls, queryset, request):
raise NotImplementedError
async def paginate(self, sch_model=None):
raise NotImplementedError
async def to_dict(self, sch_model=None):
"""Default: await paginate() then model_dump."""
...
def num_pages(self, total_count=None):
raise NotImplementedError
PageNumberPagination¶
from srf.paginator import PageNumberPagination
class PageNumberPagination(BasePagination):
"""Paginator."""
MAX_PAGE_SIZE: int = 100
PAGE_QUERY_PARAM: str = 'page'
PAGE_SIZE_QUERY_PARAM: str = 'page_size'
# Default to 10 if page_size is missing or invalid (fallback from_queryset)
@classmethod
def from_queryset(cls, queryset, request):
"""Create paginator from query set"""
pass
async def paginate(self, sch_model):
"""Execute pagination"""
pass
async def to_dict(self, sch_model):
"""Return dictionary format"""
pass
Filtering¶
BaseFilter¶
from srf.filters.filter import BaseFilter
class BaseFilter:
"""Base filter class"""
def __init__(self, view_class):
self.view_class = view_class
def filter_queryset(self, request, queryset):
"""Filter query set"""
raise NotImplementedError
Built-in Filters¶
from srf.filters.filter import (
SearchFilter, # Search filter
JsonLogicFilter, # JSON Logic filter
QueryParamFilter, # Query parameter filter
OrderingFactory # Ordering filter
)
Authentication¶
JWT Functions¶
from srf.auth.auth import authenticate, retrieve_user, store_user
async def authenticate(request, *args, **kwargs):
"""Validate user credentials, return JWT payload"""
pass
async def retrieve_user(payload, *args, **kwargs):
"""Get user object from JWT payload"""
pass
async def store_user(request, user_id, *args, **kwargs):
"""Store user in request context"""
pass
setup_auth¶
from srf.auth.viewset import setup_auth
setup_auth(
app,
secret=app.config.JWT_SECRET, # Required; missing will throw ServerError
url_prefix="/api/auth", # Default /api/auth
login_path="login", # Path passed to sanic-jwt for authentication
# Other keyword arguments for sanic-jwt Initialize...
)
Middleware¶
Authentication Middleware¶
from srf.middleware.authmiddleware import set_user_to_request_ctx
@app.middleware("request")
async def auth_middleware(request):
await set_user_to_request_ctx(request)
Rate Limiting Middleware¶
from srf.middleware.throttlemiddleware import (
MemoryStorage,
IPRateLimit,
UserRateLimit,
PathRateLimit,
HeaderRateLimit,
throttle_rate
)
storage = MemoryStorage()
app.config.REQUEST_LIMITERS = [
IPRateLimit(100, 60, storage),
UserRateLimit(1000, 60, storage),
]
@app.middleware("request")
async def throttle_middleware(request):
if not await throttle_rate(request):
return json({"error": "Too many requests"}, status=429)
Health Checks¶
BaseHealthCheck¶
from srf.health.base import BaseHealthCheck
class BaseHealthCheck:
"""Base health check class"""
name: str = "base"
timeout: int = 5 # seconds; built-in checks use asyncio.timeout(self.timeout)
def __init__(self, app):
self.app = app
client = getattr(app.ctx, self.name, None)
if client is None:
raise ValueError(f"{self.name} not found in app.ctx")
setattr(self, self.name, client)
async def check(self):
"""Execute check; raises exception on failure"""
raise NotImplementedError
async def run(self):
"""Run check and return (name, status)"""
...
Built-in Health Checks¶
from srf.health.checks import (
RedisCheck, # Redis check (requires app.ctx.redis)
SQLiteCheck, # SQLite check (requires app.ctx.sqlite)
)
# Route reads app.config.HEALTH_CHECK_LIST
app.config.HEALTH_CHECK_LIST = [RedisCheck, SQLiteCheck]
Exceptions¶
Custom Exceptions¶
from srf.exceptions import (
TargetObjectAlreadyExist, # Object already exists (409)
ImproperlyConfigured # Configuration error (500)
)
HTTP Status Codes¶
HTTPStatus¶
from srf.views.http_status import (
HTTPStatus,
is_informational,
is_success,
is_redirect,
is_client_error,
is_server_error,
)
# Status code constants
HTTPStatus.HTTP_200_OK
HTTPStatus.HTTP_201_CREATED
HTTPStatus.HTTP_204_NO_CONTENT
HTTPStatus.HTTP_400_BAD_REQUEST
HTTPStatus.HTTP_401_UNAUTHORIZED
HTTPStatus.HTTP_403_FORBIDDEN
HTTPStatus.HTTP_404_NOT_FOUND
HTTPStatus.HTTP_422_UNPROCESSABLE_ENTITY
HTTPStatus.HTTP_429_TOO_MANY_REQUESTS
HTTPStatus.HTTP_500_INTERNAL_SERVER_ERROR
# Helper functions (module-level functions, not methods of HTTPStatus)
is_informational(code) # 1xx
is_success(code) # 2xx
is_redirect(code) # 3xx
is_client_error(code) # 4xx
is_server_error(code) # 5xx
Configuration¶
LazySettings (settings)¶
from srf.config import settings
# Bind Sanic app.config (optional; request path takes precedence)
settings.set_app(app)
# Common configurations (uppercase items come from srf.config.settings, can be overridden by app.config)
settings.JWT_ACCESS_TOKEN_EXPIRES
settings.NON_AUTH_ENDPOINTS
settings.DEFAULT_FILTERS
settings.DEFAULT_PERMISSION_CLASSES
settings.EMAIL_CODE_REDIS # Default "EMAIL_CODE"
settings.REQUEST_LIMITERS # Default []
settings.HEALTH_CHECK_LIST # Default []
settings.SOCIAL_CONFIG
# JWT_SECRET must be set by application in app.config, no module-level default
app.config.JWT_SECRET = "..."
# srfconfig is a deprecated alias (issues DeprecationWarning on first use)
# from srf.config import srfconfig
Utility Functions¶
Email Sending¶
from srf.tools.email import send_email
await send_email(
to_email: str, # Recipient
subject: str = "", # Subject
content: str = "", # Content
)
Type Hints¶
from sanic import Request
from tortoise.queryset import QuerySet
from pydantic import BaseModel
from typing import Type, List, Dict, Optional
class MyViewSet(BaseViewSet):
@property
def queryset(self) -> QuerySet:
return Product.all()
def get_schema(self, request: Request, is_safe: bool = False) -> Type[BaseModel]:
is_read = request.method.upper() in ("GET", "HEAD", "OPTIONS")
return ProductSchemaReader if is_read or is_safe else ProductSchemaWriter
async def list(self, request: Request) -> JSONResponse:
pass
Full Example¶
from sanic import Sanic
from sanic.response import json
from tortoise import fields
from tortoise.models import Model
from tortoise.contrib.sanic import register_tortoise
from pydantic import BaseModel, Field
from sanic.constants import SAFE_HTTP_METHODS
from typing import Optional
from srf.views import BaseViewSet
from srf.views.decorators import action
from srf.views.http_status import HTTPStatus
from srf.route import SanicRouter
from srf.permission.permission import IsAuthenticated
from srf.config import settings
# Model
class Product(Model):
id = fields.IntField(pk=True)
name = fields.CharField(max_length=200)
price = fields.DecimalField(max_digits=10, decimal_places=2)
stock = fields.IntField(default=0)
# Schema
class ProductSchemaWriter(BaseModel):
name: str = Field(..., max_length=200)
price: float = Field(..., gt=0)
stock: int = Field(default=0, ge=0)
class ProductSchemaReader(BaseModel):
id: int
name: str
price: float
stock: int
class Config:
from_attributes = True
# ViewSet
class ProductViewSet(BaseViewSet):
permission_classes = (IsAuthenticated,)
search_fields = ["name"]
filter_fields = {"min_price": "price__gte"}
ordering_fields = {"price": "price"}
@property
def queryset(self):
return Product.all()
def get_schema(self, request, is_safe=False):
if request.method.upper() in SAFE_HTTP_METHODS or is_safe:
return ProductSchemaReader
return ProductSchemaWriter
@action(detail=False, methods=["get"], url_path="featured")
async def featured(self, request):
products = await Product.filter(stock__gt=0).limit(10)
schema = self.get_schema(request, is_safe=True)
data = [schema.model_validate(p).model_dump() for p in products]
return json({"results": data}, status=HTTPStatus.HTTP_200_OK)
# Application
app = Sanic("MyApp")
app.config.JWT_SECRET = "change-me"
settings.set_app(app)
# Database
register_tortoise(
app,
db_url="sqlite://db.sqlite3",
modules={"models": ["__main__"]},
generate_schemas=True,
)
# Routing
router = SanicRouter(prefix="api")
router.register("products", ProductViewSet)
app.blueprint(router.get_blueprint())
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
Next Steps¶
- See Getting Started to create your first project
- Read Core Concepts to understand features in depth
- Browse Configuration Options to learn about configuration parameters