Reputation: 59313
I want to handle a case where there's a primary key or unique key conflict, a.k.a. a duplicate entry. For this I'm catching the IntegrityError
, which catches the error just fine. The problem is, I can't seem to find any simple error message or error code to check for. All I'm getting is the IntegrityError.message
property which is a string that looks like this:
(IntegrityError) (1062, "Duplicate entry 'foobar' for key 'name'")
That's not very helpful. Using that I'm going to have to start parsing error messages for their code and message. Calling dir
on the exception shows only the following properties:
'args', 'connection_invalidated', 'instance', 'message', 'orig', 'params', 'statement'
args
is simply a single-item tuple with the aforementioned string inside it and params
is the data I tried to insert. I can't seem to find any way of determining that this actually is a duplicate key error without having to start parsing the error message using regex or something.
Can anyone shed some light on this issue?
Upvotes: 30
Views: 22783
Reputation: 1640
As far as I know, the only way is to parse the error string in the exception. I can't find any tuple specifying the error, the column with the violated uniqueness constraint and value separately.
Specifically, one can search the exception message using a substring search operator or a regular expression. For example:
db.session.add(SupplierUser(supplier_id=supplier_id, username=username, password=password, creator=user))
try:
db.session.commit()
except IntegrityError as err:
db.session.rollback()
if "UNIQUE constraint failed: user.username" in str(err):
return False, "error, username already exists (%s)" % username
elif "FOREIGN KEY constraint failed" in str(err):
return False, "supplier does not exist"
else:
return False, "unknown error adding user"
However, these strings can be quite long because the SQL statement is included, for example:
(sqlite3.IntegrityError) UNIQUE constraint failed: user.username [SQL: 'INSERT INTO user (username, password_hash, created_time, creator_id, role, is_active, supplier_id) VALUES (?, ?, ?, ?, ?, ?, ?)'] [parameters: ('bob', ...
Thus, you will minimise the error handling latencies parsing exceptions if you search through the database error message, without the added information from sqlalchemy. This can be done by examining err.args, which should be smaller:
'(sqlite3.IntegrityError) UNIQUE constraint failed: supplier_user.username',)
The updated example:
db.session.add(SupplierUser(supplier_id=supplier_id, username=username, password=password, creator=user))
try:
db.session.commit()
except IntegrityError as err:
db.session.rollback()
err_msg = err.args[0]
if "UNIQUE constraint failed: supplier_user.username" in err_msg:
return False, "error, supplier username already exists (%s)" % username
elif "FOREIGN KEY constraint failed" in err_msg:
return False, "supplier does not exist"
else:
return False, "unknown error adding user"
Note the error syntax I used here is for sqlite3. Parsing an mysql exception error message like:
(1062, "Duplicate entry 'usr544' for key 'username'")
Can be done with an appropriate regular expression. Note that it looks like a tuple, but it is actually a string (sqlalchemy version 1.1.3 and mysql 5.5).
For example:
except IntegrityError as err:
db.session.rollback()
if re.match("(.*)Duplicate entry(.*)for key 'username'(.*)", err.args[0]):
.... etc ....
Upvotes: 4
Reputation: 59313
I figured this out while writing the question by reading the documentation more carefully. I'm still going to post this though since it might be of help to someone.
In the documentation of the SQLAlchemy DBAPIError, from which the IntegrityError is subclassed, it explains that the exception is merely a wrapper for the underlying database API error and that the original error is saved as orig
in the exception. Sure enough, calling e.orig.args
I get a nicely organized tuple:
(1062, "Duplicate entry 'foobar' for key 'name'")
Upvotes: 54