Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
WIP: Implement pagination headers
Browse files Browse the repository at this point in the history
Fixes #19

TODO:
- Implement & test header Link for bare Flask app
  • Loading branch information
rambobinator authored and Vincent Trubesset committed Nov 29, 2018
1 parent 8328d98 commit 77f6e0d
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 2 deletions.
4 changes: 4 additions & 0 deletions flask_stupe/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ def make_response(self, rv):

rv = jsonify(rv)
rv.status_code = code

if request.response_headers:
rv.headers.extend(request.response_headers)

return rv

def __init__(self, *args, **kwargs):
Expand Down
42 changes: 40 additions & 2 deletions flask_stupe/pagination.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import functools
from collections import OrderedDict

from flask import request

Expand All @@ -8,10 +9,47 @@


if pymongo:

def _get_pagination_links(skip, limit, total_count=None):
template = "{}?limit={}&skip={{skip}}".format(request.base_url, limit)
links = OrderedDict([
("self", template.format(skip=skip)),
("first", template.format(skip=0))
])
prev_skip = skip - limit
if prev_skip >= 0:
links.update(prev=template.format(skip=prev_skip))
next_skip = skip + limit
if next_skip < total_count:
links.update(next=template.format(skip=next_skip))
if total_count:
links.update(last=template.format(skip=total_count - limit))
return links

def _paginate(cursor, skip=None, limit=None, sort=None, count=True):
total_count = None
if count:
total_count = cursor.count()
links = None
if limit:
links = _get_pagination_links(skip or 0, limit, total_count)

headers = getattr(request, "response_headers", None)
if isinstance(headers, dict):
if total_count:
headers["X-Total-Count"] = total_count
if links:
header_links = []
for name, link in links.items():
header_links.append('<{}>; rel="{}"'.format(link, name))
headers["Link"] = ", ".join(header_links)

metadata = getattr(request, "metadata", None)
if count and isinstance(metadata, dict):
metadata.update(count=cursor.count())
if isinstance(metadata, dict):
if total_count:
metadata.update(count=total_count)
if links:
metadata.update(links=links)

skip = request.args.get("skip", skip, type=int)
if skip is not None:
Expand Down
1 change: 1 addition & 0 deletions flask_stupe/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def __init__(self, *args, **kwargs):

#: Store additionnal data about the request.
self.metadata = {}
self.response_headers = {}


__all__ = ["Request"]
15 changes: 15 additions & 0 deletions tests/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,21 @@ def foo():
assert response_dict["count"] == 3


def test_stupeflask_response_with_paginate_headers(json_app, client):
encoder_rules.append((Cursor, lambda c: c.data))

@json_app.route("/")
@paginate(limit=1, skip=0)
def foo():
return Cursor([1, 2, 3])

response = client.get("/")
assert response.status_code == 200

assert "X-Total-Count" in response.headers
assert "Link" in response.headers


def test_stupeflask_converters(json_app, client):
@json_app.route("/<ObjectId:foo_id>")
def foo_id(foo_id):
Expand Down
44 changes: 44 additions & 0 deletions tests/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,47 @@ def test_paginate_function(app):
def foo_instance():
return Cursor([1, 2, 3])
assert paginate(foo_instance, skip=2)().data == [3]


@pytest.mark.parametrize("app", [
Stupeflask(__name__),
Flask(__name__)
])
def test_paginate_header_total_count(app):
with app.test_request_context():
paginate(Cursor([1, 2, 3]))
if isinstance(app, Stupeflask):
assert request.response_headers["X-Total-Count"] == 3
assert "Link" not in request.response_headers


@pytest.mark.parametrize("app", [
Stupeflask(__name__),
Flask(__name__)
])
def test_paginate_header_link(app):
with app.test_request_context():
paginate(Cursor([1, 2, 3]), limit=1, skip=1)
if isinstance(app, Stupeflask):
links = request.response_headers["Link"].split(",")
assert links[0].split("?")[1] == 'limit=1&skip=1>; rel="self"'
assert links[1].split("?")[1] == 'limit=1&skip=0>; rel="first"'
assert links[2].split("?")[1] == 'limit=1&skip=0>; rel="prev"'
assert links[3].split("?")[1] == 'limit=1&skip=2>; rel="next"'
assert links[4].split("?")[1] == 'limit=1&skip=2>; rel="last"'


@pytest.mark.parametrize("app", [
Stupeflask(__name__),
Flask(__name__)
])
def test_paginate_metadata_links(app):
with app.test_request_context():
paginate(Cursor([1, 2, 3]), limit=1, skip=1)
if isinstance(app, Stupeflask):
links = request.metadata["links"]
assert links["self"].split("?")[1] == "limit=1&skip=1"
assert links["first"].split("?")[1] == "limit=1&skip=0"
assert links["prev"].split("?")[1] == "limit=1&skip=0"
assert links["next"].split("?")[1] == "limit=1&skip=2"
assert links["last"].split("?")[1] == "limit=1&skip=2"

0 comments on commit 77f6e0d

Please sign in to comment.