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

>>> 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() failed; statements lists what was sent

Expired

a deadline 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:

>>> import psycopg
>>> conn.execute("CREATE UNIQUE PROPERTY INDEX movie_title_unique ON movie (title)")
<agensgraph.connection.Cursor ...>
>>> 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:

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:

>>> 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.