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?
Lightweight Transactions (LWT) — conditional writes with IF NOT EXISTS / IF EXISTS
Batch Operations — batch multiple statements into one round-trip
Sync vs Async API — choosing between sync and async APIs
Drivers & Initialization — driver configuration and multi-cluster setups