Exceptions & Error Handling

coodie defines a small hierarchy of exceptions that make it easy to handle errors from database operations. All exceptions inherit from CoodieError, so you can catch everything with a single base class or handle specific cases individually.

Exception Hierarchy

CoodieError
├── DocumentNotFound
├── MultipleDocumentsFound
├── ConfigurationError
└── InvalidQueryError

All exceptions live in coodie.exceptions:

from coodie.exceptions import (
    CoodieError,
    DocumentNotFound,
    MultipleDocumentsFound,
    ConfigurationError,
    InvalidQueryError,
)

DocumentNotFound

Raised when get() finds no matching row:

from coodie.exceptions import DocumentNotFound

try:
    user = User.get(id=some_id)              # sync
    # user = await User.get(id=some_id)      # async
except DocumentNotFound:
    print("User not found")

This is the most common exception you’ll encounter. Use find() with .first() if you’d rather get None instead of an exception:

user = User.find(id=some_id).first()     # returns None if not found

MultipleDocumentsFound

Raised when a single-document lookup unexpectedly returns more than one row:

from coodie.exceptions import MultipleDocumentsFound

try:
    user = User.get(role="admin")
except MultipleDocumentsFound:
    print("Expected one admin, got many")

ConfigurationError

Raised when coodie is not properly configured — most commonly when you forget to call init_coodie() before using models:

from coodie.exceptions import ConfigurationError

try:
    User.find().all()
except ConfigurationError:
    print("Did you forget to call init_coodie()?")

This also triggers when requesting a named driver that hasn’t been registered:

from coodie.drivers import get_driver

try:
    driver = get_driver("nonexistent")
except ConfigurationError:
    print("No driver registered with that name")

InvalidQueryError

Raised when a query is constructed incorrectly — for example, filtering on a column that doesn’t exist or using an unsupported operator:

from coodie.exceptions import InvalidQueryError

try:
    User.find(nonexistent_field="value").all()
except InvalidQueryError:
    print("Bad query construction")

Catch-All Pattern

Use the CoodieError base class to catch any coodie exception:

from coodie.exceptions import CoodieError

try:
    result = some_coodie_operation()
except CoodieError as e:
    print(f"coodie error: {e}")

Best Practices

Use specific exceptions

Catch the most specific exception that applies. This makes your error handling explicit and avoids masking unexpected errors:

# ✅ Good — specific
try:
    user = User.get(id=user_id)
except DocumentNotFound:
    return create_default_user(user_id)

# ❌ Avoid — too broad
try:
    user = User.get(id=user_id)
except CoodieError:
    return create_default_user(user_id)

Use find().first() for optional lookups

When a missing row is a normal case (not an error), avoid exceptions entirely:

# ✅ No exception handling needed
user = User.find(id=user_id).first()
if user is None:
    user = create_default_user(user_id)

Check LWTResult instead of catching exceptions

Conditional writes (LWT) don’t raise exceptions when the condition fails — they return an LWTResult:

from coodie.results import LWTResult

result = lock.insert()
if not result.applied:
    # Handle conflict — no exception was raised
    handle_conflict(result.existing)

Initialize early

Call init_coodie() at application startup, before any model operations. This avoids ConfigurationError scattered throughout your code:

# app.py
from coodie.sync import init_coodie

def main():
    init_coodie(hosts=["127.0.0.1"], keyspace="my_ks")
    # ... application logic

What’s Next?