pyramid Best Practices
Pyramid is a flexible, "pay-only-what-you-use" Python web framework. To build robust and scalable Pyramid applications, adhere to these modern best practices.
1. Code Organization and Structure
Organize your application for clarity, testability, and scalability. Avoid monolithic __init__.py files.
1.1. Enforce Code Style with black and flake8
Always format your code automatically and lint it. Integrate black and flake8 with pre-commit hooks.
❌ BAD - Inconsistent Formatting & Linting
# Inconsistent spacing, line length, and missing imports
from pyramid.view import view_config
import os
def my_view(request):
data = request.json_body
if data and 'name' in data:
return {'status': 'ok', 'message': f"Hello, {data['name']}!"}
return {'status': 'error', 'message': 'Name is required.'}
✅ GOOD - Automated Formatting & Linting
# Ensure your pre-commit setup handles this automatically
# Example .pre-commit-config.yaml:
# - repo: https://github.com/psf/black
# rev: 23.1.0
# hooks: [- id: black]
# - repo: https://github.com/PyCQA/flake8
# rev: 6.0.0
# hooks: [- id: flake8]
from pyramid.view import view_config
from pyramid.response import Response
from typing import Dict, Any
@view_config(route_name="hello", renderer="json")
def my_view(request) -> Dict[str, str]:
"""Handles a greeting request."""
data: Dict[str, Any] = request.json_body
if data and "name" in data:
return {"status": "ok", "message": f"Hello, {data['name']}!"}
return Response(json_body={"status": "error", "message": "Name is required."}, status=400)
1.2. Modularize Configuration
Separate your application's configuration (routes, views, security) into distinct modules. Use config.include() to compose your application.
❌ BAD - Monolithic __init__.py
# myapp/__init__.py
from pyramid.config import Configurator
from pyramid.view import view_config
def main(global_config, **settings):
with Configurator(settings=settings) as config:
config.add_route('home', '/')
config.add_route('users', '/users')
config.add_route('user_detail', '/users/{id}')
@view_config(route_name='home', renderer='json')
def home_view(request):
return {'project': 'myapp'}
# ... hundreds of lines of routes and views ...
return config.make_wsgi_app()
✅ GOOD - Modular Configuration with include()
# myapp/__init__.py
from pyramid.config import Configurator
def main(global_config, **settings):
with Configurator(settings=settings) as config:
config.include("pyramid_jinja2") # Example external package
config.include(".routes") # Local routes
config.include(".views") # Local views (for decorator-based views)
config.include(".security") # Local security policies
config.scan(".") # Scan for decorator-based views and subscribers
return config.make_wsgi_app()
# myapp/routes.py
def includeme(config):
config.add_route("home", "/")
config.add_route("users", "/users")
config.add_route("user_detail", "/users/{id}")
# myapp/views.py (or specific modules like myapp/views/user.py)
from pyramid.view import view_config
from pyramid.response import Response
from typing import Dict, Any
@view_config(route_name="home", renderer="json")
def home_view(request) -> Dict[str, str]:
return {"project": "myapp"}
@view_config(route_name="users", request_method="GET", renderer="json")
def list_users_view(request) -> Dict[str, Any]:
# ... logic to list users ...
return {"users": []}
2. Common Patterns and Anti-patterns
Leverage Pyramid's design principles to write clean, maintainable code.
2.1. Use View Predicates for Conditional Logic
Avoid if/else statements within views to handle different request methods, authentication states, or other request properties. Use @view_config predicates instead.
❌ BAD - Logic in View
from pyramid.view import view_config
from pyramid.response import Response
@view_config(route_name="items", renderer="json")
def items_view(request):
if request.method == "GET":
return {"items": ["item1", "item2"]}
elif request.method == "POST":
# ... create item logic ...
return Response(json_body={"message": "Item created"}, status=201)
else:
return Response(status=405) # Method Not Allowed
✅ GOOD - Predicates for Clarity
from pyramid.view import view_config
from pyramid.response import Response
from typing import Dict, Any
@view_config(route_name="items", request_method="GET", renderer="json")
def get_items_view(request) -> Dict[str, Any]:
"""Retrieves a list of items."""
return {"items": ["item1", "item2"]}
@view_config(route_name="items", request_method="POST", renderer="json")
def create_item_view(request) -> Response:
"""Creates a new item."""
# ... create item logic ...
return Response(json_body={"message": "Item created"}, status=201)
2.2. Embrace Pyramid's Transaction Management
Utilize the transaction management system provided by Pyramid's cookiecutters (e.g., pyramid_tm). This ensures atomicity for database operations without manual commit/rollback in your view logic.
❌ BAD - Manual Transaction Handling
from pyramid.view import view_config
from myapp.models import DBSession, User
@view_config(route_name="register", request_method="POST", renderer="json")
def register_user_view(request):
try:
user = User(name=request.json_body["name"])
DBSession.add(user)
DBSession.flush() # Manual flush
DBSession.commit() # Manual commit
return {"message": "User registered", "id": user.id}
except Exception:
DBSession.rollback() # Manual rollback
raise
✅ GOOD - Automatic Transaction Handling (with pyramid_tm)
# Ensure pyramid_tm is configured in your __init__.py
# config.include('pyramid_tm')
from pyramid.view import view_config
from myapp.models import DBSession, User
from typing import Dict, Any
@view_config(route_name="register", request_method="POST", renderer="json")
def register_user_view(request) -> Dict[str, Any]:
"""Registers a new user, transaction handled automatically."""
user = User(name=request.json_body["name"])
DBSession.add(user)
DBSession.flush() # Flush is fine, commit/rollback are handled by pyramid_tm
return {"message": "User registered", "id": user.id}
2.3. Avoid Mutable Global State and Singletons
Pyramid is designed to run multiple application instances in a single process. Avoid patterns that introduce mutable global state, which can lead to hard-to-debug side effects.
❌ BAD - Global State
# myapp/settings.py (BAD practice)
GLOBAL_CONFIG = {} # Mutable global dictionary
def initialize_config(app_settings):
GLOBAL_CONFIG.update(app_settings)
# myapp/views.py
from myapp.settings import GLOBAL_CONFIG
@view_config(route_name="config_info", renderer="json")
def config_info_view(request):
return {"global_setting": GLOBAL_CONFIG.get("some_setting")}
✅ GOOD - Pass Configuration via request.registry.settings
# myapp/__init__.py
from pyramid.config import Configurator
def main(global_config, **settings):
with Configurator(settings=settings) as config:
# settings are automatically available via request.registry.settings
pass
return config.make_wsgi_app()
# myapp/views.py
from pyramid.view import view_config
from typing import Dict, Any
@view_config(route_name="config_info", renderer="json")
def config_info_view(request) -> Dict[str, Any]:
"""Retrieves configuration information."""
return {"some_setting": request.registry.settings.get("some_setting")}
3. Type Hints
Always use type hints for functions, methods, and variables. This improves code readability, enables static analysis, and helps prevent runtime errors.
❌ BAD - Untyped Code
def calculate_total(price, quantity):
return price * quantity
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
✅ GOOD - Fully Typed Code
from typing import Union
def calculate_total(price: Union[int, float], quantity: int) -> Union[int, float]:
"""Calculates the total price for a given quantity."""
return price * quantity
class Product:
def __init__(self, name: str, price: float):
self.name = name
self.price = price
4. API Design
Design RESTful APIs with clear resources, HTTP verbs, and consistent response formats.
4.1. Consistent JSON Responses
Always return JSON for API endpoints, including error responses. Use pyramid.response.Response for custom status codes.
❌ BAD - Inconsistent Error Responses
# Sometimes returns HTML, sometimes JSON, inconsistent error structure
@view_config(route_name="data", renderer="json")
def get_data_view(request):
if not request.authenticated_userid:
return Response("Unauthorized", status=401) # Returns plain text
# ...
return {"data": "some_data"}
✅ GOOD - Consistent JSON Responses
from pyramid.view import view_config
from pyramid.response import Response
from typing import Dict, Any
@view_config(route_name="data", request_method="GET", renderer="json")
def get_data_view(request) -> Union[Dict[str, Any], Response]:
"""Retrieves data, returns consistent JSON."""
if not request.authenticated_userid:
return Response(
json_body={"error": "Unauthorized", "code": "AUTH_001"},
status=401,
)
# ... logic to fetch data ...
return {"data": "some_data"}