# Peewee

> [Applies to: **/*.py] Definitive guidelines for writing robust, maintainable, and performant Peewee ORM code in Python, covering data modeling, querying, and application structure.

- Skill: `tryboy869/peewee` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add tryboy869/peewee`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tryboy869/peewee/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Tryboy869 (https://skillmd.com/u/tryboy869)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/tryboy869/peewee

---


# peewee Best Practices

Peewee is a lightweight, expressive ORM for Python. These guidelines ensure your Peewee code is clean, efficient, and aligns with modern best practices as of 2025.

---

## 1. Code Organization and Structure

Always establish a clear, DRY (Don't Repeat Yourself) structure for your Peewee models and database connection.

### 1.1 Define a `BaseModel` for Database Connection

Centralize your database instance within a `BaseModel` to avoid repetitive declarations and ensure all models use the same connection.

❌ BAD: Direct database assignment in every model
```python
from peewee import *

db = SqliteDatabase('app.db')

class User(Model):
    username = CharField()
    class Meta:
        database = db # Repeated for every model
```

✅ GOOD: Inherit from a `BaseModel`
```python
from peewee import *

db = SqliteDatabase('app.db')

class BaseModel(Model):
    class Meta:
        database = db

class User(BaseModel):
    username = CharField(unique=True)
```

### 1.2 Model Naming Convention

Name your model classes in the singular form. Peewee automatically pluralizes table names (e.g., `User` -> `user`).

❌ BAD: Plural model name
```python
class Users(BaseModel): # Table might be 'userss' or 'users' depending on backend
    name = CharField()
```

✅ GOOD: Singular model name
```python
class User(BaseModel): # Table will be 'user'
    name = CharField()
```

### 1.3 Explicit Imports for Model Definitions

For model definition files, use `from peewee import *` for convenience. This is a common and accepted pattern within the Peewee ecosystem. For other modules or specific utility functions, prefer explicit imports.

❌ BAD: Overly verbose imports for model definitions
```python
from peewee import Model, CharField, IntegerField, SqliteDatabase, DateTimeField # ... and so on
```

✅ GOOD: Concise model definition imports
```python
from peewee import * # For model definition files (e.g., models.py)
import datetime

# In other files (e.g., main.py, services.py), for specific functions/classes:
from peewee import SqliteDatabase, DoesNotExist
```

## 2. Data Modeling

Define robust models with clear field types, constraints, and relationships.

### 2.1 Explicit Field Constraints and Defaults

Always define fields with appropriate Python types, `unique=True` where necessary, and sensible `default` values. Peewee uses `id` as an auto-incrementing primary key by default if none is specified.

```python
import datetime
from peewee import *

db = SqliteDatabase('app.db')

class BaseModel(Model):
    class Meta:
        database = db

class User(BaseModel):
    username = CharField(unique=True, index=True) # Ensure uniqueness and fast lookups
    email = CharField(unique=True, null=False)
    created_at = DateTimeField(default=datetime.datetime.now) # Use datetime.datetime.now, not datetime.now()
    is_active = BooleanField(default=True)

class Post(BaseModel):
    user = ForeignKeyField(User, backref='posts', on_delete='CASCADE') # Define relationship and cascade delete
    title = CharField(max_length=255, null=False)
    content = TextField(null=False)
    published_at = DateTimeField(null=True) # Can be null until published
    views = IntegerField(default=0)
```

### 2.2 Foreign Key Relationships with `backref` and `on_delete`

Always define `ForeignKeyField` with a `backref` to easily access related objects from the parent model. Specify `on_delete` for database-level referential integrity.

❌ BAD: Missing `backref` and `on_delete`
```python
class Comment(BaseModel):
    post = ForeignKeyField(Post) # Cannot easily get comments from a Post instance
    text = TextField()
```

✅ GOOD: With `backref` and `on_delete`
```python
class Comment(BaseModel):
    post = ForeignKeyField(Post, backref='comments', on_delete='CASCADE') # `post.comments` will work
    author = ForeignKeyField(User, backref='comments', on_delete='SET NULL', null=True) # Example with SET NULL
    text = TextField()
```

## 3. Database Connection and Transactions

Manage your database connections and ensure data integrity with transactions.

### 3.1 Connect and Create Tables Once at Application Startup

Establish the database connection and create tables only once when your application initializes. Close the connection if not using persistent connections or a framework that manages it.

❌ BAD: Connecting/creating tables in every script or function
```python
# In script A
db.connect()
db.create_tables([User, Post])

# In script B
db.connect() # Redundant, potential for errors
```

✅ GOOD: Centralized application startup
```python
# app_init.py (or main entry point)
from peewee import *
import datetime

db = SqliteDatabase('app.db')

class BaseModel(Model):
    class Meta:
        database = db

class User(BaseModel):
    username = CharField(unique=True)

class Post(BaseModel):
    user = ForeignKeyField(User, backref='posts')
    title = CharField()

def initialize_db():
    db.connect()
    db.create_tables([User, Post]) # Pass all models here
    db.close() # Close immediately if not using persistent connections

if __name__ == '__main__':
    initialize_db()
    print("Database initialized.")
    # Now, in your application logic, you'd open/close connections per request/operation
    # or use a connection pool.
```

### 3.2 Use Transactions for Atomic Operations

Wrap any multi-step database modifications in a transaction (`with db.atomic():`) to guarantee atomicity and improve performance.

❌ BAD: Multiple, non-atomic operations
```python
user = User.create(username='alice')
Post.create(user=user, title='First post') # If this fails, user is still created, leading to inconsistency
```

✅ GOOD: Atomic transaction
```python
try:
    with db.atomic():
        user = User.create(username='bob')
        Post.create(user=user, title='Second post') # Both succeed or both fail
except IntegrityError:
    print("Failed to create user and post due to integrity error.")
```

## 4. Query Optimization

Write efficient queries to minimize database load and improve response times.

### 4.1 Avoid N+1 Query Problem

Use `join()` or `prefetch()` to load related objects in a single query, preventing the N+1 problem.

❌ BAD: N+1 queries
```python
# Fetching posts, then iterating to get each user's username
for post in Post.select():
    print(f"{post.title} by {post.user.username}") # Each post.user.username triggers a new query
```

✅ GOOD: Single query with `join()`
```python
for post in Post.select().join(User):
    print(f"{post.title} by {post.user.username}") # User data loaded with post
```

### 4.2 Use Bulk Operations for Inserts

For inserting many records, use `bulk_create()` for significant performance gains by reducing the number of database round trips.

❌ BAD: Individual `create()` calls in a loop
```python
users_data = [{'username': f'user_{i}'} for i in range(100)]
for data in users_data:
    User.create(**data) # 100 separate INSERT statements
```

✅ GOOD: Single `bulk_create()` call
```python
users_data = [{'username': f'user_{i}'} for i in range(100)]
User.bulk_create([User(**data) for data in users_data]) # Single INSERT statement (or few, depending on batch size)
```

### 4.3 Atomic Updates

Perform updates atomically using `Model.update().where().execute()` to prevent race conditions and improve efficiency.

❌ BAD: Fetch-modify-save (prone to race conditions)
```python
post = Post.get(Post.id == 1)
post.views += 1
post.save() # Another process could have incremented views between get and save
```

✅ GOOD: Atomic update
```python
Post.update(views=Post.views + 1).where(Post.id == 1).execute() # Single, atomic operation
```

### 4.4 Pagination

Use the built-in `paginate()` method for efficient result set slicing, especially for large datasets.

```python
# Get page 2, with 20 items per page, ordered by username
users_page_2 = User.select().order_by(User.username).paginate(2, 20)
```

## 5. Security Best Practices

Peewee handles SQL injection prevention by default through parameterized queries. Focus on proper input validation in your application logic.

### 5.1 Trust Peewee's Query Builder

Avoid constructing SQL strings manually. Always use Peewee's ORM methods and expressions, which automatically parameterize inputs.

❌ BAD: Manual string formatting (vulnerable to injection)
```python
# DO NOT DO THIS. This is a severe security vulnerability.
user_input = "'; DROP TABLE users; --"
query = f"SELECT * FROM user WHERE username = '{user_input

