Set up PostgreSQL database with standardized schema.sql pattern. Use when starting a new project that needs PostgreSQL, setting up database schema, or creating setup scripts for postgres.
/plugin marketplace add jmazzahacks/byteforge-claude-skills/plugin install byteforge-skills@byteforge-claude-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill helps you set up a PostgreSQL database following a standardized pattern with proper separation of schema and setup scripts.
Use this skill when:
database/schema.sql - SQL schema with table definitionsdev_scripts/setup_database.py - Python setup scriptIMPORTANT: Before creating files, ask the user these questions:
"What is your project name?" (e.g., "arcana", "trading-bot", "myapp")
{project_name} (e.g., arcana){project_name} (e.g., arcana){PROJECT_NAME}_PG_PASSWORD (e.g., ARCANA_PG_PASSWORD)"What tables do you need in your schema?" (optional - can create skeleton if unknown)
Create these directories if they don't exist:
{project_root}/
├── database/
└── dev_scripts/
Create database/schema.sql with:
CREATE TABLE IF NOT EXISTS for idempotencyUUID for primary keys with gen_random_uuid() as defaultBIGINT (Unix timestamps) for all date/time fields (NOT TIMESTAMP, NOT TIMESTAMPTZ)ON DELETE CASCADE or ON DELETE SET NULLTEXT instead of VARCHAR (PostgreSQL best practice)COMMENT ON COLUMN for documentation-- Enable required extensions
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Example table
CREATE TABLE IF NOT EXISTS example_table (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
created_at BIGINT NOT NULL DEFAULT extract(epoch from now())::bigint,
updated_at BIGINT
);
-- Add indexes
CREATE INDEX IF NOT EXISTS idx_example_created_at ON example_table(created_at);
-- Add comments
COMMENT ON TABLE example_table IS 'Description of what this table stores';
COMMENT ON COLUMN example_table.created_at IS 'Unix timestamp of creation';
If user provides specific tables, create schema accordingly. Otherwise, create a skeleton with one example table.
Create dev_scripts/setup_database.py using this template, substituting project-specific values:
#!/usr/bin/env python
"""
Database setup script for {PROJECT_NAME}
Creates the {project_name} database and user with proper permissions, then applies database/schema.sql
Usage:
python setup_database.py --pg-password <postgres_password>
python setup_database.py --pg-password <postgres_password> --pg-user <superuser>
python setup_database.py --pg-password <postgres_password> --test-db
"""
import os
import sys
import argparse
import psycopg2
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
def main():
"""Setup {project_name} database and user"""
parser = argparse.ArgumentParser(description='Setup {PROJECT_NAME} database')
parser.add_argument('--pg-password', required=True,
help='PostgreSQL superuser password (required)')
parser.add_argument('--pg-user', default='postgres',
help='PostgreSQL superuser name (default: postgres)')
parser.add_argument('--test-db', action='store_true',
help='Create {project_name}_test database instead of main {project_name} database')
args = parser.parse_args()
pg_host = os.environ.get('POSTGRES_HOST', 'localhost')
pg_port = os.environ.get('POSTGRES_PORT', '5432')
pg_user = args.pg_user
pg_password = args.pg_password
if args.test_db:
{project_name}_db = '{project_name}_test'
print("Setting up TEST database '{project_name}_test'...")
else:
{project_name}_db = os.environ.get('{PROJECT_NAME}_PG_DB', '{project_name}')
{project_name}_user = os.environ.get('{PROJECT_NAME}_PG_USER', '{project_name}')
{project_name}_password = os.environ.get('{PROJECT_NAME}_PG_PASSWORD', None)
if {project_name}_password is None:
print("Error: {PROJECT_NAME}_PG_PASSWORD environment variable is required")
sys.exit(1)
print(f"Setting up database '{{project_name}_db}' and user '{{project_name}_user}'...")
print(f"Connecting to PostgreSQL at {pg_host}:{pg_port} as {pg_user}")
try:
conn = psycopg2.connect(
host=pg_host,
port=pg_port,
database='postgres',
user=pg_user,
password=pg_password
)
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
with conn.cursor() as cursor:
cursor.execute("SELECT 1 FROM pg_roles WHERE rolname = %s", ({project_name}_user,))
if not cursor.fetchone():
print(f"Creating user '{{project_name}_user}'...")
cursor.execute(f"CREATE USER {project_name}_user WITH PASSWORD %s", ({project_name}_password,))
print(f"✓ User '{{project_name}_user}' created")
else:
print(f"✓ User '{{project_name}_user}' already exists")
cursor.execute("SELECT 1 FROM pg_database WHERE datname = %s", ({project_name}_db,))
if not cursor.fetchone():
print(f"Creating database '{{project_name}_db}'...")
cursor.execute(f"CREATE DATABASE {{project_name}_db} OWNER {{project_name}_user}")
print(f"✓ Database '{{project_name}_db}' created")
else:
print(f"✓ Database '{{project_name}_db}' already exists")
print("Setting permissions...")
cursor.execute(f"GRANT ALL PRIVILEGES ON DATABASE {{project_name}_db} TO {{project_name}_user}")
print(f"✓ Granted all privileges on database '{{project_name}_db}' to user '{{project_name}_user}'")
conn.close()
print(f"\\nConnecting as '{{project_name}_user}' to apply schema...")
{project_name}_conn = psycopg2.connect(
host=pg_host,
port=pg_port,
database={project_name}_db,
user={project_name}_user,
password={project_name}_password
)
{project_name}_conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
schema_path = os.path.join(repo_root, 'database', 'schema.sql')
if not os.path.exists(schema_path):
print(f"Error: schema file not found at {schema_path}")
sys.exit(1)
with open(schema_path, 'r', encoding='utf-8') as f:
schema_sql = f.read()
with {project_name}_conn.cursor() as cursor:
print("Ensuring required extensions...")
cursor.execute("CREATE EXTENSION IF NOT EXISTS pgcrypto")
print(f"Applying schema from {schema_path}...")
cursor.execute(schema_sql)
print("✓ Schema applied")
{project_name}_conn.close()
print("✓ Database setup complete")
print(f"Database: {{project_name}_db}")
print(f"User: {{project_name}_user}")
print(f"Host: {pg_host}:{pg_port}")
except psycopg2.Error as e:
print(f"Error: {e}")
sys.exit(1)
except Exception as e:
print(f"Unexpected error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
CRITICAL: Replace ALL instances of:
{PROJECT_NAME} → uppercase project name (e.g., ARCANA, MYAPP){project_name} → lowercase project name (e.g., arcana, myapp)Add a section to the project's README.md (or create SETUP.md) documenting:
Required:
--pg-password - PostgreSQL superuser passwordOptional:
--pg-user - PostgreSQL superuser name (default: postgres)--test-db - Create test database instead of main databasePostgreSQL connection (optional):
POSTGRES_HOST - PostgreSQL host (default: localhost)POSTGRES_PORT - PostgreSQL port (default: 5432)Project-specific:
{PROJECT_NAME}_PG_DB - Database name (default: {project_name}){PROJECT_NAME}_PG_USER - Application user (default: {project_name}){PROJECT_NAME}_PG_PASSWORD - Application user password (REQUIRED)# Set required environment variables
export {PROJECT_NAME}_PG_PASSWORD="your_app_password"
# Run setup script (pass postgres superuser password as argument)
python dev_scripts/setup_database.py --pg-password "your_postgres_password"
# With custom superuser name
python dev_scripts/setup_database.py --pg-password "your_postgres_password" --pg-user "admin"
# For test database
python dev_scripts/setup_database.py --pg-password "your_postgres_password" --test-db
Run:
chmod +x dev_scripts/setup_database.py
If the project needs a database driver/connection manager, create one following this pattern:
src/{project_name}/driver/database.pyKey patterns to follow:
Connection Pooling: Use ThreadedConnectionPool from psycopg2
from psycopg2.pool import ThreadedConnectionPool
self.pool = ThreadedConnectionPool(
min_conn, # e.g., 2
max_conn, # e.g., 10
host=db_host,
database=db_name,
user=db_user,
password=db_passwd
)
Context Managers: Provide context managers for connections and cursors
@contextmanager
def get_cursor(self, commit=True, cursor_factory=None):
"""Context manager for database cursors with automatic commit/rollback"""
with self._get_connection() as conn:
cursor = conn.cursor(cursor_factory=cursor_factory)
try:
yield cursor
if commit:
conn.commit()
except Exception:
conn.rollback()
raise
finally:
cursor.close()
Always Use RealDictCursor for Loading Data: When reading from database, use RealDictCursor
from psycopg2.extras import RealDictCursor
with self.get_cursor(commit=False, cursor_factory=RealDictCursor) as cursor:
cursor.execute("SELECT * FROM table WHERE id = %s", (id,))
result = cursor.fetchone()
return Model.from_dict(dict(result))
Unix Timestamps Everywhere: Convert database timestamps to/from unix timestamps
# When saving to DB - store as BIGINT
created_at = int(time.time())
# When loading from DB - already BIGINT, use as-is
# In models, store as int (unix timestamp)
# Only convert to datetime for display/formatting purposes
Proper Cleanup: Ensure pool is closed on destruction
def close(self):
if self.pool and not self.pool.closed:
self.pool.closeall()
def __del__(self):
if hasattr(self, 'pool'):
self.close()
class {ProjectName}DB:
def __init__(self, db_host, db_name, db_user, db_passwd, min_conn=2, max_conn=10):
self.pool = ThreadedConnectionPool(...)
@contextmanager
def get_cursor(self, commit=True, cursor_factory=None):
# Context manager for cursors
pass
def load_item_by_id(self, item_id: str) -> Item:
with self.get_cursor(commit=False, cursor_factory=RealDictCursor) as cursor:
cursor.execute("SELECT * FROM items WHERE id = %s", (item_id,))
result = cursor.fetchone()
if not result:
raise Exception(f"Item {item_id} not found")
return Item.from_dict(dict(result))
def save_item(self, item: Item) -> str:
with self.get_cursor() as cursor:
cursor.execute(
"INSERT INTO items (name, created_at) VALUES (%s, %s) RETURNING id",
(item.name, int(time.time()))
)
return str(cursor.fetchone()[0])
This pattern follows these principles:
User: "Set up postgres database for my project" Claude: "What is your project name?" User: "trading-bot" Claude:
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.