API reference¶
This page lists the public objects and the signatures the library calls. The guides explain when to use them; this is the lookup table.
Imports¶
from muffin_rest import API, Api, APIError, RESTHandler
from muffin_rest.peewee import PWRESTHandler
from muffin_rest.peewee.filters import PWFilter, PWFilters
from muffin_rest.peewee.sorting import PWSort, PWSorting
from muffin_rest.sqlalchemy import SARESTHandler
from muffin_rest.sqlalchemy.filters import SAFilter, SAFilters
from muffin_rest.sqlalchemy.sorting import SASort, SASorting
from muffin_rest.mongo import MongoRESTHandler
from muffin_rest.mongo.filters import MongoFilter, MongoFilters
from muffin_rest.mongo.sorting import MongoSort, MongoSorting
Backend imports are optional. muffin_rest re-exports the backend classes when
their dependencies are installed.
API¶
API(app=None, prefix="", *, openapi=True, servers=None, **openapi_info)
| Attribute | Meaning |
|---|---|
app |
Muffin application, or None until setup |
prefix |
API path prefix |
router |
http_router.Router for this API |
openapi |
whether the docs routes are mounted |
openapi_options |
info and servers passed to apispec |
authorize |
async callback set by authorization |
api.setup(app, *, prefix="", openapi=None, servers=None, **openapi_info)
api.route(obj, *paths, **params)
api.authorization(auth)
setup can be called once. It mounts the router on the application and adds the
docs routes. route accepts a handler class or a path string. authorization
stores the callback and returns it unchanged, so it can be used as a decorator.
Handlers¶
RESTBase is the lifecycle. RESTHandler adds the OpenAPI mixin and is the base
for custom, non-database handlers. Backend handlers add storage.
| Class | Meta key | Storage |
|---|---|---|
RESTHandler |
none | your hooks |
PWRESTHandler |
model |
Peewee |
SARESTHandler |
table, database |
SQLAlchemy Core |
MongoRESTHandler |
collection |
MongoDB |
Class attributes:
methods = ("get", "post", "put", "delete")
meta: RESTOptions
meta_class: type[RESTOptions]
_api: API | None
Instance state during a request:
auth # result of authorize()
collection # result of prepare_collection()
filters # parsed filters, set for GET collection
sorting # parsed sorting, set for GET collection
The loaded resource is passed to the HTTP method as the resource keyword
argument. It is not stored on self.
Lifecycle methods:
async def authorize(self, request) -> Any
async def prepare_collection(self, request) -> Any # abstract
async def prepare_resource(self, request) -> Any | None
async def filter(self, request, collection) -> tuple[Any, dict]
async def sort(self, request, collection) -> tuple[Any, dict]
async def paginate(self, request, *, limit, offset) -> tuple[Any, int | None] # abstract
Pagination helpers:
def paginate_prepare_params(self, request) -> tuple[int, int]
def paginate_prepare_headers(self, limit, offset, total) -> dict
Serialization methods:
def get_schema(self, request, *, resource=None, **options) -> ma.Schema
async def load_data(self, request) -> dict | list
async def load(self, request, resource=None, **options) -> Any
async def dump(self, request, data, *, many=False) -> dict | list
Persistence methods:
async def save(self, request, resource, *, update=False) -> Any # abstract
async def save_many(self, request, data, *, update=False) -> list
async def remove(self, request, resource=None) -> Any # abstract
HTTP methods:
async def get(self, request, *, resource=None) -> Any
async def post(self, request, *, resource=None) -> Any
async def put(self, request, *, resource=None) -> Any
async def delete(self, request, *, resource=None) -> Any
Routing:
@RESTHandler.route(*paths, methods=None, hosts=None)
The decorator marks a method for the handler's router. The method receives
resource as a keyword argument.
Options¶
Every backend reads these from Meta:
| Option | Default |
|---|---|
name |
table, collection, or class name |
limit |
0 |
limit_min |
1 |
limit_max |
limit |
limit_total |
True |
filters |
() |
sorting |
() |
Schema |
generated |
schema_base |
backend default |
schema_fields |
{} |
schema_meta |
{} |
schema_unknown |
ma.EXCLUDE |
Backend options:
| Backend | Options |
|---|---|
| Peewee | model, manager, model_pk, delete_recursive |
| SQLAlchemy | table, database, table_pk |
| Mongo | collection, collection_id, aggregate |
See Backends for required fields and defaults.
Filters¶
FILTERS_PARAM = "where"
Filter(name, *, schema_field=None, operator=None, **meta)
Filters(handler, params)
| Member | Meaning |
|---|---|
operators |
mapping of operator name to a callable |
list_ops |
operators whose value is a list |
logic_ops |
operators whose value is a list of conditions |
default_operator |
operator used for scalar values |
apply(collection, data=None, **ctx) |
parse and apply; returns (ops, collection) |
filter(collection, *ops, **ctx) |
apply parsed operators |
parse(data) |
return a tuple of (operator, value) |
get_simple_value(ops) |
value of a scalar filter |
Filters.apply(request, collection, **ctx) |
read where and apply all filters |
Filters.convert(obj, **meta) |
build a filter from a config entry |
Filters.openapi |
query parameter description |
Sorting¶
SORT_PARAM = "sort"
Sort(name, **meta)
Sorting(handler, params)
to_sort(sort_params)
| Member | Meaning |
|---|---|
Sort.apply(collection, *, desc=False, **ctx) |
sort the collection |
Sorting.apply(request, collection, **ctx) |
read sort; returns (collection, dict) |
Sorting.prepare(collection) |
reset existing ordering before applying (Peewee) |
Sorting.convert(obj, **meta) |
build a sorter from a config entry |
Sorting.sort_default(collection) |
apply default sorting |
Sorting.default |
list of sorters marked default |
Sorting.openapi |
query parameter description |
Limits¶
RateLimiter(limit, *, period=60, **opts)
MemoryRateLimiter(limit, **opts)
RedisRateLimiter(limit, *, redis, **opts)
await limiter.check(key) -> bool
check returns True while the key is under the limit and False after.
MemoryRateLimiter is process-local. RedisRateLimiter uses incr and
expire.
Schemas¶
EnumField(enum, *, by_value=True, **kwargs)
load_data(data, schema=None, **params)
CompositePKField(model, **kwargs)
composite_key_to_id(fields, instance, _)
id_to_composite_keys(pk, id_)
SEPARATOR = "::"
EnumField serializes enum values and validates on load. load_data raises
APIError.BAD_REQUEST with errors when Marshmallow validation fails.
CompositePKField renders a Peewee composite key as one string id.
OpenAPI¶
render_openapi(api, request=None) -> dict
OpenAPIMixin.openapi(route, spec, tags) -> dict
render_openapi builds or returns the cached spec. OpenAPIMixin is the class
that adds openapi to RESTHandler.
Errors¶
APIError(content=None, *, status_code=400, **json_data)
InvalidEnpointError
HandlerNotBindedError
APIError is a JSON response and a BaseException subclass. Status shortcuts
come from HTTPStatus: APIError.NOT_FOUND(), APIError.CONFLICT("..."), and
so on.
Query parameters¶
| Parameter | Applies to | Meaning |
|---|---|---|
where |
list | JSON filter object |
sort |
list | comma-separated fields, - for descending |
limit |
list, when Meta.limit |
page size |
offset |
list, when Meta.limit |
page offset |
schema_only |
all | comma-separated fields to keep |
schema_exclude |
all | comma-separated fields to drop |
Response headers:
| Header | Meaning |
|---|---|
x-limit |
applied page size |
x-offset |
applied offset |
x-total |
total rows, unless disabled |
Status codes¶
| Code | When |
|---|---|
200 |
every successful operation, including create |
400 |
invalid JSON, validation error, bad pagination, bad Peewee id |
401 |
authorization callback returned a falsy value |
404 |
unknown resource, invalid Mongo id, missing resource on PUT |
405 |
method not in methods |
429 |
raised by a rate limiter |