Skip to content
Sign In

Concepts

Error handling

The Python SDK provides a structured error hierarchy for handling server and client-side failures.

All errors raised by the Python SDK extend SurrealError, so you can catch every SDK error with a single except clause. Server-originated errors use the ServerError subtree with structured kinds, details, and cause chains. SDK-side errors cover connection, parsing, and feature support failures.

The error kinds, wire codes and structured shape behind these are documented once in Errors, which applies to every SDK and protocol.

See the Errors reference for the complete error hierarchy and all available properties.

Error class

Description

SurrealError

Base class for all SDK errors

ServerError

Structured server error with kind, details, and cause

NotAllowedError

Thrown when permission is denied

NotFoundError

Thrown when a resource is not found

ConnectionUnavailableError

Thrown when no connection is active

UnsupportedFeatureError

Thrown for features not supported by the connection type

A query travels through two layers, and it is worth knowing which one a failure comes from.

The first is the request itself: a connection that is unavailable, a rejected sign-in, a query that will not parse. The second is the individual statements inside the query, which can fail while the request as a whole succeeds.

.execute() collapses both into an exception. It raises on the first statement that fails, so a query whose earlier statements succeeded returns nothing at all: those results are lost along with the error.

Where the per-statement outcome matters, query_raw() returns every statement instead of raising. Each entry carries a status of OK or ERR, and a failing one also carries the kind.

from surrealdb import Surreal, ThrownError

with Surreal("ws://localhost:8000") as db:
    db.signin({"username": "root", "password": "secret"})
    db.use("test", "test")

    # .execute() raises on the first statement that fails, so the result of
    # the statement that succeeded is not returned.
    try:
        db.query("RETURN 1; THROW 'second'").execute()
    except ThrownError as e:
        print(f"{e.kind}: {e}")

    # query_raw() reports every statement instead of raising.
    response = db.query_raw("RETURN 1; THROW 'second'")
    for index, statement in enumerate(response["result"]):
        print(index, statement["status"], statement["result"])
Output
Thrown: An error occurred: second
0 OK 1
1 ERR An error occurred: second

Every server error carries a .kind, and the SDK raises a dedicated class for each of the kinds below. The meaning of each kind is described in Errors. Match on the kind or the class rather than on the message text, which is free to change between releases.

KindException class
ValidationValidationError
ConfigurationConfigurationError
QueryQueryError
SerializationSerializationError
NotAllowedNotAllowedError
NotFoundNotFoundError
AlreadyExistsAlreadyExistsError
ThrownThrownError
InternalInternalError

Any kind without a dedicated class, including one added by a newer server, arrives as the base ServerError with its .kind intact. Catching ServerError therefore stays correct as the server grows new kinds, and the ErrorKind enum can be used with .has_kind() to test for one without importing its class.

The simplest way to handle errors is to catch SurrealError, which is the base class for every exception the SDK raises.

from surrealdb import Surreal, SurrealError

with Surreal("ws://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    try:
        result = db.query("SELECT * FROM users").execute()
    except SurrealError as e:
        print("SDK error:", e)

This pattern is useful at the top level of your application where you want to ensure no SDK error goes unhandled.

Server errors carry structured information beyond the error message. A ServerError has a .kind string, an optional .details dictionary, and an optional .server_cause linking to the underlying error in the chain.

You can check whether an error is a ServerError and then inspect its kind using the constants defined on ErrorKind.

from surrealdb import ServerError, ErrorKind

try:
    result = db.query("INVALID QUERY").execute()
except ServerError as e:
    print("Kind:", e.kind)
    print("Details:", e.details)

    if e.kind == ErrorKind.VALIDATION:
        print("The query has a validation issue")
    elif e.kind == ErrorKind.NOT_ALLOWED:
        print("Permission denied")

The ErrorKind constants include VALIDATION, CONFIGURATION, THROWN, QUERY, SERIALIZATION, NOT_ALLOWED, NOT_FOUND, ALREADY_EXISTS, CONNECTION, and INTERNAL.

Server errors can form a chain where one error caused another. The .has_kind() method checks whether this error or any error in its cause chain matches a given kind. The .find_cause() method returns the first matching error in the chain.

from surrealdb import ServerError, ErrorKind

try:
    db.signin({"username": "user", "password": "wrong"})
except ServerError as e:
    if e.has_kind(ErrorKind.NOT_ALLOWED):
        print("Authentication failure somewhere in the chain")

    auth_cause = e.find_cause(ErrorKind.NOT_ALLOWED)
    if auth_cause:
        print("Root auth error:", auth_cause)
        print("Details:", auth_cause.details)

These methods are especially useful when a high-level error wraps a more specific cause, such as a query error that was ultimately caused by a permission denial.

For fine-grained control, catch the specific error subclass you need. The SDK maps server error kinds to dedicated Python classes such as ValidationError, NotAllowedError, and NotFoundError.

from surrealdb import NotAllowedError

try:
    db.signin({
        "namespace": "surrealdb",
        "database": "docs",
        "access": "account",
        "variables": {
            "email": "user@example.com",
            "password": "wrong_password",
        },
    })
except NotAllowedError as e:
    if e.is_invalid_auth:
        print("Invalid credentials")
    elif e.is_token_expired:
        print("Token expired, please re-authenticate")

You can also catch NotFoundError to handle missing resources.

from surrealdb import NotFoundError, RecordID

try:
    user = db.select(RecordID("users", "nonexistent"))
except NotFoundError as e:
    if e.table_name:
        print(f"Table not found: {e.table_name}")
    elif e.record_id:
        print(f"Record not found: {e.record_id}")

Some errors originate from the SDK itself rather than the server. These cover situations like missing connections and unsupported features.

A ConnectionUnavailableError is raised when you try to perform an operation before establishing a connection.

from surrealdb import Surreal, ConnectionUnavailableError

db = Surreal("ws://localhost:8000")

try:
    db.select("users")
except ConnectionUnavailableError:
    print("Not connected - call db.connect() first")

An UnsupportedFeatureError is raised when you attempt to use a feature that requires a specific connection type. For example, sessions and transactions require a WebSocket connection.

from surrealdb import Surreal, UnsupportedFeatureError

with Surreal("http://localhost:8000") as db:
    db.use("my_ns", "my_db")
    db.signin({"username": "root", "password": "secret"})

    try:
        session = db.new_session()
    except UnsupportedFeatureError:
        print("Sessions require a WebSocket connection")

An UnsupportedEngineError is raised when the URL scheme is not recognized.

from surrealdb import Surreal, UnsupportedEngineError

try:
    db = Surreal("ftp://localhost:8000")
except UnsupportedEngineError as e:
    print(f"Unsupported protocol: {e.url}")

Was this page helpful?