From 927f7a2309a98138586acf0c10840bdbef0c7c2e Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 19 Feb 2024 17:30:29 -0500 Subject: [PATCH] site: api: Add a next_page field as the canonical next page Supply a next_page field which fills in the parameters automatically. This will let us move away from limit/offset paging if I ever get around to doing it. Undocument offset as the canonical way is now next_page. The implementation itself is almost directly lifted from the existing navigation macro. Signed-off-by: Sean Anderson --- test/site_test.py | 35 +++++++++++++++++++--------------- trends/site/api.py | 13 ++++++++++++- trends/site/templates/api.html | 20 +++++++++---------- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/test/site_test.py b/test/site_test.py index 444bada..e0047d4 100644 --- a/test/site_test.py +++ b/test/site_test.py @@ -232,9 +232,10 @@ def test_api_logs(client): def get(**params): resp = client.get("api/v1/logs", query_string=params) assert resp.status_code == 200 - return resp.json['logs'] + resp = resp.json + return resp['logs'], resp['next_page'] - logs = get() + logs, next_page = get() valid_keys = { 'demoid', 'duplicate_of', @@ -280,22 +281,26 @@ def get(**params): updated_pivot = max(updated_pivot, log['updated']) assert logs == sorted(logs, key=lambda log: log['logid'], reverse=True) + assert next_page is None def paged(limit=10): off = 0 while True: - logs = get(limit=limit, offset=off) + logs, next_page = get(limit=limit, offset=off) assert len(logs) <= limit yield from logs if len(logs) < limit: + assert next_page is None return + off += limit + assert next_page == f"/api/v1/logs?limit={limit}&offset={off}" assert logs == list(paged()) - assert get(offset=len(logs)) == [] + assert get(offset=len(logs)) == ([], None) - for log in get(view='players'): + for log in get(view='players')[0]: assert set(log.keys()) == valid_keys | { 'red', 'blue' } for team in ('red', 'blue'): team = log[team] @@ -315,32 +320,32 @@ def paged(limit=10): for by in ('logid', 'date', 'duration'): key = lambda log: log['time' if by == 'date' else by] for reverse in (True, False): - logs = get(sort=by, sort_dir='desc' if reverse else 'asc') + logs, _ = get(sort=by, sort_dir='desc' if reverse else 'asc') assert logs == sorted(logs, key=key, reverse=reverse) - for log in get(league="etf2l"): + for log in get(league="etf2l")[0]: assert log['league'] == "etf2l" - for log in get(format="sixes"): + for log in get(format="sixes")[0]: assert log['format'] == "sixes" - for log in get(title="serveme"): + for log in get(title="serveme")[0]: assert "serveme" in log['title'] - for log in get(map="cp"): + for log in get(map="cp")[0]: assert "cp" in log['map'] - for log in get(date_from='2019-11-06', timezone='America/New_York'): + for log in get(date_from='2019-11-06', timezone='America/New_York')[0]: assert log['time'] >= 1573016400 - for log in get(date_to='2019-11-06', timezone='America/New_York'): + for log in get(date_to='2019-11-06', timezone='America/New_York')[0]: assert log['time'] <= 1573016400 - for log in get(time_from=1573016400): + for log in get(time_from=1573016400)[0]: assert log['time'] >= 1573016400 - for log in get(time_to=1573016400): + for log in get(time_to=1573016400)[0]: assert log['time'] <= 1573016400 - for log in get(updated_since=updated_pivot): + for log in get(updated_since=updated_pivot)[0]: assert log['updated'] > updated_pivot diff --git a/trends/site/api.py b/trends/site/api.py index bf1d023..ad67420 100644 --- a/trends/site/api.py +++ b/trends/site/api.py @@ -24,12 +24,23 @@ def do_cache(resp): resp.cache_control.max_age = 300 return resp +def next_page(rows): + args = flask.request.args.copy() + args.update(flask.request.view_args) + + limit, offset = flask.g.page + args['limit'] = limit + if len(rows) == limit: + args['offset'] = offset + limit + return flask.url_for(flask.request.endpoint, **args) + @api.route('/logs') def logs(): if resp := logs_last_modified(): return resp view = flask.request.args.get('view', 'basic', str) - return flask.jsonify(logs=[dict(log) for log in get_logs(view)]) + logs = [dict(log) for log in get_logs(view)] + return flask.jsonify(logs=logs, next_page=next_page(logs)) @api.route('/maps') def maps(): diff --git a/trends/site/templates/api.html b/trends/site/templates/api.html index 9a143ba..4912a62 100644 --- a/trends/site/templates/api.html +++ b/trends/site/templates/api.html @@ -171,15 +171,6 @@

Parameters

control-point logs.

- {{ paramdef("offset") }} -

- Skip results equal to the value of this parameter. For example, supplying - {{ pre("limit=10") }} and {{ pre("offset=20") }} to the {{ apiref('logs') }} endpoint would - return 10 matches starting from the - 21st match. This may be used to request successive pages from an endpoint. - The default offset is 0. -

- {{ paramdef("sort") }}

This parameter specifies the key to use when sorting the output. The valid keys vary between @@ -284,6 +275,13 @@

Members

assigned by their respective {{ fieldref('league') }}s.

+ {{ fielddef('next_page') }} +

+ A relative URL to the next page of results. If the number of returned results is equal + to the {{ paramref('limit') }}, then this field may be non-{{ pre("null") }} even if the + next page is empty. +

+ {{ fielddef('steamid64') }}

A unique number identifying a player. This is assigned by @@ -323,7 +321,6 @@

Parameters

  • {{ paramref("league") }}
  • {{ paramref("limit") }}
  • {{ paramref("map") }}
  • -
  • {{ paramref("offset") }}
  • {{ paramref("sort") }}, with valid values being:
    @@ -431,7 +428,8 @@

    Response

    - +
    {{ pre('next_page') }} : option(string)
    +
    The {{ fieldref('next_page') }} of logs, if any
    {% endblock %}