Error Handling

Errors are raised as exceptions. There is no return value that means failure, and nothing is committed after an error.

try:
    uid = db.insert('users', {'email': '[email protected]'})
except Exception:
    ...   # the row was NOT inserted and NOT committed

What is raised, and when

Situation Result
SQL error (constraint, syntax, type) the driver's exception (pymysql.MySQLError / psycopg2.Error)
Connection closed RuntimeError
condition of an invalid type TypeError
Empty WHERE in update() / delete() ValueError
Empty data in insert() / update() ValueError
NaN or Inf as a value ValueError
str() or an f-string over a Condition TypeError
and, or, not or bool() over a Condition TypeError
resetSession() with a transaction open RuntimeError
reconnect() or connect() with a transaction open RuntimeError
Any operation from a thread that does not own the open transaction RuntimeError
CRUD, a batch, begin(), truncate(), reconnect or reset with pending work outside transaction() RuntimeError
Any operation after a failed begin, commit, rollback or savepoint RuntimeError, until rollback() or close()
for_update=True outside a transaction RuntimeError
Rows of a batch with different keys, or a non-dict row ValueError
A str, bytes or dict as the batch container TypeError
conflict_columns on MySQL, or missing on PostgreSQL ValueError

Nothing is retried

A statement is sent once. After a driver exception there is no automatic reconnection and no second send, inside or outside a transaction. A failed commit is not repeated either.

try:
    db.insert('users', {'email': '[email protected]'})
except Exception:
    ...            # sent once
    db.reconnect() # explicit, when the session has to be replaced

A lost response does not mean the write or the commit failed on the server. Check the state from another connection before repeating the operation.

What is not an error

A SELECT with no matches returns []. That means zero rows, never a failure:

rows = db.select('users', {'email': '[email protected]'})
if not rows:
    ...   # no user with that address; the query itself worked

Logging

Library messages go through the standard logging module under the easymysql logger. Nothing is printed to stdout.

import logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger('easymysql').setLevel(logging.DEBUG)

Counters are invalidated by a failure

count() and getLastId() are reset before every operation, and again if it fails at any point — argument validation, the statement itself, the fetch, or the commit. After a failure they return 0 and None:

db.insert('users', {'email': '[email protected]'})
db.getLastId()          # the new id

try:
    db.insert('users', {'email': '[email protected]'})   # violates UNIQUE
except Exception:
    db.getLastId()      # None, not the earlier id

count() == 0 and getLastId() is None mean the metadata is unavailable. They are not evidence that the server applied nothing.

Errors inside a transaction

If the exception escapes the with block, everything is rolled back:

with db.transaction():
    db.insert('orders', {'customer_id': 7})
    db.update('stock', {'quantity': 0}, {'id': 3})   # if this fails,
# ...the insert above is rolled back too