Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes: #18150 - Get pagination limit with default 0 #18151

Merged
merged 9 commits into from
Dec 12, 2024

Conversation

bctiemann
Copy link
Contributor

@bctiemann bctiemann commented Dec 3, 2024

Fixes: #18150

If limit is not specified in an API list view, the value raises a KeyError which is silently swallowed without processing MAX_PAGE_SIZE, leading to inconsistent pagination results. This change uses .get() with a default of 0 to ensure no exception is raised and the configured page limit is enforced.

Specifically: in our current behavior, we have these scenarios:

    defaults:
    PAGINATE_COUNT: 50
    MAX_PAGE_SIZE: 1000

    limit=0 => MAX_PAGE_SIZE (this can functionally be "unlimited" if MAX_PAGE_SIZE is set very high)
    limit=n => min(n, MAX_PAGE_SIZE)
    limit omitted => PAGINATE_COUNT

With this behavior, if a script hits a list endpoint with no limit, the next URL will be paginated by PAGINATE_COUNT; but if the script follows that link, the next page will use the limit=n case, and the overall page count and next and previous links are paginated by MAX_PAGE_SIZE rather than PAGINATE_COUNT, which would re-flow all the pages and potentially lead to miscounts or other unexpected script behavior. This can be worked around by a script always specifying a limit, but it seems more sensible for omitting limit to be consistent with limit=0.

After this change:

    limit=0 (or omitted) => min(PAGINATE_COUNT, MAX_PAGE_SIZE)
    limit=n => min(n, MAX_PAGE_SIZE)
    No way to get "unlimited" except by setting both limit and MAX_PAGE_SIZE very high

Open question(s):

  • Should we support a use case for "unlimited" results, and if so, how? Is the above suggestion good?
  • Is limit=0 being a synonym for "unlimited" more intuitive than having limit=0 behave the same as omitting limit?
  • Will limit=0 being interpreted as limit=50 be surprising to the user?

NOTE: The fix has changed; leaving the above to preserve the discussion history.

This fix maintains the existing (separate) behaviors of limit=0 and limit omitted. However, it changes the "limit omitted" case to return min(PAGINATE_COUNT, MAX_PAGE_SIZE), i.e. to ensure MAX_PAGE_SIZE is enforced.

@bctiemann bctiemann self-assigned this Dec 3, 2024
@bctiemann bctiemann marked this pull request as draft December 3, 2024 20:52
@bctiemann bctiemann requested a review from rboucher-me December 4, 2024 17:09
@DanSheps
Copy link
Member

DanSheps commented Dec 5, 2024

Documentation on the parameters: https://demo.netbox.dev/static/docs/rest-api/overview/

The maximum number of objects that can be returned is limited by the MAX_PAGE_SIZE configuration parameter, which is 1000 by default. Setting this to 0 or None will remove the maximum limit. An API consumer can then pass ?limit=0 to retrieve all matching objects with a single request.

@DanSheps
Copy link
Member

DanSheps commented Dec 5, 2024

My take:

limit=0: Limit up to MAX_PAGE_SIZE
limit=n: Limit up to min(limit, MAX_PAGE_SIZE)
limit=None: Limit up to min(PAGINATE_COUNT, MAX_PAGE_SIZE)

@bctiemann bctiemann marked this pull request as ready for review December 5, 2024 21:11
@jeremystretch
Copy link
Member

Can this bug be resolved simply by checking that the configured MAX_PAGE_SIZE is greater than or equal to PAGINATE_COUNT?

@bctiemann
Copy link
Contributor Author

@jeremystretch This update leaves the original logic intact but just ensures that MAX_PAGE_SIZE is at least PAGINATE_COUNT (or 0, meaning ignored). The unit tests now cover both cases:

  • 0 < MAX_PAGE_SIZE < PAGINATE_COUNT < num_records
  • 0 < PAGINATE_COUNT < MAX_PAGE_SIZE < num_records

@jeremystretch
Copy link
Member

@bctiemann I was thinking we could just compare MAX_PAGE_SIZE to PAGINATE_COUNT in settings.py and raise an ImproerlyConfigured exception if it's larger. Do you think that works?

@bctiemann
Copy link
Contributor Author

@jeremystretch I think you're right; after working through the use cases I think this does solve it. Note this also puts both variables explicitly into settings.py so their default values will be easier to find than by relying on their DRF library defaults.

@bctiemann
Copy link
Contributor Author

Also note that I'm leaving the unit test changes in place because with @override_settings they exercise the logic without getting stopped by the settings.py validation.

@bctiemann
Copy link
Contributor Author

Reverting to the previous solution (validating the two variables against each other in pagination.py), with unit tests handling both cases accordingly

@jeremystretch jeremystretch merged commit abfa28d into develop Dec 12, 2024
6 checks passed
@jeremystretch jeremystretch deleted the 18150-get-limit-with-default-0 branch December 12, 2024 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

MAX_PAGE_SIZE not being enforced if "limit" query param is missing
3 participants