Skip to main content
rule

peewee — Cursor rule: what it does, how to install

Guides Cursor on writing Peewee ORM code in Python: sets up a central BaseModel, configures foreign keys, and prevents N+1 query loops.

Install into Cursor · Manually: put the files in place All methods and platforms ↓
.cursor/rules/peewee.mdc
Open on GitHub

What peewee does

This rule sets conventions for writing Peewee ORM code across **/*.py files. It requires centralizing database connections inside a single BaseModel, keeping model names singular, and passing callables like datetime.datetime.now without parentheses into default field values. Foreign keys must explicitly define backref and on_delete rules.

Database initialization happens strictly once at startup using create_tables. For write operations spanning multiple records, the agent wraps logic into with db.atomic(): blocks. To prevent N+1 queries during data fetching, the instructions enforce join() or prefetch() instead of loading related instances inside loops.

The rule strictly targets Peewee workflows. Loading it into projects based on SQLAlchemy or Django ORM will lead to conflicting code patterns.

What it helps with

  • configure models around a shared BaseModel connection
  • define foreign keys with explicit backref and on_delete behaviors
  • eliminate N+1 query overhead using join() and prefetch()
  • secure multi-step database writes within db.atomic() transactions

Example prompts for the agent

  • Create User and Post models with cascading deletes and a backref
  • Optimize this post query to fetch authors without N+1 lookups
  • Write an atomic transaction that registers a user and saves their profile

How to install peewee

Manually: put the files in place

.cursor/rules/peewee.mdc

Frontmatter: description, globs, alwaysApply

Step by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules

Questions about peewee

How to install peewee in Cursor?

Save the rule file to .cursor/rules/peewee.mdc in the root of your project. Cursor loads it whenever editing matching **/*.py files.

What does the peewee rule do?

It tells Cursor how to write clean Peewee ORM code in Python, enforcing centralized model inheritance, singular naming, atomic transactions, and bulk writes.

How does this rule handle foreign key relationships?

It mandates adding backref to every ForeignKeyField for reverse access and setting an explicit on_delete policy like CASCADE or SET NULL.

Source text

peewee.mdc · CC0-1.0
---
description: Definitive guidelines for writing robust, maintainable, and performant Peewee ORM code in Python, covering data modeling, querying, and application structure.
globs: **/*.py
---
# 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

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Similar IDE rules

All IDE rules for Backend and APIs →

From the same set240

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor