Skip to content

Conversation

@piyush-jaiswal
Copy link
Owner

@piyush-jaiswal piyush-jaiswal commented Oct 1, 2025

Summary by CodeRabbit

  • New Features

    • Introduced /subcategories API with full CRUD, related categories/products endpoints, and paginated product listing. Create/update/delete require authentication.
  • Refactor

    • Standardized routes from singular to plural (/subcategory → /subcategories).
    • Aligned responses: updates return 200 OK; deletes return 204 No Content.
    • Improved validation and duplicate handling with clearer error responses (e.g., 409 conflicts).
  • Documentation

    • Updated README examples and payloads to reflect new pluralized endpoints and response structures.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 1, 2025

Walkthrough

Pluralizes Subcategory endpoints across docs and tests, registers a new “/subcategories” blueprint, migrates Subcategory CRUD and relationship routes into app/migrated_routes/subcategory.py with JWT-protected mutations and pagination, adds a Subcategory input schema, and removes prior Subcategory routes from app/routes.py.

Changes

Cohort / File(s) Summary
Documentation
README.md
Updated endpoint paths from /subcategory/... to /subcategories/...; revised examples and status expectations.
Blueprint registration
app/__init__.py
Imports and registers Subcategory blueprint at url_prefix "/subcategories".
Route migration (removals)
app/routes.py
Removes legacy Subcategory CRUD and relationship endpoints; retains other existing routes.
Subcategory blueprint implementation
app/migrated_routes/subcategory.py
Adds new Blueprint with MethodViews: collection (GET/POST), by-id (GET/PUT/DELETE), categories (GET), products (GET with pagination); JWT for mutating ops; validation; IntegrityError/unique-constraint handling.
Schemas
app/schemas.py
Introduces SubcategoryIn schema with name normalization/validation and category/product ID lists.
Tests
tests/conftest.py, tests/test_subcategory.py, tests/test_relationships.py
Switch tests to plural routes; add auth headers usage; update expected status codes and error handling (IntegrityError); expand relationship and pagination coverage.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant F as Flask App
  participant SB as Subcategory Blueprint
  participant DB as Database

  rect rgb(240,248,255)
  note right of C: Read operations
  C->>F: GET /subcategories
  F->>SB: Route dispatch
  SB->>DB: Query all subcategories
  DB-->>SB: Rows
  SB-->>F: 200 JSON {subcategories:[...]}
  F-->>C: 200
  end

  rect rgb(245,255,240)
  note right of C: Auth-required mutations
  C->>F: POST /subcategories (JWT, body)
  F->>SB: Validate JWT, parse body
  SB->>DB: Validate refs, insert subcategory
  DB-->>SB: OK or IntegrityError
  alt Unique violation
    SB-->>F: 409 Conflict
  else Success
    SB-->>F: 201 Created {id,...}
  end
  F-->>C: 201/409
  end

  rect rgb(255,250,240)
  note right of C: Updates and deletion
  C->>F: PUT /subcategories/{id} (JWT)
  F->>SB: Dispatch + validate
  SB->>DB: Update + assoc links
  DB-->>SB: OK or IntegrityError
  SB-->>F: 200 or 409
  F-->>C: 200/409

  C->>F: DELETE /subcategories/{id} (JWT)
  F->>SB: Dispatch
  SB->>DB: Delete row
  DB-->>SB: OK
  SB-->>F: 204 No Content
  F-->>C: 204
  end

  rect rgb(248,248,255)
  note right of C: Related collections
  C->>F: GET /subcategories/{id}/products?page=n
  F->>SB: Dispatch
  SB->>DB: Query + paginate
  DB-->>SB: Page results
  SB-->>F: 200 {items, page, pages}
  F-->>C: 200
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Add subcategory tests #10: Touches subcategory helpers and tests similarly, indicating alignment on test infrastructure for subcategory endpoints.
  • Add relationship tests #13: Modifies relationship tests/fixtures for subcategories; overlaps with this PR’s route changes and relationship coverage.
  • Feature/db error handling category #18: Implements dynamic unique-constraint handling in a migrated_routes resource (category), mirroring the approach used here for subcategories.

Poem

I hop through routes, plural and bright,
New blueprints bloom in restful light.
JWT carrots guard the gate,
409s decide the fate.
Paginated fields I freely browse—
Subcategories now fill the house.
Thump-thump! Deploying with rabbit vows. 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title “Feature/flask smorest subcategory” reads more like a branch name than a concise summary and does not clearly convey that the pull request pluralizes subcategory endpoints and introduces a Flask-Smorest blueprint for subcategory CRUD functionality. It lacks a clear, specific description of the main change and uses generic terminology and a slash that may confuse readers. Rename the title to a concise statement of the primary change, for example: “Implement subcategory CRUD with Flask-Smorest and pluralize subcategory routes to /subcategories”.
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/flask-smorest-subcategory

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.

@github-actions
Copy link

github-actions bot commented Oct 1, 2025

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
app/routes.py (6)

190-191: Improve error handling.

The bare except clause catches all exceptions, including system exits. Additionally, the generic error message doesn't aid debugging or provide actionable feedback to clients.

Apply this diff to improve error handling:

-    except:
-        return "Error occured", 500
+    except IntegrityError:
+        return jsonify({'error': 'Product name already exists'}), 409
+    except Exception as e:
+        app.logger.error(f"Error creating product: {str(e)}")
+        return jsonify({'error': 'Failed to create product'}), 500

279-281: Risk of duplicate subcategory associations.

Using extend adds subcategories to the existing collection without clearing it first. Repeated updates with the same subcategory IDs will create duplicate associations.

Apply this diff to replace rather than extend:

         if sc_ids is not None:
+            product.subcategories = []
             subcategories = Subcategory.query.filter(Subcategory.id.in_(sc_ids))
             product.subcategories.extend(subcategories)

283-283: Use 200 status code for successful updates.

Status code 201 is for resource creation. Updates should return 200.

Apply this diff:

-        return jsonify(product.to_json()), 201
+        return jsonify(product.to_json()), 200

284-285: Improve error handling.

The bare except clause catches all exceptions, including system exits. The generic error message doesn't aid debugging.

Apply this diff to improve error handling:

-    except:
-        return "Error occured", 500
+    except IntegrityError:
+        return jsonify({'error': 'Product name already exists'}), 409
+    except Exception as e:
+        app.logger.error(f"Error updating product: {str(e)}")
+        return jsonify({'error': 'Failed to update product'}), 500

320-321: Improve error handling.

The bare except clause catches all exceptions, including system exits. The generic error message doesn't aid debugging.

Apply this diff:

-    except:
-        return "Error occured", 500
+    except Exception as e:
+        app.logger.error(f"Error deleting product: {str(e)}")
+        return jsonify({'error': 'Failed to delete product'}), 500

409-410: Improve error handling.

The bare except clause catches all exceptions, including system exits. The generic error message doesn't aid debugging.

Apply this diff:

-    except:
-        return "Error occured", 500
+    except Exception as e:
+        app.logger.error(f"Error retrieving subcategories: {str(e)}")
+        return jsonify({'error': 'Failed to retrieve subcategories'}), 500
🧹 Nitpick comments (3)
app/migrated_routes/subcategory.py (2)

45-58: Optional: return a stable ordering for list endpoints.

For deterministic tests and clients, order results (e.g., by id).

-        return {"subcategories": Subcategory.query.all()}
+        return {"subcategories": Subcategory.query.order_by(Subcategory.id.asc()).all()}

296-298: Prefer materializing related collections and ordering explicitly.

Returning a dynamic query relies on Marshmallow iterating the query; return a list with stable order.

-        return {"categories": subcategory.categories}
+        return {
+            "categories": subcategory.categories.order_by(Category.id.asc()).all()
+        }
tests/test_subcategory.py (1)

42-51: Replace DB-specific exception in test_create_subcategory_duplicate_name: in tests/test_subcategory.py:42-51, avoid asserting sqlite3.IntegrityError (ties tests to SQLite). Instead, handle uniqueness violations at the HTTP layer (assert a 409 Conflict JSON response) or at minimum catch SQLAlchemy’s IntegrityError without referencing sqlite3.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95f1931 and 56a82a5.

📒 Files selected for processing (8)
  • README.md (3 hunks)
  • app/__init__.py (1 hunks)
  • app/migrated_routes/subcategory.py (1 hunks)
  • app/routes.py (1 hunks)
  • app/schemas.py (1 hunks)
  • tests/conftest.py (1 hunks)
  • tests/test_relationships.py (3 hunks)
  • tests/test_subcategory.py (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
app/schemas.py (1)
app/models.py (1)
  • Subcategory (96-113)
app/routes.py (1)
app/models.py (2)
  • Subcategory (96-113)
  • Product (116-134)
app/migrated_routes/subcategory.py (2)
app/models.py (3)
  • Category (77-93)
  • Product (116-134)
  • Subcategory (96-113)
app/schemas.py (6)
  • CategoriesOut (12-13)
  • PaginationArgs (75-76)
  • ProductsOut (71-72)
  • SubcategoriesOut (41-42)
  • SubcategoryIn (45-63)
  • SubcategoryOut (36-38)
tests/test_relationships.py (1)
tests/conftest.py (5)
  • create_category (54-63)
  • create_product (82-93)
  • create_subcategory (67-78)
  • client (14-21)
  • create_authenticated_headers (43-50)
tests/test_subcategory.py (1)
tests/conftest.py (3)
  • create_subcategory (67-78)
  • create_authenticated_headers (43-50)
  • client (14-21)
🪛 Ruff (0.13.1)
app/schemas.py

54-54: Unused method argument: kwargs

(ARG002)


61-61: Unused method argument: data_key

(ARG002)


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

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: deploy-preview
🔇 Additional comments (9)
app/__init__.py (1)

16-19: LGTM! Blueprint registration follows existing patterns.

The subcategory blueprint import and registration correctly mirrors the category blueprint pattern, with the appropriate pluralized URL prefix "/subcategories".

tests/conftest.py (1)

67-78: LGTM! Test helper correctly updated to pluralized endpoint.

The create_subcategory fixture now targets the new /subcategories endpoint, consistent with the API migration and matching the pattern used in the create_category fixture.

app/schemas.py (1)

45-64: LGTM! Schema implementation is consistent with existing patterns.

The SubcategoryIn schema correctly mirrors the CategoryIn structure with appropriate field definitions, validation, and preprocessing. The inclusion of both categories and products fields aligns with the bidirectional relationships in the Subcategory model.

Note: The static analysis hints about unused parameters (kwargs, data_key) are false positives—these are part of Marshmallow's callback signatures and should be ignored.

README.md (2)

67-67: LGTM! Product fetch endpoint correctly updated.

The subcategory products endpoint now uses the pluralized path /subcategories/<subcategory_id: int>/products, consistent with the API migration.


126-147: LGTM! Subcategory documentation comprehensively updated.

All Subcategory endpoints have been correctly updated to use the pluralized /subcategories prefix. The documentation structure mirrors the Category section and properly indicates which endpoints require authentication.

tests/test_subcategory.py (1)

58-58: LGTM: pluralized endpoints and status codes are consistent.

GET/PUT/DELETE paths under /subcategories and 200/204 expectations align with the new blueprint.

Also applies to: 65-69, 85-85, 88-88, 100-100, 102-104, 118-118, 139-139, 163-164

tests/test_relationships.py (1)

215-215: LGTM: relationship routes moved to /subcategories/... and pagination assertions updated.

Paths and expected codes match the new API surface.

Also applies to: 218-225, 229-230, 232-243, 267-269

app/migrated_routes/subcategory.py (1)

32-44: Constraint‐name matching is reliable under existing naming conventions; no changes required.

app/routes.py (1)

7-7: LGTM! Import changes align with the migration.

The removal of Category and retention of Subcategory is correct. Subcategory is still needed here for Product-Subcategory relationship management in the remaining Product endpoints.

from flask.views import MethodView
from flask_jwt_extended import jwt_required
from flask_smorest import Blueprint, abort
from psycopg2.errors import UniqueViolation
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 1, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid hard dependency on psycopg2 in SQLite/dev environments.

Importing psycopg2.errors will crash if psycopg2 isn’t installed. Guard the import and feature-detect at runtime.

Apply:

-from psycopg2.errors import UniqueViolation
+try:
+    from psycopg2.errors import UniqueViolation as PGUniqueViolation
+except Exception:  # psycopg2 not installed or different driver
+    PGUniqueViolation = ()  # so isinstance(..., PGUniqueViolation) is always False

And update usages:

-            if (
-                isinstance(ie.orig, UniqueViolation)
+            if (
+                isinstance(ie.orig, PGUniqueViolation)
                 and getattr(ie.orig, "diag", None)
                 and ie.orig.diag.constraint_name
                 == SubcategoryCollection._NAME_UNIQUE_CONSTRAINT.name
             ):

…and similarly in put().

🤖 Prompt for AI Agents
In app/migrated_routes/subcategory.py around line 4, the direct import "from
psycopg2.errors import UniqueViolation" creates a hard dependency that will
crash in environments without psycopg2; wrap the import in a try/except
ImportError (or ModuleNotFoundError) and assign a fallback (e.g.,
UniqueViolation = None or a generic Exception subclass) so the module can import
in SQLite/dev setups. Then, in the post() and put() handlers, change the error
handling to first check if UniqueViolation is not None before using it in an
except clause (or catch Exception and inspect getattr(err, '__class__', None) or
err.pgcode/name to detect unique-violation semantics), so you only rely on
psycopg2-specific exception classes when psycopg2 is actually available. Ensure
behavior is unchanged when psycopg2 is present and that code paths gracefully
handle the fallback in dev.

Copy link
Owner Author

Choose a reason for hiding this comment

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

already discussed in #18

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR #18 details and comments to understand the prior discussion
gh pr view 18 --json title,body,comments --jq '{title, body, comments: [.comments[] | {author: .author.login, body: .body}]}'

Length of output: 13165


@piyush-jaiswal, understood! Since you've already addressed this pattern in #18 and are applying it consistently here, I'll note that decision. Thanks for the clarification.

Comment on lines +333 to +339
subcategory = Subcategory.query.get_or_404(id)

products = subcategory.products.order_by(Product.id.asc()).paginate(
page=page, per_page=SubcategoryProducts._PER_PAGE, error_out=False
)

return {"products": products}
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 1, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: returning Pagination object breaks schema; return items.

ProductsOut expects a list; Pagination isn’t a list/iterable for Marshmallow. Return products.items.

Apply:

-        return {"products": products}
+        return {"products": products.items}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
subcategory = Subcategory.query.get_or_404(id)
products = subcategory.products.order_by(Product.id.asc()).paginate(
page=page, per_page=SubcategoryProducts._PER_PAGE, error_out=False
)
return {"products": products}
subcategory = Subcategory.query.get_or_404(id)
products = subcategory.products.order_by(Product.id.asc()).paginate(
page=page, per_page=SubcategoryProducts._PER_PAGE, error_out=False
)
return {"products": products.items}
🤖 Prompt for AI Agents
In app/migrated_routes/subcategory.py around lines 333 to 339, the handler
currently returns the Flask-SQLAlchemy Pagination object which breaks the
Marshmallow ProductsOut schema; instead return the actual list by replacing the
Pagination return with the paginated.items list (e.g., return {"products":
products.items}) so the serializer receives a plain list. Ensure any clients
that need pagination metadata still get it separately if required.

Copy link
Owner Author

Choose a reason for hiding this comment

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

it works. tests pass and manually verified as well

Copy link
Contributor

Choose a reason for hiding this comment

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

@piyush-jaiswal Great to hear that the fix works correctly! Thanks for verifying both through tests and manual testing. 🎉


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Comment on lines +132 to +166
def test_update_subcategory_adds_linked_categories_and_products(self, create_authenticated_headers, create_category, create_product, create_subcategory):
headers = create_authenticated_headers()
category = create_category("UC1", headers=headers).get_json()
product = create_product("UP1", headers=headers).get_json()
subcategory = create_subcategory("U_SC", categories=[category["id"]], products=[product["id"]], headers=headers).get_json()

with pytest.raises(IntegrityError) as ie_c:
self.client.put(
f"/subcategories/{subcategory['id']}",
json={"categories": [category["id"]]},
headers=headers,
)
with pytest.raises(IntegrityError) as ie_p:
self.client.put(
f"/subcategories/{subcategory['id']}",
json={"products": [product["id"]]},
headers=headers,
)
with pytest.raises(IntegrityError) as ie_cp:
self.client.put(
f"/subcategories/{subcategory['id']}",
json={"categories": [category["id"]], "products": [product["id"]]},
headers=headers,
)

assert isinstance(ie_c.value.orig, sqlite3.IntegrityError)
assert isinstance(ie_p.value.orig, sqlite3.IntegrityError)
assert isinstance(ie_cp.value.orig, sqlite3.IntegrityError)
assert "UNIQUE constraint failed" in str(ie_c.value.orig)
assert "UNIQUE constraint failed" in str(ie_p.value.orig)
assert "UNIQUE constraint failed" in str(ie_cp.value.orig)

assert self._subcategory_category_ids(subcategory["id"]) == [category["id"]]
assert self._subcategory_product_ids(subcategory["id"]) == [product["id"]]

Copy link
Contributor

@coderabbitai coderabbitai bot Oct 1, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Re-link tests depend on raw IntegrityError; consider dual-assert for Postgres.

On SQLite this raises IntegrityError; on Postgres the API maps to 409. If CI ever runs on Postgres, gate by engine or assert 409 in an alternate test.


🏁 Script executed:

#!/bin/bash
# Check engine in config to decide if tests should expect exceptions or HTTP 409
rg -n 'SQLALCHEMY_DATABASE_URI' -C2 app

Length of output: 239


🏁 Script executed:

#!/bin/bash
rg -n "IntegrityError" -C3 app

Length of output: 3605


🏁 Script executed:

#!/bin/bash
sed -n '100,140p' app/migrated_routes/subcategory.py
sed -n '210,260p' app/migrated_routes/subcategory.py

Length of output: 3113


Ensure consistent 409 response for duplicate links on all backends
In app/migrated_routes/subcategory.py’s update handler (around the IntegrityError catch in the PUT method), also catch sqlite3.IntegrityError on UNIQUE constraint failures and abort(409) to match the Postgres UniqueViolation path—or update the tests to assert an HTTP 409 rather than a raw IntegrityError.

Copy link
Owner Author

Choose a reason for hiding this comment

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

already discussed in #18

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR #18 details to understand the prior discussion context
gh pr view 18 --repo piyush-jaiswal/ecommerce-rest-api --json title,body,comments --jq '{title, body, comments: [.comments[] | {author: .author.login, body: .body}]}'

Length of output: 13165


@piyush-jaiswal Got it! I can see that PR #18 established the pattern of catching IntegrityError in tests for UNIQUE constraint violations (e.g., test_update_category_adds_linked_subcategories). This PR consistently applies the same approach to subcategory operations. Thanks for the clarification!

@piyush-jaiswal piyush-jaiswal merged commit 3be44fe into master Oct 1, 2025
3 checks passed
@piyush-jaiswal piyush-jaiswal deleted the feature/flask-smorest-subcategory branch October 1, 2025 11:30
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.

1 participant