pony
Definitive guidelines for writing efficient, maintainable, and secure Python applications using Pony ORM.
المهارة نفسها
# 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', 'alice@example.com')
create_user('Bob', 'bob@example.com')
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.
```
كيف تثبتها
1. Open the tool and start a new conversation, project or template. 2. Paste the text below wherever the tool keeps standing instructions — usually called system prompt, instructions, or persona. 3. Save it so it applies to every run rather than a single message.