Skip to main content
rule

pony — Cursor rule for Pony ORM: what it does, how to install

A Cursor rule that sets conventions for Pony ORM code: sessions, queries, typing, layer separation. Useful in a Python project already on Pony ORM.

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

What pony does

Drop pony.mdc into Cursor and the agent gets a written style for Pony ORM work. Six sections cover transaction handling with db_session, generator-based queries instead of hand-rolled SQL, splitting models from services, type hints on entities, leaving caching to IdentityMap, and keeping the schema in step with model definitions. Each point comes as a bad example next to a good one.

The rule fires on **/*.py, so any Python file in the project picks it up. There is nothing to install beyond the file itself. But the advice only lands in a codebase that already uses Pony ORM — on SQLAlchemy or the Django ORM, notes about db_session and select(p for p in Product) have nowhere to apply.

The author suggests keeping entities in models.py and data operations in repositories.py or services.py. That shapes project layout, so it is worth deciding early rather than after the codebase grows.

What it helps with

  • forgetting a commit and leaking a database connection
  • pulling every row into Python and filtering in a loop
  • mixing entity definitions, database setup and business logic in one module
  • leaving entity attributes and function arguments untyped
  • building a custom cache on top of IdentityMap and serving stale objects

Example prompts for the agent

  • Rewrite this function so all database work happens inside db_session.
  • Replace the Python-side filtering with a select query over Product.
  • Split this file into models.py and services.py and add type hints.

How to install pony

Manually: put the files in place

.cursor/rules/pony.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 pony

How do I install the pony rule in Cursor?

Create .cursor/rules/pony.mdc and paste the rule contents into it.

What does the pony rule do?

It gives the Cursor agent conventions for Pony ORM: transactions through db_session, generator queries, type hints, and separated data-access layers.

Which files does the rule apply to?

All Python files matching **/*.py.

Source text

pony.mdc · CC0-1.0
---
description: Definitive guidelines for writing efficient, maintainable, and secure Python applications using Pony ORM.
globs: **/*.py
---
# pony Best Practices

Pony ORM simplifies database interactions with Pythonic syntax and powerful optimizations. Adhere to these rules for robust, high-performance applications.

## 1. Always Use `db_session` for Transaction Management

The `db_session` context manager is non-negotiable. It ensures proper transaction handling, automatic commits, and rollbacks, preventing resource leaks and data inconsistencies.

❌ BAD: Direct database operations without a session.
```python
from pony.orm import *
from decimal import Decimal

db = Database('sqlite', ':memory:')

class Product(db.Entity):
    name = Required(str)
    price = Required(Decimal)

db.generate_mapping(create_tables=True)

# This will fail or lead to unexpected behavior in production
p = Product(name='Laptop', price=Decimal('1200.00'))
db.commit() # Manual commit is error-prone and easily forgotten
```

✅ GOOD: Encapsulate all database work within `db_session`.
```python
from pony.orm import *
from decimal import Decimal

db = Database('sqlite', ':memory:')

class Product(db.Entity):
    name = Required(str)
    price = Required(Decimal)

db.generate_mapping(create_tables=True)

@db_session
def create_product(name: str, price: Decimal) -> Product:
    p = Product(name=name, price=price)
    return p # Auto-committed on exit if no exception

with db_session:
    laptop = create_product('Laptop', Decimal('1200.00'))
    print(f"Created product: {laptop.name}")
```

## 2. Leverage Pythonic Query Syntax for Optimization

Pony translates generator expressions and lambdas into optimized SQL, automatically handling N+1 problems and complex joins. Avoid manual SQL unless absolutely necessary.

❌ BAD: Manual filtering or inefficient data access.
```python
@db_session
def get_expensive_products_bad():
    all_products = list(select(p for p in Product)) # Fetches all products
    expensive_products = [p for p in all_products if p.price > Decimal('1000')] # Filters in Python
    return expensive_products
```

✅ GOOD: Let Pony generate optimized SQL.
```python
@db_session
def get_expensive_products_good() -> list[Product]:
    # Pony translates this directly to SQL: SELECT ... WHERE price > 1000
    return select(p for p in Product if p.price > Decimal('1000'))[:] # Use [:] to fetch all results
```

## 3. Separate Data Access from Business Logic

Maintain a clean architecture by defining entities in a dedicated `models.py` and encapsulating data operations in a `repositories.py` or `services.py` module.

❌ BAD: Mixing entity definitions, database setup, and business logic in one file.
```python
# app.py
from pony.orm import *
db = Database('sqlite', 'app.sqlite')
class User(db.Entity):
    name = Required(str)
    email = Required(str, unique=True)
db.generate_mapping(create_tables=True)

@db_session
def create_user_and_send_email(name, email):
    user = User(name=name, email=email)
    # ... send email logic here ...
```

✅ GOOD: Clear separation of concerns.
```python
# models.py
from pony.orm import *
db = Database() # Bind later

class User(db.Entity):
    id = PrimaryKey(int, auto=True)
    name = Required(str)
    email = Required(str, unique=True)

# database.py
from pony.orm import Database
from .models import db # Import db instance

def setup_database(provider: str, filename: str):
    db.bind(provider=provider, filename=filename, create_db=True)
    db.generate_mapping(create_tables=True)

# services.py
from pony.orm import db_session
from .models import User
from typing import List

@db_session
def create_user(name: str, email: str) -> User:
    user = User(name=name, email=email)
    return user

@db_session
def get_all_users() -> List[User]:
    return list(select(u for u in User))

# main.py (or app.py)
from .database import setup_database
from .services import create_user, get_all_users

if __name__ == '__main__':
    setup_database('sqlite', 'my_app.sqlite')
    
    create_user('Alice', '[email protected]')
    create_user('Bob', '[email protected]')
    
    for user in get_all_users():
        print(f"User: {user.name}, Email: {user.email}")
```

## 4. Apply Static Typing to Entities and Queries

Use type hints (`typing` module) for entity attributes and function signatures. This improves readability, enables IDE auto-completion, and catches errors early.

❌ BAD: Untyped entity attributes and function parameters.
```python
class Order(db.Entity):
    amount = Required(float) # float is imprecise for currency
    customer = Required('Customer')

@db_session
def get_total_orders(customer_id): # No type hints
    customer = Customer[customer_id]
    return sum(o.amount for o in customer.orders)
```

✅ GOOD: Explicit type hints for clarity and correctness.
```python
from decimal import Decimal
from typing import List

class Customer(db.Entity):
    id = PrimaryKey(int, auto=True)
    name = Required(str)
    orders: List['Order'] # Forward reference for type hinting

class Order(db.Entity):
    id = PrimaryKey(int, auto=True)
    amount = Required(Decimal) # Use Decimal for currency
    customer = Required(Customer)

@db_session
def get_customer_total_orders(customer_id: int) -> Decimal:
    customer: Customer = Customer[customer_id]
    # Pony handles aggregation efficiently
    total_amount: Decimal = sum(o.amount for o in customer.orders)
    return total_amount
```

## 5. Utilize Pony's Automatic Caching and IdentityMap

Pony automatically caches entities and query results within a `db_session`. Avoid manual caching layers for simple entity lookups to prevent stale data.

❌ BAD: Implementing a custom cache for entities within a session.
```python
my_entity_cache = {} # Don't do this; it's redundant and error-prone with Pony

@db_session
def get_user_cached(user_id: int) -> User:
    if user_id not in my_entity_cache:
        user = User[user_id]
        my_entity_cache[user_id] = user
    return my_entity_cache[user_id]
```

✅ GOOD: Trust Pony's IdentityMap and session-level caching.
```python
@db_session
def get_user_pony_cached(user_id: int) -> User:
    # Pony's IdentityMap ensures that User[user_id] returns the same object
    # within the session if it has already been fetched.
    user1 = User[user_id]
    user2 = User[user_id]
    assert user1 is user2 # This assertion is True within the same db_session
    return user1
```

## 6. Manage Schema with the Online Editor or Versioned Scripts

Keep your Pony model definitions in sync with the database schema. For complex projects, use version-controlled migration scripts. For rapid prototyping, Pony's online editor is excellent.

❌ BAD: Manually altering database tables without updating Pony models, or vice-versa.
```python
# models.py
class Product(db.Entity):
    name = Required(str)
    # Forgot to add 'description' here, but added it to DB manually, leading to mismatches.
```

✅ GOOD: Use `db.generate_mapping(create_tables=True)` for initial setup and Pony's editor or custom migration scripts for changes.
```python
# Initial setup
db.generate_mapping(create_tables=True)

# For schema changes, either use Pony's online editor to generate SQL
# or write explicit migration scripts (e.g., using Alembic or custom logic)
# to apply changes and then update your Pony entity definitions.
```

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

Similar IDE rules

All IDE rules for Data and databases →

From the same set87

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