lfnovo/open-notebook · critical · DatabaseOperationError

Error fetching all {cls.table_name}: {str(e)}

Error message

Error fetching all {cls.table_name}: {str(e)}

What it means

get_all() wraps its database query in a broad try/except: any exception from repo_query, row deserialization (target_class(**row)), or the SurrealDB connection is logged and re-raised as DatabaseOperationError. It signals an infrastructure/deserialization failure, not bad input.

Source

Thrown at open_notebook/domain/base.py:102

            if order_by:
                validated_order_by = cls._validate_order_by(order_by)
                query = f"SELECT * FROM {table_name} ORDER BY {validated_order_by}"
            else:
                query = f"SELECT * FROM {table_name}"

            result = await repo_query(query)
            objects = []
            for obj in result:
                try:
                    objects.append(target_class(**obj))
                except Exception as e:
                    logger.critical(f"Error creating object: {str(e)}")

            return objects
        except Exception as e:
            logger.error(f"Error fetching all {cls.table_name}: {str(e)}")
            logger.exception(e)
            raise DatabaseOperationError(e)

    @classmethod
    async def get(cls: Type[T], id: str) -> T:
        if not id:
            raise InvalidInputError("ID cannot be empty")
        try:
            # Get the table name from the ID (everything before the first colon)
            table_name = id.split(":")[0] if ":" in id else id

            # If we're calling from a specific subclass and IDs match, use that class
            if cls.table_name and cls.table_name == table_name:
                target_class: Type[T] = cls
            else:
                # Otherwise, find the appropriate subclass based on table_name
                found_class = cls._get_class_by_table_name(table_name)
                if not found_class:
                    raise InvalidInputError(f"No class found for table {table_name}")
                target_class = cast(Type[T], found_class)

View on GitHub (pinned to a7de90d38a)

Solutions

  1. Verify SurrealDB is running (make database / make status) and the API could reach it at startup
  2. Inspect logs — logger.exception(e) prints the original traceback; fix the underlying cause
  3. If it's deserialization, compare the model's Pydantic fields against actual records (schema drift) and let migrations run or fix the data
  4. Catch DatabaseOperationError at the API layer and return a 503/500 rather than crashing

Example fix

// before
objects = await Note.get_all()  # raises DatabaseOperationError bare

// after
from open_notebook.domain.errors import DatabaseOperationError
try:
    objects = await Note.get_all()
except DatabaseOperationError as e:
    logger.error(f"DB unavailable: {e}")
    objects = []
Defensive patterns

Strategy: try-catch

Validate before calling

null

Try / catch

from open_notebook.domain.errors import DatabaseOperationError
try:
    items = await Note.get_all()
except DatabaseOperationError as e:
    logger.exception("get_all failed", exc_info=e)
    raise HTTPException(status_code=503, detail="Database temporarily unavailable")

Prevention

When it happens

Trigger: SurrealDB is down or unreachable; the record shape doesn't match the model's Pydantic fields (schema drift after a migration); connection timeout; permission errors on the table. The exception's __cause__ holds the original error.

Common situations: Forgot to run `make database` before the API, schema migrations didn't run on startup (check API logs), a model field was renamed in Python but not migrated in SurrealDB, or stale docker container with old schema.

Related errors


AI-assisted analysis of lfnovo/open-notebook@a7de90d38a (2026-08-27). Data as JSON: /api/errors/d8cfc5f7b42cc6eb. Report an issue: GitHub.