Pagination support!!!
This commit is contained in:
parent
3f677fceee
commit
a64c64e828
5 changed files with 298 additions and 12 deletions
21
crudkit/api/_cursor.py
Normal file
21
crudkit/api/_cursor.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import base64, json
|
||||
from typing import Any
|
||||
|
||||
def encode_cursor(values: list[Any] | None, desc_flags: list[bool], backward: bool) -> str | None:
|
||||
if not values:
|
||||
return None
|
||||
payload = {"v": values, "d": desc_flags, "b": backward}
|
||||
return base64.urlsafe_b64encode(json.dumps(payload).encode()).decode()
|
||||
|
||||
def decode_cursor(token: str | None) -> tuple[list[Any] | None, bool] | tuple[None, bool]:
|
||||
if not token:
|
||||
return None, False
|
||||
try:
|
||||
obj = json.loads(base64.urlsafe_b64decode(token.encode()).decode())
|
||||
vals = obj.get("v")
|
||||
backward = bool(obj.get("b", False))
|
||||
if isinstance(vals, list):
|
||||
return vals, backward
|
||||
except Exception:
|
||||
pass
|
||||
return None, False
|
||||
|
|
@ -1,15 +1,59 @@
|
|||
from flask import Blueprint, jsonify, request
|
||||
|
||||
from crudkit.api._cursor import encode_cursor, decode_cursor
|
||||
from crudkit.core.service import _is_truthy
|
||||
|
||||
def generate_crud_blueprint(model, service):
|
||||
bp = Blueprint(model.__name__.lower(), __name__)
|
||||
|
||||
@bp.get('/')
|
||||
def list_items():
|
||||
items = service.list(request.args)
|
||||
args = request.args.to_dict(flat=True)
|
||||
|
||||
# legacy detection
|
||||
legacy_offset = "offset" in args or "page" in args
|
||||
|
||||
# sane limit default
|
||||
try:
|
||||
return jsonify([item.as_dict() for item in items])
|
||||
except Exception as e:
|
||||
return jsonify({"status": "error", "error": str(e)})
|
||||
limit = int(args.get("limit", 50))
|
||||
except Exception:
|
||||
limit = 50
|
||||
args["limit"] = limit
|
||||
|
||||
if legacy_offset:
|
||||
# Old behavior: honor limit/offset, same CRUDSpec goodies
|
||||
items = service.list(args)
|
||||
return jsonify([obj.as_dict() for obj in items])
|
||||
|
||||
# New behavior: keyset seek with cursors
|
||||
key, backward = decode_cursor(args.get("cursor"))
|
||||
|
||||
window = service.seek_window(
|
||||
args,
|
||||
key=key,
|
||||
backward=backward,
|
||||
include_total=_is_truthy(args.get("include_total", "1")),
|
||||
)
|
||||
|
||||
desc_flags = list(window.order.desc)
|
||||
body = {
|
||||
"items": [obj.as_dict() for obj in window.items],
|
||||
"limit": window.limit,
|
||||
"next_cursor": encode_cursor(window.last_key, desc_flags, backward=False),
|
||||
"prev_cursor": encode_cursor(window.first_key, desc_flags, backward=True),
|
||||
"total": window.total,
|
||||
}
|
||||
|
||||
resp = jsonify(body)
|
||||
# Optional Link header
|
||||
links = []
|
||||
if body["next_cursor"]:
|
||||
links.append(f'<{request.base_url}?cursor={body["next_cursor"]}&limit={window.limit}>; rel="next"')
|
||||
if body["prev_cursor"]:
|
||||
links.append(f'<{request.base_url}?cursor={body["prev_cursor"]}&limit={window.limit}>; rel="prev"')
|
||||
if links:
|
||||
resp.headers["Link"] = ", ".join(links)
|
||||
return resp
|
||||
|
||||
@bp.get('/<int:id>')
|
||||
def get_item(id):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue