Skip to content

Conversation

@piyush-jaiswal
Copy link
Owner

@piyush-jaiswal piyush-jaiswal commented Nov 3, 2025

Summary by CodeRabbit

  • New Features

    • API endpoints now use cursor-based pagination; product, category, and subcategory list responses include cursor metadata for next/previous navigation.
  • Documentation

    • Docs updated to describe cursor semantics, first-page behavior, and cursor parameters for product/category/subcategory listing.
  • Tests

    • Integration tests updated to validate cursor-based pagination flows.
  • Chores

    • Added support for cursor pagination and surfaced cursor fields in API responses.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

Walkthrough

Switches pagination from numeric page parameters to cursor-based pagination across documentation, route handlers, schemas, and tests, adding sqlakeyset-backed cursor handling, a new Cursor schema field, and updating endpoints/tests to accept and return cursor tokens.

Changes

Cohort / File(s) Summary
Documentation
README.md
Replaces page-based pagination descriptions with cursor-based semantics; introduces first-page endpoints and cursor query param usage; documents next/prev cursors in responses.
Route handlers
app/routes/category.py, app/routes/product.py, app/routes/subcategory.py
Replaces page params with cursor; uses sqlakeyset.get_page() instead of page-based paginate; handlers now return { "products": <page>, "cursor": <metadata> }.
Schema updates
app/schemas.py
Adds Cursor field type to encode/decode base64 bookmarks; replaces PaginationArgs.page with PaginationArgs.cursor; ProductsOut now exposes a cursor field.
Tests
tests/test_product.py, tests/test_relationships.py
Tests updated to fetch first page without page param, extract cursor.next from responses, and fetch subsequent pages using ?cursor={token}.
Dependencies
requirements.txt
Adds sqlakeyset==2.0.1746777265 to enable cursor/bookmark pagination.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant DB

    rect rgba(200,230,255,0.35)
    Note over Client,API: Initial fetch (no cursor)
    Client->>API: GET /products
    API->>DB: Query (ordered, limit N)
    DB-->>API: Rows[0..N-1] + bookmark_next
    API-->>Client: { products: [...], cursor: { next: "b64_next", prev: null } }
    end

    rect rgba(220,255,220,0.35)
    Note over Client,API: Subsequent fetch using cursor
    Client->>API: GET /products?cursor=b64_next
    API->>API: Decode cursor -> bookmark
    API->>DB: Query(from bookmark, limit N)
    DB-->>API: Rows[N..2N-1] + bookmark_next/prev
    API-->>Client: { products: [...], cursor: { next: "b64_next2", prev: "b64_prev" } }
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to:
    • app/schemas.py::Cursor encoding/decoding and ValidationError handling.
    • Correct sqlakeyset.get_page() usage and that returned page object and cursor metadata are serialized as expected in app/routes/*.
    • Test changes in tests/test_product.py and tests/test_relationships.py for cursor extraction and subsequent requests.

Possibly related PRs

  • add product tests #11 — Related product pagination tests; earlier pagination test changes that this PR converts to cursor-based flow.

Poem

🐰 I hopped from pages to encoded strings,
Bookmarks tucked in tiny wings,
Next and prev in base64 light,
We hop through rows, from left to right,
A rabbit's cursor — swift and bright.

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: implementing cursor-based pagination throughout the codebase, which is the primary focus across all modified files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/cursor-based-pagination

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02dca06 and 8edc586.

📒 Files selected for processing (1)
  • tests/test_relationships.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_relationships.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@piyush-jaiswal piyush-jaiswal force-pushed the feature/cursor-based-pagination branch from b3da074 to 02dca06 Compare November 3, 2025 16:46
@github-actions
Copy link

github-actions bot commented Nov 3, 2025

@piyush-jaiswal
Copy link
Owner Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25d426f and bbbbef6.

📒 Files selected for processing (7)
  • README.md (3 hunks)
  • app/routes/category.py (2 hunks)
  • app/routes/product.py (2 hunks)
  • app/routes/subcategory.py (2 hunks)
  • app/schemas.py (3 hunks)
  • tests/test_product.py (1 hunks)
  • tests/test_relationships.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
app/routes/subcategory.py (3)
app/routes/category.py (4)
  • get (47-48)
  • get (89-90)
  • get (145-147)
  • get (158-168)
app/routes/product.py (3)
  • get (55-62)
  • get (102-103)
  • get (160-162)
app/models.py (2)
  • Subcategory (93-110)
  • Product (113-131)
tests/test_relationships.py (1)
tests/conftest.py (1)
  • client (26-27)
tests/test_product.py (1)
tests/conftest.py (1)
  • client (26-27)
app/routes/category.py (3)
app/routes/product.py (3)
  • get (55-62)
  • get (102-103)
  • get (160-162)
app/routes/subcategory.py (4)
  • get (48-49)
  • get (95-96)
  • get (160-162)
  • get (173-178)
app/models.py (3)
  • Category (74-90)
  • Product (113-131)
  • Subcategory (93-110)
app/routes/product.py (3)
app/routes/category.py (4)
  • get (47-48)
  • get (89-90)
  • get (145-147)
  • get (158-168)
app/routes/subcategory.py (4)
  • get (48-49)
  • get (95-96)
  • get (160-162)
  • get (173-178)
app/models.py (1)
  • Product (113-131)
🪛 LanguageTool
README.md

[grammar] ~13-~13: Use a hyphen to join words.
Context: ...

Paginates result using cursor based pagination when products are fetch...

(QB_NEW_EN_HYPHEN)

🪛 Ruff (0.14.3)
app/schemas.py

11-11: Unused method argument: attr

(ARG002)


11-11: Unused method argument: obj

(ARG002)


11-11: Unused method argument: kwargs

(ARG002)


24-24: Unused method argument: attr

(ARG002)


24-24: Unused method argument: data

(ARG002)


24-24: Unused method argument: kwargs

(ARG002)


33-33: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (5)
app/routes/subcategory.py (1)

175-178: Nice use of deterministic ordering
Locking the subcategory products query to Product.id.asc() before calling get_page keeps the keyset stable, which is exactly what sqlakeyset expects for consistent paging (dokk.org).

README.md (1)

67-70: Doc update aligns with the new cursor contract
Calling out focus-page behaviour and the cursor query param in the README makes it much easier for API consumers to follow the same hop pattern the tests exercise.

app/schemas.py (1)

10-34: Cursor field handles both directions cleanly
Serializing the sqlakeyset bookmarks through url-safe Base64 keeps the payload URL-friendly, and surfacing a marshmallow ValidationError on decode failures guarantees bad cursors short-circuit with a 422 response (sqlakeyset.readthedocs.io).

app/routes/category.py (1)

163-168: Category products share the same stable ordering
Mirroring the subcategory implementation with an existence check plus Product.id.asc() ensures this endpoint produces reproducible window slices across requests, matching sqlakeyset’s guidance on unique ordering for keysets (dokk.org).

tests/test_product.py (1)

225-226: Test now walks the “next” cursor
Driving the second request with data1["cursor"]["next"] exercises the entire response contract, so we’ll catch regressions if either side stops emitting/accepting the expected bookmark.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
tests/test_relationships.py (1)

256-256: Remove the stale ?page=1 query parameter.

Line 256 still uses the legacy ?page=1 parameter. Since the migration to cursor-based pagination is complete, this parameter is no longer supported and should be removed. The first page should be fetched without any paging query string, consistent with the category test above (Line 221).

Apply this diff:

-        page1 = self.client.get(f"/subcategories/{subcategory['id']}/products?page=1").get_json()
+        page1 = self.client.get(f"/subcategories/{subcategory['id']}/products").get_json()
🧹 Nitpick comments (1)
README.md (1)

13-13: Fix hyphenation for compound adjective.

Use a hyphen in "cursor-based pagination" as it modifies "pagination".

Apply this diff:

-Paginates result using cursor based pagination when products are fetched by categories, subcategories or themselves.
+Paginates result using cursor-based pagination when products are fetched by categories, subcategories or themselves.

As per coding guidelines

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25d426f and 02dca06.

📒 Files selected for processing (8)
  • README.md (3 hunks)
  • app/routes/category.py (2 hunks)
  • app/routes/product.py (2 hunks)
  • app/routes/subcategory.py (2 hunks)
  • app/schemas.py (3 hunks)
  • requirements.txt (1 hunks)
  • tests/test_product.py (1 hunks)
  • tests/test_relationships.py (2 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md

[grammar] ~13-~13: Use a hyphen to join words.
Context: ...

Paginates result using cursor based pagination when products are fetch...

(QB_NEW_EN_HYPHEN)

🪛 Ruff (0.14.3)
app/schemas.py

11-11: Unused method argument: attr

(ARG002)


11-11: Unused method argument: obj

(ARG002)


11-11: Unused method argument: kwargs

(ARG002)


24-24: Unused method argument: attr

(ARG002)


24-24: Unused method argument: data

(ARG002)


24-24: Unused method argument: kwargs

(ARG002)


33-33: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (14)
tests/test_product.py (1)

225-226: LGTM!

The cursor-based pagination is correctly implemented: the first page is fetched without a cursor parameter, and the second page uses the next cursor from the first page's response.

app/routes/subcategory.py (2)

5-5: LGTM!

The sqlakeyset import enables cursor-based pagination across the codebase.


173-178: LGTM!

The cursor-based pagination implementation is correct:

  • Orders by Product.id.asc() for deterministic pagination
  • Uses get_page with the cursor parameter
  • Returns both the page object and cursor metadata for client navigation
tests/test_relationships.py (2)

221-223: LGTM!

The category products pagination correctly fetches the first page without any paging parameter and then uses the cursor for subsequent pages.


257-258: LGTM!

The cursor extraction and subsequent page fetch are correctly implemented.

app/routes/product.py (2)

5-5: LGTM!

The sqlakeyset import enables cursor-based pagination for product listings.


55-62: No changes needed — the code correctly serializes Query objects.

The test test_get_product_by_name exercises exactly this code path and passes. Marshmallow-sqlalchemy's SQLAlchemyAutoSchema automatically handles SQLAlchemy Query objects through iteration when used with a List field containing a Nested schema. No explicit .all() call is required; line 58's direct return of the Query object is correct and consistent with the framework's design.

app/routes/category.py (2)

5-5: LGTM!

The sqlakeyset import enables cursor-based pagination for category products.


158-168: LGTM!

The cursor-based pagination is correctly implemented:

  • Validates category existence before pagination
  • Orders by Product.id.asc() for deterministic results
  • Uses get_page with the cursor parameter
  • Returns both the page object and cursor metadata
app/schemas.py (4)

1-5: LGTM!

The imports are correctly added to support cursor-based pagination with base64 encoding and sqlakeyset bookmarks.


10-34: LGTM!

The Cursor field implementation is well-designed:

  • Properly serializes cursors as base64-encoded next/prev tokens
  • Deserializes and validates cursor strings with appropriate error handling
  • Integrates cleanly with sqlakeyset bookmarks

Note: The static analysis warnings about unused parameters (attr, obj, kwargs) are false positives—these parameters are required by the marshmallow Field interface.


102-102: LGTM!

The cursor field is correctly added to ProductsOut with required=False to accommodate the name-based product lookup flow that doesn't use pagination.


133-133: LGTM!

The PaginationArgs schema correctly replaces the page-based parameter with the cursor-based parameter, completing the migration to cursor-based pagination.

requirements.txt (1)

12-12: Verification confirms version is valid with no security advisories.

The version 2.0.1746777265 is a legitimate, published release on PyPI. The large build number follows a timestamp-based versioning scheme used consistently by this package. The security advisory check returned no vulnerabilities for sqlakeyset.

No action needed on this dependency.

@piyush-jaiswal piyush-jaiswal merged commit efce7fc into master Nov 3, 2025
3 checks passed
@piyush-jaiswal piyush-jaiswal deleted the feature/cursor-based-pagination branch November 3, 2025 18:01
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.

2 participants