| name | peewee |
| description | [Applies to: **/*.py] Definitive guidelines for writing robust, maintainable, and performant Peewee ORM code in Python, covering data modeling, querying, and application structure. |
| source | cursor_mdc |
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
from peewee import *
db = SqliteDatabase('app.db')
class User(Model):
username = CharField()
class Meta:
database = db
✅ GOOD: Inherit from a BaseModel
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
class Users(BaseModel):
name = CharField()
✅ GOOD: Singular model name
class User(BaseModel):
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
from peewee import Model, CharField, IntegerField, SqliteDatabase, DateTimeField
✅ GOOD: Concise model definition imports
from peewee import *
import datetime
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.
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)
email = CharField(unique=True, null=False)
created_at = DateTimeField(default=datetime.datetime.now)
is_active = BooleanField(default=True)
class Post(BaseModel):
user = ForeignKeyField(User, backref='posts', on_delete='CASCADE')
title = CharField(max_length=255, null=False)
content = TextField(null=False)
published_at = DateTimeField(null=True)
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
class Comment(BaseModel):
post = ForeignKeyField(Post)
text = TextField()
✅ GOOD: With backref and on_delete
class Comment(BaseModel):
post = ForeignKeyField(Post, backref='comments', on_delete='CASCADE')
author = ForeignKeyField(User, backref='comments', on_delete='SET NULL', null=True)
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
db.connect()
db.create_tables([User, Post])
db.connect()
✅ GOOD: Centralized application startup
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])
db.close()
if __name__ == '__main__':
initialize_db()
print("Database initialized.")
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
user = User.create(username='alice')
Post.create(user=user, title='First post')
✅ GOOD: Atomic transaction
try:
with db.atomic():
user = User.create(username='bob')
Post.create(user=user, title='Second post')
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
for post in Post.select():
print(f"{post.title} by {post.user.username}")
✅ GOOD: Single query with join()
for post in Post.select().join(User):
print(f"{post.title} by {post.user.username}")
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
users_data = [{'username': f'user_{i}'} for i in range(100)]
for data in users_data:
User.create(**data)
✅ GOOD: Single bulk_create() call
users_data = [{'username': f'user_{i}'} for i in range(100)]
User.bulk_create([User(**data) for data in users_data])
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)
post = Post.get(Post.id == 1)
post.views += 1
post.save()
✅ GOOD: Atomic update
Post.update(views=Post.views + 1).where(Post.id == 1).execute()
4.4 Pagination
Use the built-in paginate() method for efficient result set slicing, especially for large datasets.
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)
user_input = "'; DROP TABLE users; --"
query = f"SELECT * FROM user WHERE username = '{user_input