# Errors A statement the server refuses raises psycopg's exception for that refusal, by SQLSTATE, from `psycopg.errors`, raised by psycopg itself. The driver does not wrap it: `except psycopg.errors.UniqueViolation` catches a duplicate key, and `exc.sqlstate` and `exc.diag` hold what the server said. The server mints no SQLSTATE of its own for graph operations, so every class you know from PostgreSQL is the one you get. ## The driver's own classes Three refusals cannot be told apart by their SQLSTATE, so the driver raises a class of its own for each, a subclass of what psycopg would have raised: | Class | Raised when | Subclass of | |---|---|---| | `ConfigurationError` | the server refuses because of a setting: `enable_graph_dml` or `enable_eager` is off; `setting` names it | `OperationalError` | | `ReadOnlyGraphWrite` | a graph write in a read-only transaction | `ReadOnlySqlTransaction` | | `CapabilityError` | something this server's version does not have; `feature`, `required` and `found` say what | `NotSupportedError` | ```pycon >>> conn.execute("INSERT INTO moviekb.movie (properties) VALUES ('{}')") Traceback (most recent call last): ... agensgraph.errors.ConfigurationError: DML query to graph objects is not allowed: plain SQL data modification of a label table is refused while enable_graph_dml is off, which is its default and which only a superuser may change; write through Cypher instead >>> conn.rollback() ``` And for the driver's own failures: | Class | Raised when | |---|---| | `StaleLabelCache` | a composite-rendering read meets a label the connection's label table does not have; `refresh_labels()` | | `NoEnclosingTransaction` | `stream()` on a connection in autocommit, outside a transaction | | `ReleasedConnection` | a pooled connection used after it went back to the pool | | `StaleGeneration` | a pool could not find a connection of its current generation | | `BatchFailed` | a [`pipeline_batch()`](../guide/running-statements.md#many-statements-at-once) failed; `statements` lists what was sent | | `Expired` | a [deadline](timeouts.md#deadlines) ran out before anything was sent; a `TimeoutError` | All of them are in `agensgraph.errors`, which also re-exports psycopg's PEP 249 hierarchy as the very same classes, so `except agensgraph.errors.IntegrityError` and `except psycopg.IntegrityError` catch the same exceptions. `BatchFailed` and `Expired` are also importable from `agensgraph` itself. A value the driver refuses before sending, such as a NaN in a property map or a parameter for the length of a walk, raises `ValueError` and never reaches the server. ## What a failure prints as PostgreSQL puts row data in a failure's DETAIL. A uniqueness failure says which value was a duplicate, so a plain `logger.exception()` would write into the log a value the application never meant to log. **The driver leaves DETAIL and CONTEXT out of the message by default**: ```pycon >>> import psycopg >>> conn.execute("CREATE UNIQUE PROPERTY INDEX movie_title_unique ON movie (title)") >>> try: ... conn.execute_query("CREATE (:movie {title: %s})", ("Inception",)) ... except psycopg.errors.UniqueViolation as exc: ... duplicate = exc >>> conn.rollback() >>> str(duplicate) 'duplicate key value violates unique constraint "movie_title_unique"' >>> duplicate.diag.message_detail 'Key ((properties.\'title\'))=("Inception") already exists.' ``` The data is still there, in `exc.diag`, for a post-mortem; only what the exception prints as is cut. Anything after the first line of the message goes: DETAIL, CONTEXT and the `LINE` marker libpq adds. To have it printed, for development, turn it on for the whole process: ```python agensgraph.show_error_details(True) ``` `showing_error_details()` reports the setting, and `redact_details(exc)` cuts one exception you hold. The setting applies to failures raised by statements, streams and the bulk loaders; a failure raised by `commit()` itself, such as a deferred constraint, is psycopg's and printed in full. ## Handing a failure to someone else To report a failure to someone who did not send the statement, in an API response, a tool's result, or back to a language model that wrote the query, send the SQLSTATE and the server's first line and nothing else. `safe_message()` is that: ```pycon >>> agensgraph.safe_message(duplicate) '23505: duplicate key value violates unique constraint "movie_title_unique"' ``` It never includes DETAIL, it is always one line, `show_error_details()` does not change it, and it works for any exception: one without a SQLSTATE, such as the driver's own, gives its first line. `agensgraph.errors.mask_dsn()` hides the password in a connection string you want to log. Nothing in the driver puts a connection string in a message, a log record or a `repr`. ## Whether to try again `agensgraph.retryability(exc)` classifies any exception into what to do next, from `SAFE` to `FATAL`; see [Retries](retries.md).