-
Notifications
You must be signed in to change notification settings - Fork 46
feat(pagination): add cursor-based pagination + client APIs #112
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
Open
histrio
wants to merge
9
commits into
master
Choose a base branch
from
feat/pagination-add-cursor-based-pagination-apis
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
15c8f54
feat(pagination): add cursor-based pagination + client APIs
histrio 77066dd
docs(quickstart): document view and Mango pagination
histrio 82e2787
fix(utils): handle UnicodeDecodeError when parsing JSON
histrio 27fa4f8
test(pagination): add unit and integration coverage
histrio b05fa86
fix(mypy): adjust Database.find return and add __all__ in pagination
histrio a1cedde
docs(client): use correct Sphinx versionadded directive
histrio e92077f
refactor(pagination): remove unused mango helper and clarify cursor c…
histrio 7aba7a6
test(utils): cover as_json invalid UTF-8 with replacement chars
histrio da5587e
docs(pagination): warn about unreliable pagination with reduced views
histrio File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,152 @@ | ||||||
| # -*- coding: utf-8 -*- | ||||||
|
|
||||||
| """ | ||||||
| Pagination utilities for CouchDB views and Mango queries. | ||||||
|
|
||||||
| This module provides convenient pagination functionality for both CouchDB views | ||||||
| and Mango queries, handling the complexity of cursor-based pagination internally. | ||||||
| """ | ||||||
|
|
||||||
| from typing import Any, Dict, List, Iterator, Optional, Callable, Union, Tuple | ||||||
| import json | ||||||
| import copy | ||||||
|
|
||||||
| from . import utils | ||||||
| from .types import Row, Document, Json, ViewRows, MangoDocs, PageSize | ||||||
|
|
||||||
| __all__ = ['view_pages', 'mango_pages', 'ViewRows', 'MangoDocs', 'PageSize'] | ||||||
|
|
||||||
|
|
||||||
| def view_pages( | ||||||
| fetch: Callable[[Dict[str, Any]], Tuple[Any, Optional[Dict[str, Any]]]], | ||||||
| view: str, | ||||||
| page_size: PageSize, | ||||||
| params: Optional[Dict[str, Any]] = None | ||||||
| ) -> Iterator[ViewRows]: | ||||||
| """ | ||||||
| Paginate through CouchDB view results using startkey/startkey_docid cursor. | ||||||
|
|
||||||
| This function handles the complexity of CouchDB view pagination by automatically | ||||||
| managing startkey and startkey_docid parameters for stable pagination. | ||||||
|
|
||||||
| .. warning:: | ||||||
| Pagination with grouped and reduced views (group=true, reduce=true) is | ||||||
| inefficient and unreliable. CouchDB must process all preceding groups | ||||||
| for skip operations, and total_rows/offset values are inconsistent with | ||||||
| reduced output. Consider fetching all results at once for reduced views. | ||||||
|
|
||||||
| :param fetch: Function that makes the actual HTTP request and returns (response, result) | ||||||
| :param view: View name (e.g., "design/view") | ||||||
| :param page_size: Number of rows per page | ||||||
| :param params: Additional query parameters | ||||||
| :returns: Iterator yielding lists of rows for each page | ||||||
| """ | ||||||
| if params is None: | ||||||
| params = {} | ||||||
|
|
||||||
| # Create a copy to avoid modifying the original | ||||||
| query_params = copy.deepcopy(params) | ||||||
| query_params['limit'] = page_size + 1 # Request one extra to detect if there are more pages | ||||||
|
|
||||||
| # Track pagination state | ||||||
| startkey = None | ||||||
| startkey_docid = None | ||||||
| skip = 0 | ||||||
|
|
||||||
| while True: | ||||||
| # Build current page parameters | ||||||
| current_params = copy.deepcopy(query_params) | ||||||
|
|
||||||
| if startkey is not None: | ||||||
| current_params['startkey'] = startkey | ||||||
| current_params['startkey_docid'] = startkey_docid | ||||||
| current_params['skip'] = skip | ||||||
|
|
||||||
| # Encode view parameters properly | ||||||
| current_params = _encode_view_params(current_params) | ||||||
|
|
||||||
| # Make the request | ||||||
| response, result = fetch(current_params) | ||||||
|
|
||||||
| if result is None or 'rows' not in result: | ||||||
| break | ||||||
|
|
||||||
| rows = result['rows'] | ||||||
|
|
||||||
| # If we got fewer rows than requested, this is the last page | ||||||
| if len(rows) <= page_size: | ||||||
| if rows: # Only yield if there are rows | ||||||
| yield rows | ||||||
| break | ||||||
|
|
||||||
| # We got more rows than page_size, so there are more pages | ||||||
| # Yield current page (excluding the extra row) | ||||||
| current_page = rows[:page_size] | ||||||
| yield current_page | ||||||
|
|
||||||
| # Set up for next page using the last row as cursor | ||||||
| last_row = rows[page_size - 1] | ||||||
| startkey = last_row['key'] | ||||||
| startkey_docid = last_row['id'] | ||||||
| skip = 1 # Skip the row used as the cursor to avoid returning it again (prevents duplicate results in cursor-based pagination) | ||||||
|
||||||
| skip = 1 # Skip the row used as the cursor to avoid returning it again (prevents duplicate results in cursor-based pagination) | |
| skip = 1 # Skip cursor row to avoid duplicates |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code uses magic string keys 'key' and 'id' without validation. Consider adding defensive checks or constants for these keys to improve maintainability.