Best Practices Guide

Essential best practices for writing maintainable and efficient Moltres code.

Code Organization

1. Separate Connection Logic

# ✅ Good: Centralized connection management
# config.py
from moltres import connect

def get_database():
    return connect("postgresql://user:pass@localhost/mydb")

# main.py
from config import get_database

db = get_database()
df = db.table("users").select()

2. Use Type Hints

from moltres import connect
from typing import List, Dict
from moltres import Database
from moltres.table.schema import column
from moltres.io.records import Records

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()

# Insert sample data
Records.from_list([
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"},
], database=db).insert_into("users")

def get_active_users(db: Database) -> List[Dict[str, object]]:
    df = db.table("users").select().where(col("active") == 1)
    return df.collect()

3. Organize by Functionality

from moltres import connect
from moltres.table.schema import column
from moltres.io.records import Records

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()

# Insert sample data
Records.from_list([
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"},
], database=db).insert_into("users")
# ✅ Good: Group related operations
class UserService:
    def __init__(self, db: Database):
        self.db = db
    
    def get_active_users(self):
        return self.db.table("users").select().where(col("active") == 1).collect()
    
    def update_user_status(self, user_id: int, status: str):
        return self.db.update("users", where=col("id") == user_id, set={"status": status})

Query Writing

1. Use Descriptive Variable Names

from moltres import connect
from moltres.table.schema import column
from moltres.io.records import Records

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()

# Insert sample data
Records.from_list([
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"},
], database=db).insert_into("users")
# ✅ Good
active_users_query = db.table("users").select().where(col("active") == 1)
high_value_orders = db.table("orders").select().where(col("amount") > 1000)

# ❌ Bad
q1 = db.table("users").select().where(col("active") == 1)
q2 = db.table("orders").select().where(col("amount") > 1000)

2. Build Queries Incrementally

from moltres import connect
from moltres.table.schema import column
from moltres.io.records import Records

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()

# Insert sample data
Records.from_list([
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"},
], database=db).insert_into("users")
# ✅ Good: Clear, readable query building
base_query = db.table("users").select()
active_users = base_query.where(col("active") == 1)
adult_active_users = active_users.where(col("age") >= 18)
results = adult_active_users.collect()

3. Use Column Aliases

from moltres import connect
from moltres.table.schema import column

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()
# ✅ Good: Clear column names
df = db.table("orders").select(
    col("id").alias("order_id"),
    col("amount").alias("order_amount"),
    (col("amount") * 1.1).alias("amount_with_tax")
)

Error Handling

1. Always Handle Errors

from moltres import connect
from moltres.table.schema import column
from moltres.io.records import Records

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")
# ✅ Good
def safe_collect(df):
    try:
        results = df.collect()
        return results
    except ExecutionError as e:
        logger.error(f"Query failed: {e}")
        return []

2. Validate Inputs

from moltres import connect
from moltres.table.schema import column
from moltres.io.records import Records

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()

# Insert sample data
Records.from_list([
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"},
], database=db).insert_into("users")
# ✅ Good
def query_users(db: Database, min_age: int):
    if not isinstance(min_age, int):
        raise ValueError("min_age must be an integer")
    if min_age < 0:
        raise ValueError("min_age must be non-negative")
    
    return db.table("users").select().where(col("age") >= min_age).collect()

3. Use Context Managers

# ✅ Good: Automatic connection cleanup
with connect("postgresql://...") as db:
    results = db.table("users").select().collect()

Performance

1. Filter Early

from moltres import connect
from moltres.table.schema import column

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()
# ✅ Good: Filter before expensive operations
df = (
    db.table("orders")
    .select()
    .where(col("date") >= "2024-01-01")  # Filter early
    .join(db.table("users").select(), on=[...])
)

# ❌ Bad: Filter after join
df = (
    db.table("orders")
    .select()
    .join(db.table("users").select(), on=[...])
    .where(col("date") >= "2024-01-01")  # Filter late
)

2. Select Only Needed Columns

from moltres import connect
from moltres.table.schema import column

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()
# ✅ Good
df = db.table("users").select("id", "name", "email")

# ❌ Bad
df = db.table("users").select()  # Selects all columns

3. Use Indexes

from moltres import connect
from moltres.table.schema import column
from moltres.io.records import Records

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")
# ✅ Good: Create indexes on frequently queried columns
db.create_index("idx_user_email", "users", "email").collect()
db.create_index("idx_order_date", "orders", "date").collect()

4. Avoid N+1 Queries

from moltres import connect
from moltres.table.schema import column
from moltres.io.records import Records

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()

# Insert sample data
Records.from_list([
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"},
], database=db).insert_into("users")
# ✅ Good: Single query with IN clause
user_ids = [1, 2, 3, 4, 5]
df = db.table("orders").select().where(col("user_id").isin(user_ids))
results = df.collect()

# ❌ Bad: Multiple queries
results = []
for user_id in user_ids:
    df = db.table("orders").select().where(col("user_id") == user_id)
    results.append(df.collect())

Security

1. Never Use String Formatting for SQL

from moltres import connect
from moltres.table.schema import column
from moltres.io.records import Records

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()

# Insert sample data
Records.from_list([
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"},
], database=db).insert_into("users")
# ❌ Bad: SQL injection risk
user_input = "'; DROP TABLE users; --"
query = f"SELECT * FROM users WHERE name = '{user_input}'"

# ✅ Good: Use parameterized queries
df = db.table("users").select().where(col("name") == user_input)

2. Validate Table/Column Names

from moltres import connect
from moltres.table.schema import column

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()
# ✅ Good: Moltres validates automatically, but be aware
# Moltres will raise ValidationError for invalid identifiers
try:
    df = db.table("users; DROP TABLE users; --").select()
except ValidationError:
    # Invalid table name caught
    pass

3. Use Least Privilege

# ✅ Good: Use database user with minimal required permissions
# Don't use root/admin user for application queries
db = connect("postgresql://app_user:pass@localhost/mydb")

Testing

1. Use Test Databases

# ✅ Good: Separate test database
import pytest

@pytest.fixture
def test_db():
    db = connect("sqlite:///:memory:")  # In-memory for tests
    yield db
    db.close()

2. Test Query Logic Separately

# ✅ Good: Test query building separately from execution
def test_user_query_building():
    db = connect("sqlite:///:memory:")
    df = db.table("users").select().where(col("age") > 25)
    
    # Test plan structure
    assert isinstance(df.plan, Filter)
    assert df.plan.predicate.op == "gt"

3. Use Fixtures for Test Data

# ✅ Good: Reusable test data
@pytest.fixture
def sample_users(db):
    from moltres.io.records import Records
    users = Records.from_list([
        {"id": 1, "name": "Alice", "age": 30},
        {"id": 2, "name": "Bob", "age": 25},
    ], database=db)
    users.insert_into("users")
    return users

Documentation

1. Document Complex Queries

from moltres import connect
from moltres.table.schema import column
from moltres.io.records import Records

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()

# Insert sample data
Records.from_list([
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"},
], database=db).insert_into("users")
# ✅ Good: Document complex logic
def get_user_revenue_by_country(db: Database) -> List[Dict]:
    """
    Calculate total revenue per user grouped by country.
    
    Returns:
        List of dicts with keys: user_id, country, total_revenue
    """
    df = (
        db.table("orders")
        .select()
        .join(db.table("users").select(), on=[col("orders.user_id") == col("users.id")])
        .group_by("user_id", "country")
        .agg(F.sum(col("amount")).alias("total_revenue"))
    )
    return df.collect()

2. Use Type Hints

from moltres import connect
from moltres.table.schema import column
from moltres.io.records import Records

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()

# Insert sample data
Records.from_list([
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"},
], database=db).insert_into("users")
# ✅ Good: Type hints improve code clarity
from typing import List, Dict, Optional
from moltres import Database, col

def find_user(db: Database, user_id: int) -> Optional[Dict[str, object]]:
    results = db.table("users").select().where(col("id") == user_id).collect()
    return results[0] if results else None

Configuration

1. Use Environment Variables

# ✅ Good: Configuration via environment
import os
from moltres import connect

db = connect(
    os.getenv("DATABASE_URL", "sqlite:///:memory:"),
    echo=os.getenv("MOLTRES_ECHO", "false").lower() == "true"
)

2. Centralize Configuration

# ✅ Good: Centralized config
# config.py
class DatabaseConfig:
    DSN = "postgresql://user:pass@localhost/mydb"
    POOL_SIZE = 10
    ECHO = False

# main.py
from config import DatabaseConfig
db = connect(
    DatabaseConfig.DSN,
    pool_size=DatabaseConfig.POOL_SIZE,
    echo=DatabaseConfig.ECHO
)

Code Reusability

1. Create Query Builders

from moltres import connect
from moltres.table.schema import column
from moltres.io.records import Records

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()

# Insert sample data
Records.from_list([
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"},
], database=db).insert_into("users")
# ✅ Good: Reusable query builders
class UserQueries:
    @staticmethod
    def active_users(db: Database):
        return db.table("users").select().where(col("active") == 1)
    
    @staticmethod
    def users_by_country(db: Database, country: str):
        return db.table("users").select().where(col("country") == country)

2. Use Helper Functions

from moltres import connect
from moltres.table.schema import column

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()
# ✅ Good: Helper functions for common patterns
def filter_by_date_range(df, start_date: str, end_date: str):
    return df.where(
        (col("date") >= start_date) & (col("date") <= end_date)
    )

# Usage
df = db.table("orders").select()
df_filtered = filter_by_date_range(df, "2024-01-01", "2024-12-31")

Maintenance

1. Keep Queries Simple

from moltres import connect
from moltres.table.schema import column

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()
# ✅ Good: Simple, readable queries
df = (
    db.table("users")
    .select("id", "name")
    .where(col("active") == 1)
    .order_by("name")
)

# ❌ Bad: Overly complex one-liner
df = db.table("users").select("id", "name").where(col("active") == 1).order_by("name").join(db.table("orders").select(), on=[col("users.id") == col("orders.user_id")]).group_by("users.id").agg(F.sum(col("orders.amount")))

2. Refactor Complex Queries

from moltres import connect
from moltres.table.schema import column

# Use in-memory SQLite for easy setup (no file needed)
db = connect("sqlite:///:memory:")

# Create sample table
db.create_table("users", [
    column("id", "INTEGER", primary_key=True),
    column("name", "TEXT"),
]).collect()
# ✅ Good: Break complex queries into steps
# Step 1: Get active users
active_users = db.table("users").select().where(col("active") == 1)

# Step 2: Join with orders
user_orders = active_users.join(
    db.table("orders").select(),
    on=[col("users.id") == col("orders.user_id")]
)

# Step 3: Aggregate
summary = user_orders.group_by("users.id").agg(
    F.sum(col("orders.amount")).alias("total")
)

Next Steps