Skip to main content

Keble-DB

A comprehensive database toolkit providing CRUD operations for MongoDB, SQL, and Qdrant databases with both synchronous and asynchronous support.

Installation

pip install keble-db

Key Features

  • CRUD Operations: Complete Create, Read, Update, Delete operations for different databases
  • Dual API Support: Both synchronous and asynchronous interfaces
  • Multiple Database Support: MongoDB, SQL (SQLAlchemy/SQLModel), and Qdrant vector database
  • FastAPI Integration: Specialized dependency injection utilities for FastAPI
  • Extended Redis Support: Enhanced Redis functionality with namespace management and batch operations
  • Redis Connection Resilience: Sync and async pools validate idle connections every 30 seconds before reuse. Automatic command retry remains operation-owned because these general clients can issue non-idempotent commands; the package does not risk replaying an ambiguous write. Namespaced extended clients reuse the same configured pool, so this policy is preserved on both the base and extended APIs.
  • Pydantic Integration: Fully compatible with Pydantic v2 for data validation

SQL Pool Limits

Db uses bounded SQLAlchemy pools for every sync and async read/write engine. The defaults are intentionally conservative because each backend process owns separate read and write engines:

  • sync pool size: 3
  • sync max overflow: 2
  • async pool size: 3
  • async max overflow: 2
  • pool recycle: 1800 seconds
  • pool timeout: 30 seconds

Override these properties on your DbSettingsABC implementation when a service has a larger PostgreSQL connection budget. The package validates pool settings at startup so invalid values fail before traffic can exhaust PostgreSQL with oversized connection pools.

Schemas

The package provides essential schemas for database operations:

  • QueryBase: Used throughout the package for creating consistent queries
  • ObjectId: Custom ObjectId implementation for use with Pydantic (you cannot use bson.ObjectId directly in Pydantic)
from keble_db.schemas import QueryBase, ObjectId

# Create a query with filters
# Note: filters and order_by vary by database type
query = QueryBase(
    filters={"name": "test"},  # MongoDB: dict with query operators, SQL: list of expressions, Qdrant: dict
    limit=10,
    offset=0,
    # Order by fields vary by database type:
    # MongoDB: list of tuples [(field_name, ASCENDING/DESCENDING)]
    # SQL: list of SQLAlchemy expressions
    # Qdrant: not applicable for vector similarity search
    order_by=[("created_at", -1)]  # MongoDB example
)

# Using ObjectId with Pydantic
from pydantic import BaseModel

class MyModel(BaseModel):
    id: ObjectId
    name: str

QueryBase Implementation Details

The QueryBase class is used to build queries across different database types, but its fields have different expectations depending on the database type:

MongoDB QueryBase

from keble_db.schemas import QueryBase
from pymongo import ASCENDING, DESCENDING

# MongoDB uses dict for filters with MongoDB query operators
query = QueryBase(
    filters={"name": "John", "age": {"$gt": 18}},  # MongoDB query dict
    limit=10,
    offset=0,  # MongoDB requires int offset
    order_by=[("created_at", DESCENDING), ("name", ASCENDING)]  # List of (field, direction) tuples
)

SQL QueryBase

from keble_db.schemas import QueryBase
from sqlmodel import select
from mymodels import User  # Your SQLModel

# SQL uses list of SQLAlchemy expressions for filters
query = QueryBase(
    filters=[User.age > 18, User.name == "John"],  # List of SQLAlchemy expressions
    limit=10,
    offset=0,  # SQL requires int offset
    order_by=[User.created_at.desc(), User.name.asc()]  # List of SQLAlchemy expression objects
)

Qdrant QueryBase

from keble_db.schemas import QueryBase

# For Qdrant search operations (with int offset)
search_query = QueryBase(
    filters={"name": {"$eq": "Test Item"}},  # Qdrant filter dict
    limit=10,
    offset=0,  # For search: integer offset
    # order_by is not applicable for vector similarity search
)

# For Qdrant scroll operations (with string point_id as offset)
scroll_query = QueryBase(
    filters={"name": {"$eq": "Test Item"}},  # Qdrant filter dict
    limit=10,
    offset="some_point_id",  # For scroll: string point_id as offset
    # order_by is not applicable for vector similarity search
)

Creating CRUD Classes

You can define custom CRUD classes by extending the base classes for each database type:

MongoDB CRUD Class

from pydantic import BaseModel
from keble_db.crud.mongo import MongoCRUDBase

# Define your model
class UserModel(BaseModel):
    name: str
    email: str
    age: int

# Define your CRUD class
class CRUDUser(MongoCRUDBase[UserModel]):
    # You can add custom methods here
    pass

# Initialize the CRUD instance
user_crud = CRUDUser(
    model=UserModel,
    collection="users",
    database="my_database"
)

SQL CRUD Class

from sqlmodel import SQLModel, Field
from typing import Optional
from uuid import UUID, uuid4
from keble_db.crud.sql import SqlCRUDBase

# Define your model
class UserModel(SQLModel, table=True):
    id: Optional[UUID] = Field(default_factory=uuid4, primary_key=True)
    name: str
    email: str
    age: int

# Define your CRUD class
class CRUDUser(SqlCRUDBase[UserModel]):
    # You can add custom methods here
    pass

# Initialize the CRUD instance
user_crud = CRUDUser(
    model=UserModel,
    table_name="users"
)

Qdrant CRUD Class

from pydantic import BaseModel
from typing import List
from keble_db.crud.qdrant import QdrantCRUDBase

# Define your models
class VectorModel(BaseModel):
    vector: List[float]

class ItemModel(BaseModel):
    id: int
    name: str
    description: str

# Define your CRUD class
class CRUDItem(QdrantCRUDBase[ItemModel, VectorModel]):
    # You can add custom methods here
    pass

# Initialize the CRUD instance
item_crud = CRUDItem(
    model=ItemModel,
    vector_model=VectorModel,
    collection="items"
)

Database Operations by Type

MongoDB CRUD Operations

The MongoDB CRUD interface provides methods for working with MongoDB collections.

from keble_db.crud.mongo import MongoCRUDBase
from pymongo import MongoClient, ASCENDING, DESCENDING
from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseModel
from keble_db.schemas import QueryBase

# Define your model
class User(BaseModel):
    name: str
    email: str
    age: int

# Create a CRUD instance
user_crud = MongoCRUDBase(
    model=User,
    collection="users",
    database="my_database"
)

# Synchronous operations
mongo_client = MongoClient("mongodb://localhost:27017")

# Create a document
user = User(name="John", email="john@example.com", age=30)
# Returns pymongo.results.InsertOneResult
result = user_crud.create(mongo_client, obj_in=user)

# Read documents
# MongoDB QueryBase usage
query = QueryBase(
    filters={"name": "John"},  # MongoDB uses dict for filters with query operators
    limit=10,
    offset=0,
    order_by=[("created_at", DESCENDING)]  # List of (field, direction) tuples
)

# First returns a User model instance or None
user = user_crud.first(mongo_client, query=query)
# get_multi returns a list of User model instances
users = user_crud.get_multi(mongo_client, query=QueryBase(limit=10, offset=0))

# MongoDB _id is typically a bson.ObjectId
user_by_id = user_crud.first_by_id(mongo_client, _id="6463a8880f23dfd71c67c487")  # ObjectId as string

# Update a document
# Returns pymongo.results.UpdateResult
update_result = user_crud.update(mongo_client, _id="6463a8880f23dfd71c67c487", obj_in={"age": 31})

# Delete documents
# Returns pymongo.results.DeleteResult
delete_result = user_crud.delete(mongo_client, _id="6463a8880f23dfd71c67c487")
delete_multi_result = user_crud.delete_multi(mongo_client, query=QueryBase(filters={"age": {"$lt": 18}}))

# Asynchronous operations with motor client
async_mongo_client = AsyncIOMotorClient("mongodb://localhost:27017")

# Create a document asynchronously
user = User(name="Jane", email="jane@example.com", age=28)
result = await user_crud.acreate(async_mongo_client, obj_in=user)

# Read documents asynchronously
user = await user_crud.afirst(async_mongo_client, query=QueryBase(filters={"name": "Jane"}))
users = await user_crud.aget_multi(async_mongo_client, query=QueryBase(limit=10, offset=0))
user_by_id = await user_crud.afirst_by_id(async_mongo_client, _id="6463a8880f23dfd71c67c487")

# Update a document asynchronously
update_result = await user_crud.aupdate(async_mongo_client, _id="6463a8880f23dfd71c67c487", obj_in={"age": 29})

# Delete documents asynchronously
delete_result = await user_crud.adelete(async_mongo_client, _id="6463a8880f23dfd71c67c487")
delete_multi_result = await user_crud.adelete_multi(async_mongo_client, query=QueryBase(filters={"age": {"$lt": 18}}))

# Aggregate operations (MongoDB specific)
from typing import List
class AggregationResult(BaseModel):
    _id: int
    count: int

aggregated_data = user_crud.aggregate(
    mongo_client,
    pipelines=[{"$group": {"_id": "$age", "count": {"$sum": 1}}}],
    model=AggregationResult
)

# Async aggregate operations (MongoDB specific)
aggregated_data = await user_crud.aaggregate(
    async_mongo_client,
    pipelines=[{"$group": {"_id": "$age", "count": {"$sum": 1}}}],
    model=AggregationResult
)

SQL CRUD Operations

The SQL CRUD interface provides methods for working with SQL databases via SQLModel/SQLAlchemy.

from keble_db.crud.sql import SqlCRUDBase

Synchronous SQL Operations

from keble_db.crud.sql import SqlCRUDBase
from sqlmodel import Session, SQLModel, Field
from typing import Optional
from uuid import UUID, uuid4

# Define your model
class User(SQLModel, table=True):
    id: Optional[UUID] = Field(default_factory=uuid4, primary_key=True)
    name: str
    email: str
    active: bool = True

# Initialize CRUD instance
user_crud = SqlCRUDBase[User](
    model=User,
    table_name="users"
)

# Create a DB session
from sqlmodel import create_engine, Session

engine = create_engine("sqlite:///database.db")
session = Session(engine)

# Create a new user
new_user = User(name="John Doe", email="john@example.com")
# Returns User instance with ID populated
user = user_crud.create(session, obj=new_user)

# QueryBase usage for SQL
from keble_db.schemas import QueryBase

query = QueryBase(
    filters=[User.active == True, User.name.contains("John")],  # List of SQLAlchemy expressions
    limit=10,
    offset=0,
    order_by=[User.name.asc(), User.email.desc()]  # List of SQLAlchemy order expressions
)

# Get a single user
# Returns User instance or None
user = user_crud.first(session, query=query)

# Get multiple users
# Returns list of User instances
users = user_crud.get_multi(session, query=query)

# Update a user
# Returns updated User instance or None if not found
user.name = "Jane Doe"
updated_user = user_crud.update(session, _id=user.id, obj=user)

# Delete a user
# Returns boolean (True if user was deleted)
result = user_crud.delete(session, _id=user.id)

# Count users
# Returns integer count
count = user_crud.count(session, query=query)

# Create multiple users
from typing import List
users: List[User] = [
    User(name="User 1", email="user1@example.com"),
    User(name="User 2", email="user2@example.com"),
    User(name="User 3", email="user3@example.com"),
]
# Returns list of created User instances
created_users = user_crud.create_multi(session, objs_in=users)

# Delete multiple users
# Returns number of deleted records
deleted_count = user_crud.delete_multi(session, query=query)

Async SQL Operations

The SQL CRUD interface also provides asynchronous methods for working with SQL databases, which are prefixed with 'a'.

from keble_db.crud.sql import SqlCRUDBase
from sqlmodel.ext.asyncio.session import AsyncSession
from sqlalchemy.ext.asyncio import create_async_engine
from sqlmodel import SQLModel, Field
from typing import Optional
from uuid import UUID, uuid4
from keble_db.schemas import QueryBase

# Define your model (same as for synchronous operations)
class User(SQLModel, table=True):
    id: Optional[UUID] = Field(default_factory=uuid4, primary_key=True)
    name: str
    email: str
    age: int

# Create a CRUD instance (same as for synchronous operations)
user_crud = SqlCRUDBase(
    model=User,
    table_name="users"
)

# Create an async session
# Note: Must use an async driver (psycopg for PostgreSQL, aiosqlite for SQLite)
engine = create_async_engine("postgresql+psycopg://user:password@localhost/dbname")
async with AsyncSession(engine) as session:
    # Create a document asynchronously
    user = User(name="John", email="john@example.com", age=30)
    # Returns the created User instance with populated id
    created_user = await user_crud.acreate(session, obj_in=user)
    
    # Create multiple documents asynchronously
    users = [
        User(name="Alice", email="alice@example.com", age=25),
        User(name="Bob", email="bob@example.com", age=35),
    ]
    # Returns a list of created User instances
    created_users = await user_crud.acreate_multi(session, obj_in_list=users)
    
    # SQL QueryBase usage (same as synchronous)
    query = QueryBase(
        # SQL uses list of SQLAlchemy expressions for filters
        filters=[User.age > 18, User.name == "John"],
        limit=10,
        offset=0,
        order_by=[User.created_at.desc()]  # List of SQLAlchemy expression objects
    )
    
    # Read documents asynchronously
    # Returns a User instance or None
    user = await user_crud.afirst(session, query=query)
    # Returns a list of User instances
    users = await user_crud.aget_multi(session, query=QueryBase(limit=10, offset=0))
    # SQL _id is typically a UUID or int depending on your model
    user_by_id = await user_crud.afirst_by_id(session, _id=uuid4())
    
    # Count documents asynchronously
    # Returns an integer
    count = await user_crud.acount(session, query=QueryBase(filters=[User.age > 18]))
    
    # Update a document asynchronously
    # Returns the updated User instance
    updated_user = await user_crud.aupdate(session, _id=uuid4(), obj_in={"age": 31})
    
    # Delete documents asynchronously
    # Returns None
    await user_crud.adelete(session, _id=uuid4())
    # Can delete by id or by object instances
    await user_crud.adelete_multi(session, obj_in_list=[uuid4(), user1, user2])

Using the database session manager:

from keble_db.session import Db
from keble_db.schemas import DbSettingsABC
from sqlmodel.ext.asyncio.session import AsyncSession

# Initialize DB with settings
db = Db(settings)  # settings implements DbSettingsABC

# Get async SQL session
async_session = db.get_async_sql_write_client()

try:
    # Use the async session with the CRUD methods
    user = await user_crud.afirst(
        async_session, 
        query=QueryBase(filters=[User.name == "John"])
    )
    
    # Update the user
    if user:
        updated_user = await user_crud.aupdate(
            async_session,
            _id=user.id,
            obj_in={"age": 32}
        )
finally:
    # Always close the session
    await db.try_close_async(async_session)

With FastAPI dependency injection:

from fastapi import FastAPI, Depends
from keble_db.deps.api import ApiDbDeps

# Initialize dependencies
api_db_deps = ApiDbDeps(db)
app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(
    user_id: UUID,
    async_session = Depends(api_db_deps.get_async_read_sql)
):
    user = await user_crud.afirst_by_id(async_session, _id=user_id)
    return user

@app.post("/users")
async def create_user(
    user_data: UserCreate,
    async_session = Depends(api_db_deps.get_async_write_sql)
):
    user = User(**user_data.dict())
    created_user = await user_crud.acreate(async_session, obj_in=user)
    return created_user

Qdrant Vector Database CRUD Operations

The Qdrant CRUD interface provides methods for working with Qdrant vector database.

from keble_db.crud.qdrant import QdrantCRUDBase
from qdrant_client import QdrantClient, AsyncQdrantClient
from pydantic import BaseModel
from typing import List
from keble_db.schemas import QueryBase

# Define your models
class VectorModel(BaseModel):
    vector: List[float]

class Item(BaseModel):
    id: int
    name: str
    description: str

# Create a CRUD instance
item_crud = QdrantCRUDBase(
    model=Item,
    vector_model=VectorModel,
    collection="items"
)

# Synchronous operations
qdrant_client = QdrantClient("localhost", port=6333)

# Create an item with vector
vector = VectorModel(vector=[0.1, 0.2, 0.3])
item = Item(id=1, name="Test Item", description="This is a test item")
# Returns boolean (True if operation was successful)
result = item_crud.create(qdrant_client, vector, item, "unique_id_1")

# Create multiple items
items_and_vectors = [
    ("unique_id_2", Item(id=2, name="Item 2", description="Description 2"), VectorModel(vector=[0.4, 0.5, 0.6])),
    ("unique_id_3", Item(id=3, name="Item 3", description="Description 3"), VectorModel(vector=[0.7, 0.8, 0.9])),
]
# Returns boolean (True if operation was successful)
result = item_crud.create_multi(qdrant_client, payloads_and_vectors=items_and_vectors)

# Qdrant QueryBase usage
# order_by is not applicable for vector similarity search
query = QueryBase(
    filters={"name": {"$eq": "Test Item"}},  # Qdrant uses dict for filters
    limit=10,
    offset=0,  # For search: int offset
    # order_by is not applicable for Qdrant vector similarity search
)

# Read items
# Returns Item instance or None
item = item_crud.first_by_id(qdrant_client, _id="unique_id_1")
# Returns full Qdrant record (with vector and payload)
record = item_crud.first_record_by_id(qdrant_client, _id="unique_id_1")
# Returns list of Item instances
items = item_crud.get_multi_by_ids(qdrant_client, _ids=["unique_id_1", "unique_id_2"])
# Returns list of full Qdrant records
records = item_crud.get_multi_records_by_ids(qdrant_client, _ids=["unique_id_1", "unique_id_2"])

# Search by vector similarity
# Returns list of search results with scores
search_results = item_crud.search(
    qdrant_client,
    vector=[0.1, 0.2, 0.3],
    vector_key="vector",
    score_threshold=0.75
)

# Update items
# Returns boolean (True if operation was successful)
result = item_crud.update_payload(qdrant_client, _id="unique_id_1", payload=item)
result = item_crud.overwrite_payload(qdrant_client, _id="unique_id_1", payload=item)
result = item_crud.update_vector(qdrant_client, _id="unique_id_1", vector=vector)

# Delete items
# Returns boolean (True if operation was successful)
result = item_crud.delete(qdrant_client, _id="unique_id_1")
result = item_crud.delete_multi(qdrant_client, query=QueryBase(filters={"name": {"$eq": "Test Item"}}))

# Scroll through items with pagination using point_id
# Returns tuple of (list of items, next_point_id)
items, next_point_id = item_crud.scroll(
    qdrant_client,
    query=QueryBase(
        filters={"name": {"$eq": "Test Item"}},
        limit=10,
        offset=None  # First page has None offset, subsequent pages use the returned next_point_id
    )
)
# Using the next_point_id for the next page
if next_point_id:
    next_page_items, next_point_id = item_crud.scroll(
        qdrant_client,
        query=QueryBase(
            filters={"name": {"$eq": "Test Item"}},
            limit=10,
            offset=next_point_id  # Use the point_id as string offset
        )
    )

# Asynchronous operations
async_qdrant_client = AsyncQdrantClient("localhost", port=6333)

# Create an item asynchronously
result = await item_crud.acreate(async_qdrant_client, vector, item, "unique_id_4")

# Read items asynchronously
item = await item_crud.afirst_by_id(async_qdrant_client, _id="unique_id_4")
items = await item_crud.aget_multi_by_ids(async_qdrant_client, _ids=["unique_id_4"])

# Search asynchronously
search_results = await item_crud.asearch(
    async_qdrant_client,
    vector=[0.1, 0.2, 0.3],
    vector_key="vector",
    score_threshold=0.75
)

# Update items asynchronously
await item_crud.aupdate_payload(async_qdrant_client, _id="unique_id_4", payload=item)
await item_crud.aoverwrite_payload(async_qdrant_client, _id="unique_id_4", payload=item)
await item_crud.aupdate_vector(async_qdrant_client, _id="unique_id_4", vector=vector)

# Delete items asynchronously
await item_crud.adelete(async_qdrant_client, _id="unique_id_4")
await item_crud.adelete_multi(async_qdrant_client, query=QueryBase(filters={"name": {"$eq": "Test Item"}}))

# Scroll items asynchronously
items, next_point_id = await item_crud.ascroll(
    async_qdrant_client,
    query=QueryBase(
        filters={"name": {"$eq": "Test Item"}},
        limit=10
    )
)

Database Session Management

The session module provides tools for managing database connections in API services. It handles the creation and management of database sessions, which should be handled at the API endpoint level.

from keble_db import DbSettingsABC
from pydantic_settings import BaseSettings
from fastapi import FastAPI, Depends

# 1. Define settings class implementing the DbSettingsABC interface
class Settings(BaseSettings, DbSettingsABC):
    # Implement required settings for database connections
    mongodb_uri: str
    sql_uri: str
    async_sql_uri: str  # For async SQL connections
    redis_url: str
    # ... other settings
    
# 2. Initialize settings
settings = Settings()

# 3. Initialize database and dependency objects
from keble_db.session import Db
from keble_db.deps.api import ApiDbDeps

# Initialize the core database handler
db = Db(settings)

# Example of db usage
mongo_client = db.get_mongo()
redis_client = db.get_redis(namespace="my-app")
sql_session = db.get_sql_write_client()

# Async SQL usage
async_sql_session = db.get_async_sql_write_client()  # Returns SQLModelAsyncSession
# Use async_sql_session in async methods with await

# Initialize the API dependencies handler
api_db_deps = ApiDbDeps(db)

# 4. Use in FastAPI application
app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: str, mongo_client = Depends(api_db_deps.get_amongo)):
    # Use the mongo client to access the database
    user = await user_crud.afirst_by_id(mongo_client, _id=user_id)
    return user

@app.get("/products")
def get_products(sql_session = Depends(api_db_deps.get_read_sql)):
    # Use SQL session (synchronous)
    products = product_crud.get_multi(sql_session, query=QueryBase(limit=10))
    return products

@app.get("/products-async")
async def get_products_async(async_sql_session = Depends(api_db_deps.get_async_read_sql)):
    # Use async SQL session (asynchronous)
    products = await product_crud.aget_multi(async_sql_session, query=QueryBase(limit=10))
    return products

@app.get("/cache")
def get_cache(redis_client = Depends(api_db_deps.get_redis)):
    # Use Redis client
    cached_data = redis_client.get("some_key")
    return {"data": cached_data}

@app.get("/cache-async")
async def get_cache_async(redis_client = Depends(api_db_deps.get_aredis)):
    # Use async Redis client
    cached_data = await redis_client.get("some_key")
    return {"data": cached_data}

@app.get("/namespaced-cache")
def get_namespaced_cache(redis_client = Depends(lambda: api_db_deps.get_extended_redis(namespace="my-namespace"))):
    # Use extended Redis client with namespace
    cached_data = redis_client.get("some_key")  # Will be prefixed with "my-namespace:"
    return {"data": cached_data}

@app.get("/namespaced-cache-async")
async def get_namespaced_cache_async(redis_client = Depends(lambda: api_db_deps.get_extended_aredis(namespace="my-namespace"))):
    # Use extended async Redis client with namespace
    cached_data = await redis_client.get("some_key")  # Will be prefixed with "my-namespace:"
    return {"data": cached_data}

FastAPI Dependency Injection

The ApiDbDeps class provides dependency injection for FastAPI applications, handling the lifecycle of database connections:

Dependency Method Return Type Description
get_redis() Redis | None Synchronous Redis client with proper connection lifecycle
get_aredis() AsyncRedis Asynchronous Redis client
get_extended_redis(namespace=None) ExtendedRedis | None Extended Redis client with namespace support
get_extended_aredis(namespace=None) ExtendedAsyncRedis | None Extended asynchronous Redis client with namespace support
get_mongo() MongoClient | None Synchronous MongoDB client
get_amongo() AsyncIOMotorClient Asynchronous MongoDB client
get_write_sql() Session | None Synchronous SQL session for write operations
get_read_sql() Session | None Synchronous SQL session for read operations
get_async_write_sql() SQLModelAsyncSession | None Asynchronous SQL session for write operations
get_async_read_sql() SQLModelAsyncSession | None Asynchronous SQL session for read operations
get_qdrant() QdrantClient | None Synchronous Qdrant client
get_aqdrant() AsyncQdrantClient | None Asynchronous Qdrant client

Each dependency method creates a new client instance and manages its lifecycle, ensuring connections are properly closed after the API request completes.

Extended Redis Functionality

The extended Redis clients (ExtendedRedis and ExtendedAsyncRedis) provide additional functionality:

  • Namespace support: Automatically prefixes all keys with the namespace
  • Batch operations: Methods for efficient batch retrieval and storage
  • Type conversion: Automatic serialization/deserialization of values

Example with namespace:

# Get extended Redis client
extended_redis = api_db_deps.get_extended_redis(namespace="my-app")

# Key will be stored as "my-app:user:123"
extended_redis.set("user:123", json.dumps({"name": "John"}))

# Retrieves from "my-app:user:123"
value = extended_redis.get("user:123")

Async Database Connections

All async connections in keble-db follow the same pattern - the methods are prefixed with 'a' to indicate they're async:

Synchronous Method Async Method Description
get_mongo() get_amongo() MongoDB client
get_redis() get_aredis() Redis client
get_qdrant_client() get_aqdrant_client() Qdrant client
get_sql_write_client() get_async_sql_write_client() SQL write client
get_sql_read_client() get_async_sql_read_client() SQL read client

For FastAPI dependency injection, use the corresponding methods from ApiDbDeps:

Synchronous Dependency Async Dependency Description
get_mongo() get_amongo() MongoDB client
get_redis() get_aredis() Redis client
get_qdrant() get_aqdrant() Qdrant client
get_write_sql() get_async_write_sql() SQL write client
get_read_sql() get_async_read_sql() SQL read client

Async SQL Configuration

For async SQL support, you need to configure your database settings:

PostgreSQL with Psycopg v3 (Recommended)

Psycopg v3 is the recommended PostgreSQL driver for both synchronous and asynchronous operations:

# Install Psycopg v3 with binary and connection pooling support
poetry add 'psycopg[binary,pool]>=3.1.10' psycopg-pool greenlet

# If upgrading from psycopg2, remove it first
poetry remove psycopg2 psycopg2-binary

Configure your connection strings:

# In your settings class
class Settings(BaseSettings, DbSettingsABC):
    # Synchronous SQL connection with Psycopg v3
    sql_uri: str = "postgresql+psycopg://user:password@localhost:5432/dbname"
    
    # Async SQL connection with Psycopg v3 (same format as sync!)
    async_sql_uri: str = "postgresql+psycopg://user:password@localhost:5432/dbname"
    
    # You can also specify separate read connections
    sql_read_uri: Optional[str] = None  # If None, sql_uri is used for both
    async_sql_read_uri: Optional[str] = None  # If None, async_sql_uri is used for both
    
    # Connection arguments (optional)
    @property
    def sql_connect_args(self) -> Dict[str, Any]:
        return {
            "application_name": "my_app",
            "connect_timeout": 10,
            # Other Psycopg3 connection parameters
        }

Important notes on using Psycopg v3:

  • Use postgresql+psycopg for both sync and async connections (not the older postgresql+asyncpg)
  • Psycopg v3 supports both synchronous and asynchronous operations with the same driver
  • The async functionality is handled automatically by SQLAlchemy
  • Always include the port (:5432) in your connection strings for better reliability
  • Make sure to have greenlet installed: poetry add greenlet
Other Database Drivers

For other databases, install the appropriate async driver:

  • For PostgreSQL with asyncpg: poetry add asyncpg and use postgresql+asyncpg://user:password@localhost/dbname
  • For SQLite: poetry add aiosqlite and use sqlite+aiosqlite:///database.db
  • For MySQL: poetry add aiomysql and use mysql+aiomysql://user:password@localhost/dbname

Always properly close async SQL sessions:

try:
    # Use async_session
    result = await crud.afirst(async_session, query=query)
finally:
    await db.try_close_async(async_session)

Extended Redis Support

The package provides extended Redis functionality with namespace management and batch operations.

from keble_db.wrapper import ExtendedRedis
from redis import Redis

redis_client = Redis(host="localhost", port=6379)
# The ExtendedRedis act like redis.asyncio.Redis,
# which it DOES NOT have an "a" in front of the api,
# but all apis are awaitable
extended_redis = ExtendedRedis(redis_client, namespace="my-app")

# Set with namespace
await extended_redis.set("user:1", "data")  # Actual key: "my-app:user:1"

# Get with namespace
data = await extended_redis.get("user:1")

# Delete all keys in a namespace
await extended_redis.delete_keys_by_pattern("user:*")  # Deletes all "my-app:user:*" keys

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

keble_db-0.1.33-py3-none-any.whl (31.9 kB view details)

Uploaded Python 3

File details

Details for the file keble_db-0.1.33-py3-none-any.whl.

File metadata

  • Download URL: keble_db-0.1.33-py3-none-any.whl
  • Upload date:
  • Size: 31.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for keble_db-0.1.33-py3-none-any.whl
Algorithm Hash digest
SHA256 c2e4178a0729efcba678613bfff36706950a20ba7dd5f29ecfa218c69eb605a3
MD5 09c67fe1683bcd8ab2371b9f6768bf49
BLAKE2b-256 abd2e517394c047056d22e3874abddc30c77e2612a0b1356dd611518fc955708

See more details on using hashes here.

Release history Release notifications | RSS feed

1.10.2

1 file

1.10.1

2 files

1.10.0.post1

1 file

1.10.0

2 files

1.9.0

2 files

1.8.1

2 files

1.8.0

2 files

1.6.1

2 files

1.6.0

2 files

1.5.0

2 files

1.4.1

2 files

1.4.0

2 files

1.3.2

2 files

1.3.1

2 files

1.3.0

2 files

1.1.2

2 files

1.1.1

2 files

1.1.0

2 files

1.0.0

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

This release

0.1.33 This release

1 file

0.1.31

2 files

0.1.30

2 files

0.1.27

2 files

0.1.26

2 files

0.1.25

2 files

0.1.24

2 files

0.1.23

2 files

0.1.21

2 files

0.1.20

2 files

0.1.19

2 files

0.1.18

2 files

0.1.17

2 files

0.1.16

2 files

0.1.15

2 files

0.1.14

2 files

0.1.13

2 files

0.1.12

2 files

0.1.11

2 files

0.1.10

2 files

0.1.9

2 files

0.1.8

2 files

0.1.7

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

0.0.46

2 files

0.0.45

2 files

0.0.44

2 files

0.0.43

2 files

0.0.42

2 files

0.0.41

2 files

0.0.40

2 files

0.0.39

2 files

0.0.38

2 files

0.0.37

2 files

0.0.36

2 files

0.0.35

2 files

0.0.34

2 files

0.0.33

2 files

0.0.32

2 files

0.0.31

2 files

0.0.30

2 files

0.0.29

2 files

0.0.28

2 files

0.0.27

2 files

0.0.26

2 files

0.0.25

2 files

0.0.24

2 files

0.0.23

2 files

0.0.22

2 files

0.0.21

2 files

0.0.20

2 files

0.0.18

2 files

0.0.17

2 files

0.0.16

2 files

0.0.15

2 files

0.0.14

2 files

0.0.13

2 files

0.0.12

2 files

0.0.11

2 files

0.0.10

2 files

0.0.9

2 files

0.0.8

2 files

0.0.7

2 files

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page