Skip to content

Commit fa021e6

Browse files
catch unique constraint violations for categories and return 409 response
1 parent e5a815a commit fa021e6

File tree

2 files changed

+55
-4
lines changed

2 files changed

+55
-4
lines changed

app/migrated_routes/category.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from flask.views import MethodView
22
from flask_jwt_extended import jwt_required
33
from flask_smorest import Blueprint, abort
4-
from sqlalchemy import exists
4+
from psycopg2.errors import UniqueViolation
5+
from sqlalchemy import UniqueConstraint, exists
6+
from sqlalchemy.exc import IntegrityError
57

68
from app import db
79
from app.models import (
@@ -27,6 +29,19 @@
2729
class CategoryCollection(MethodView):
2830
init_every_request = False
2931

32+
@staticmethod
33+
def _get_name_unique_constraint():
34+
name_col = Category.__table__.c.name
35+
return next(
36+
con
37+
for con in Category.__table__.constraints
38+
if isinstance(con, UniqueConstraint)
39+
and len(con.columns) == 1
40+
and con.columns.contains_column(name_col)
41+
)
42+
43+
_NAME_UNIQUE_CONSTRAINT = _get_name_unique_constraint()
44+
3045
@bp.response(200, CategoriesOut)
3146
def get(self):
3247
"""
@@ -88,8 +103,18 @@ def post(self, data):
88103

89104
category.subcategories = subcategories
90105

91-
db.session.add(category)
92-
db.session.commit()
106+
try:
107+
db.session.add(category)
108+
db.session.commit()
109+
except IntegrityError as ie:
110+
db.session.rollback()
111+
if (
112+
isinstance(ie.orig, UniqueViolation)
113+
and ie.orig.diag.constraint_name
114+
== CategoryCollection._NAME_UNIQUE_CONSTRAINT.name
115+
):
116+
abort(409, message="Category with this name already exists")
117+
raise ie
93118

94119
return category
95120

@@ -176,7 +201,18 @@ def put(self, data, id):
176201

177202
category.subcategories.extend(subcategories)
178203

179-
db.session.commit()
204+
try:
205+
db.session.commit()
206+
except IntegrityError as ie:
207+
db.session.rollback()
208+
if (
209+
isinstance(ie.orig, UniqueViolation)
210+
and ie.orig.diag.constraint_name
211+
== category_subcategory.primary_key.name
212+
):
213+
abort(409, message="Category and subcategory already linked")
214+
raise ie
215+
180216
return category
181217

182218
@jwt_required()

tests/test_relationships.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import sqlite3
2+
13
import pytest
4+
from sqlalchemy.exc import IntegrityError
25

36
from app.models import Category, Product, Subcategory
47

@@ -96,6 +99,18 @@ def test_update_category_adds_subcategories(self, create_authenticated_headers,
9699

97100
assert self._category_subcategory_ids(category["id"]) == sorted([subcategory1["id"], subcategory2["id"]])
98101

102+
def test_update_category_adds_linked_subcategories(self, create_authenticated_headers, create_category, create_subcategory):
103+
headers = create_authenticated_headers()
104+
subcategory = create_subcategory("U_SC1", headers=headers).get_json()
105+
category = create_category("U_Cat", subcategories=[subcategory["id"]], headers=headers).get_json()
106+
107+
with pytest.raises(IntegrityError) as ie:
108+
self.client.put(f"/categories/{category['id']}", json={"subcategories": [subcategory["id"]]}, headers=headers)
109+
110+
assert isinstance(ie.value.orig, sqlite3.IntegrityError)
111+
assert "UNIQUE constraint failed" in str(ie.value.orig)
112+
assert self._category_subcategory_ids(category["id"]) == [subcategory["id"]]
113+
99114
def test_update_subcategory_adds_categories_and_products(self, create_authenticated_headers, create_category, create_product, create_subcategory):
100115
category1 = create_category("UC1").get_json()
101116
category2 = create_category("UC2").get_json()

0 commit comments

Comments
 (0)