Events, Tweens & Hooks
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Pyramid 2.1 (the 2.1-branch documentation and the 2.1 release installed from PyPI) on
Python 3.13, checked on 2026-10-07, with pyramid_tm 2.6 for the transaction example. Every Python example was
executed with webtest.TestApp, in one shared namespace from top to bottom; where an example needs something that a
real project keeps in another file (a tweens module, a module scanned for decorators), the text says how the example
stands in for it. The running example is the Bookshelf Shelves scenario: public shelves owned by users, which grow
over the pages of this section.
Every web framework offers a way to run code around requests without touching each view. Flask has request hooks and
signals (Flask: Hooks & View Decorators,
Flask: Signals), Django has middleware
(Django: Middleware) and signals (Django: Signals),
and Tornado has prepare and on_finish on the handler plus WSGI wrapping
(Tornado: Applications & Request Handlers). Pyramid
has not one mechanism but five, each at a different depth of the request, and choosing the right one is the skill this
page teaches:
-
Events and subscribers are notifications broadcast by the framework (and by you) at fixed points. A subscriber observes and may attach things to the request; its return value is ignored.
-
Tweens wrap the whole request handling function, like WSGI middleware that runs inside the router and sees the Pyramid request and response objects.
-
View derivers wrap each view when it is registered, and can read the options given to
add_view. -
Factories and hooks replace one piece of the machinery: the root factory, the request factory, the response factory, the not-found and forbidden views, response adapters and request methods.
-
Callbacks run once, for one request: response callbacks and finished callbacks.
Where Each Mechanism Runs
The figure shows one request, from the WSGI server to the response, with the place of each mechanism. It extends the request pipeline of the introduction page with the pieces this page adds: the two groups of tweens, the three events that fire before the view, the view derivers that wrap the view and the events and callbacks that fire on the way out.
Three consequences follow from the figure, and each one is a source of bugs later in the page.
-
NewRequest,BeforeTraversalandContextFoundfire inside the tweens, so an exception raised by a subscriber is seen by the exception-view tween and can be turned into an error response.NewResponseand the response callbacks run after the outermost tween has returned, so an exception raised there reaches the WSGI server. -
View derivers run once per view, at configuration time, to build the wrapper; only the wrapper they return runs for each request. Tweens and subscribers run for every request, whether or not a view was found.
-
The thread-local stack is pushed before the tweens and popped after the finished callbacks, which is why
get_current_request()works inside tweens and subscribers, and why it works nowhere else.
The table summarizes how to choose.
| Mechanism | Use it for | Does not suit |
|---|---|---|
Subscriber on |
Attaching data to the request (a request id, a start time), registering response and finished callbacks. |
Anything that must see the final response or wrap the rest of the request in |
Subscriber on |
Auditing which route and context matched, per-context bookkeeping that needs |
Rejecting the request: raise an HTTP exception from a view or a tween instead. |
Subscriber on |
Adding names that every template can use. |
Per-request database queries, because it runs for every rendered response, error pages included. |
Subscriber on |
Last-minute inspection of the response that is about to leave. |
Changing the response: the official documentation says a WSGI middleware or a tween is usually better. |
Custom event |
Decoupling what happened from who cares: a book was added, a shelf was published. |
Slow work: subscribers run synchronously inside the request. |
Tween |
Behaviour that wraps every request and its exceptions: timing, security headers, rate limiting, request-scoped
resource management ( |
Per-view behaviour; there the view deriver is the better tool. |
View deriver |
Behaviour that wraps views and reads their options: permissions, CSRF checks, caching headers, deprecation headers. |
Anything that must run for requests that match no view. |
WSGI middleware |
Behaviour that must work for non-Pyramid parts of the stack (proxy headers, compression) or that has no need of the Pyramid request. |
Anything that needs the registry, the matched route or a Pyramid response. |
Source: Pyramid: Request Processing and Pyramid: Using Hooks
The examples start from one small application. SHELVES is an in-memory stand-in for the database, show_shelf is
the view and build creates a configured test application, with an optional function for the extra configuration of
each example.
import re
import sys
import types
import uuid
from webtest import TestApp
from pyramid.config import Configurator
from pyramid.events import (
ApplicationCreated,
BeforeRender,
BeforeTraversal,
ContextFound,
NewRequest,
NewResponse,
subscriber,
)
from pyramid.httpexceptions import HTTPNotFound
from pyramid.response import Response
SHELVES = {"ada": ["Notes on the Analytical Engine"], "grace": ["Compilers"]}
def show_shelf(request):
name = request.matchdict["name"]
if name not in SHELVES:
raise HTTPNotFound()
return {"shelf": name, "books": SHELVES[name]}
def build(configure=None, **settings):
with Configurator(settings=settings) as config:
config.add_route("shelf", "/shelves/{name}")
config.add_view(show_shelf, route_name="shelf", renderer="json")
if configure:
configure(config)
return TestApp(config.make_wsgi_app())
Events and Subscribers
An event is an object that the framework (or your code) broadcasts at an interesting point of the application’s life. A subscriber is a function with one parameter, the event, that Pyramid calls when an event of the type it subscribed to is broadcast. Events are always sent; they only matter once a subscriber exists. Three facts from the official documentation govern everything below: the return value of a subscriber is ignored, subscribers to the same event are not called in any guaranteed order, and all subscribers run synchronously, so a slow subscriber slows the request.
Registering a Subscriber
There are two equivalent ways. Imperatively, config.add_subscriber(subscriber, iface=None, **predicates) takes the
function (or a dotted name that refers to it) and the event type. Declaratively, the @subscriber(*ifaces,
**predicates) decorator from pyramid.events marks a function, and a config.scan() finds it. As with every
decorator of this kind, nothing happens without the scan; a forgotten scan means the subscriber silently never runs.
The first example is a realistic one: a NewRequest subscriber gives every request an id, taken from the
X-Request-ID header when it is well formed (a reverse proxy or an ingress often sets one, as in
Kubernetes) and generated otherwise, and registers a response callback that copies
the id to the response. The header value comes from the client, so it is validated before being reused, which keeps
log injection and header injection out.
REQUEST_ID = re.compile(r"[A-Za-z0-9-]{8,64}")
def stamp_request(event):
request = event.request
incoming = request.headers.get("X-Request-ID", "")
request.request_id = incoming if REQUEST_ID.fullmatch(incoming) else uuid.uuid4().hex
def copy_to_response(request, response):
response.headers["X-Request-ID"] = request.request_id
request.add_response_callback(copy_to_response)
return "ignored" # the return value of a subscriber is never used
app = build(lambda config: config.add_subscriber(stamp_request, NewRequest))
ok = app.get("/shelves/ada", headers={"X-Request-ID": "client-supplied-1"})
missing = app.get("/shelves/nobody", status=404)
bad = app.get("/shelves/ada", headers={"X-Request-ID": "x\ty<script>"})
print(ok.headers["X-Request-ID"], len(missing.headers["X-Request-ID"]), len(bad.headers["X-Request-ID"]))
assert ok.headers["X-Request-ID"] == "client-supplied-1"
assert "X-Request-ID" in missing.headers and len(bad.headers["X-Request-ID"]) == 32
The output is client-supplied-1 32 32. The 404 page carries a request id too, because response callbacks also run
when an exception view produced the response; the malformed header was replaced by a generated id. A callback added
this way lives for one request, which is why it is registered from a NewRequest subscriber on each request.
Source: Pyramid: Using Events
The declarative form needs a scan. In a project the decorated functions live in a module, usually
shelves/subscribers.py, and the application scans it with config.scan(). To keep this page one script, the helper
below hands the scan a module object built from the current namespace; the scan finds exactly the decorators it would
find in a real module. The example subscribes one function to ContextFound, one to two event types at once and one
to everything (@subscriber() with no arguments is called for every event sent), and prints what they saw.
seen = []
@subscriber(ContextFound)
def count_route(event):
seen.append(("ContextFound", event.request.matched_route.name, event.request.matchdict))
@subscriber(BeforeTraversal, NewResponse)
def note(event):
seen.append((type(event).__name__,))
@subscriber()
def everything(event):
seen.append(("*", type(event).__name__))
def scan_here(config):
"""Scan the subscribers defined above, as config.scan() would scan a module."""
here = types.ModuleType("__main__")
here.__dict__.update(globals())
config.scan(here)
build(scan_here).get("/shelves/grace")
for item in seen:
print(item)
The everything subscriber shows the full sequence of one request that renders a JSON view: ApplicationCreated
(when the application was built), NewRequest, BeforeTraversal, ContextFound, BeforeRender and NewResponse.
The two specific subscribers fire at their own points. The ContextFound line shows the matched route name and the
matchdict, which is how an audit subscriber learns what was requested without a line in any view. Since 1.9.1 the
decorator also accepts _depth and _category for people who wrap it in their own decorator or who scan by
category.
Source: pyramid.events.subscriber
The Built-in Event Types
Every event class has a matching interface in pyramid.interfaces, and a subscription is made against either one. The
interface documentation lists the attributes of each event; the table lists what you need in practice.
Event (pyramid.events) |
Attributes | When it fires, and what it is for |
|---|---|---|
|
|
When |
|
|
As the router starts to process a request, before the route mapper runs. The request has no |
|
|
After the route mapper has tried the routes and before the root factory and traversal. |
|
|
After traversal found a context and before any view code runs. |
|
|
Whenever a view or an exception view returned a response, after the response callbacks. Not sent when an exception
no exception view handled escapes. If an exception view produced the response, |
|
a dictionary of the renderer globals, plus |
Just before a renderer runs, for every renderer including JSON. A subscriber adds names to the globals with
|
Two remarks about the table. First, NewResponse is a weak hook by the official documentation’s own admission: it
exists almost for symmetry with NewRequest, and post-processing a response is usually better done in a WSGI
middleware or, better, a tween that sits at a known place in the chain. Second, request.exception and
request.exc_info are set only if the response was produced by the exception-view tween (a change of 1.9), so do not
look for them on a response that something else squashed an exception into.
The ApplicationCreated event is the one place where a missing setting can stop an application before it serves
anything. The subscriber below refuses to start when a required setting is absent, which beats finding out on the
first request.
from pyramid.exceptions import ConfigurationError
def require_settings(event):
settings = event.app.registry.settings
missing = [key for key in ("shelves.page_size",) if key not in settings]
if missing:
raise ConfigurationError(f"missing settings: {', '.join(missing)}")
for settings in ({}, {"shelves.page_size": "20"}):
try:
with Configurator(settings=settings) as config:
config.add_subscriber(require_settings, ApplicationCreated)
config.make_wsgi_app()
print("started")
except ConfigurationError as exc:
print("refused to start:", exc)
The output is refused to start: missing settings: shelves.page_size and then started. The
Configuration & the Configurator page has the other use
of this event, reading the introspector once the registry is complete.
Source: pyramid.events API
BeforeRender: Renderer Globals
A BeforeRender event behaves like the dictionary of the values that the renderer receives next to the view’s own
return value: the system values (request, view, context, renderer_name and so on) plus whatever
subscribers add. The view’s own return value is available as event.rendering_val. The next example registers a custom
renderer so that the effect is visible without a template engine.
def greeting_renderer_factory(info):
def render(value, system):
request = system["request"]
return f"{system['site_name']} | {value['shelf']} | rendered for {request.path}"
return render
def add_site_name(event):
if "site_name" not in event: # never overwrite another subscriber's key
event["site_name"] = "Bookshelf Shelves"
print("rendering_val keys:", sorted(event.rendering_val))
def with_greeting(config):
config.add_renderer("greeting", greeting_renderer_factory)
config.add_route("greet", "/greet/{name}")
config.add_view(show_shelf, route_name="greet", renderer="greeting")
config.add_subscriber(add_site_name, BeforeRender)
print(build(with_greeting).get("/greet/ada").text)
The output is rendering_val keys: ['books', 'shelf'] followed by Bookshelf Shelves | ada | rendered for
/greet/ada. Subscribers have no relative order, so two of them that set the same key race. The narrative
documentation says Pyramid raises a KeyError for that, the API documentation says the later assignment overwrites,
and on 2.1 the assignment was accepted without an error (the Templates page of this section observed the same), so
write defensive subscribers like the one above and give every global a distinctive name. The
Templates page shows the same event with a real template.
Source: Using the Before Render Event
Subscribing by Interface, by Class and to Everything
add_subscriber(function, NewRequest) and add_subscriber(function, INewRequest) both work: Pyramid’s own events
provide the interface, and a subscription to a class or to an interface is satisfied by any event that provides it.
The same rule makes subscriptions to a base class of a custom event receive its subclasses, which the next section
puts to use. Leaving the second argument out subscribes to every event, as @subscriber() does.
Subscriber Failures
A subscriber runs in the request. Where it runs decides what happens to its exception, and this is the only piece of the figure that changes the outcome:
def explode(event):
raise RuntimeError("subscriber failed")
def json_500(exc, request):
request.response.status = 500
return {"error": type(exc).__name__}
def explode_on_request(config):
config.add_subscriber(explode, NewRequest)
config.add_exception_view(json_500, context=RuntimeError, renderer="json")
def explode_on_response(config):
config.add_subscriber(explode, NewResponse)
config.add_exception_view(json_500, context=RuntimeError, renderer="json")
print(build(explode_on_request).get("/shelves/ada", status=500).json)
try:
build(explode_on_response).get("/shelves/ada", expect_errors=True)
except RuntimeError as exc:
print("escaped to the server:", exc)
The output is {'error': 'RuntimeError'} and then escaped to the server: subscriber failed. An error in a
NewRequest subscriber is inside the excview tween, so the exception view answers; an error in a NewResponse
subscriber (and in a response callback) is outside every tween, and nothing but the WSGI server can answer it. Keep
NewResponse subscribers trivial, and let the real work happen in tweens or view code that the exception machinery
covers. The errors page of this section, Errors & Exception Views,
explains the tween.
Source: Pyramid: Request Processing
Custom Events
An event is any object. There are no required attributes: a class that carries what its subscribers need is enough.
You fire it with request.registry.notify(event), the documented spelling of Registry.notify, and every
subscriber registered for the event’s class, one of its base classes or an interface it provides is called, in turn,
in the same thread. The Bookshelf Shelves application defines one event for everything that happens to a shelf
(ShelfEvent) and a specialization for added books (BookAdded). The audit log subscribes to the base class, the
mailing list to the specific event, and the view knows about neither.
class ShelfEvent:
def __init__(self, shelf, request):
self.shelf = shelf
self.request = request
class BookAdded(ShelfEvent):
def __init__(self, shelf, title, request):
super().__init__(shelf, request)
self.title = title
audit, mails = [], []
def write_audit(event):
audit.append((type(event).__name__, event.shelf))
def queue_mail(event):
mails.append(f"{event.title} added to {event.shelf}")
def add_book(request):
shelf = request.matchdict["name"]
title = request.json_body["title"]
SHELVES.setdefault(shelf, []).append(title)
request.registry.notify(BookAdded(shelf, title, request))
return {"added": title}
def with_books(config):
config.add_route("books", "/shelves/{name}/books", request_method="POST")
config.add_view(add_book, route_name="books", renderer="json")
config.add_subscriber(write_audit, ShelfEvent)
config.add_subscriber(queue_mail, BookAdded)
app = build(with_books)
print(app.post_json("/shelves/ada/books", {"title": "Lectures on Computation"}).json)
print(audit, mails)
The audit subscriber, registered for ShelfEvent, received the BookAdded event. Real code would also authorize the
write; the security pages of this section show permission= on the view, and the example leaves it out only to stay
about events.
Events and Transactions
A subscriber runs before the view returns, so with pyramid_tm it runs inside the transaction. A subscriber that
sends an email or publishes a message sends it even if the transaction later aborts. The remedy is to defer the side
effect to the commit: the transaction package, which pyramid_tm drives, accepts after-commit hooks that receive a
success flag. The next example registers the hook from the subscriber; the second request fails validation after the
event fired, the transaction aborts and no mail is sent. (The transaction setup of a real application is the subject
of SQLAlchemy, Transactions & Migrations.)
import transaction
def queue_mail_after_commit(event):
def send(success):
if success:
mails.append(f"sent: {event.title}")
transaction.get().addAfterCommitHook(send)
def create_or_fail(request):
title = request.json_body["title"]
request.registry.notify(BookAdded(request.matchdict["name"], title, request))
if title.startswith("!"):
raise ValueError("rejected by validation")
return {"added": title}
def with_transactions(config):
config.include("pyramid_tm")
config.add_route("tm_books", "/tm/{name}/books", request_method="POST")
config.add_view(create_or_fail, route_name="tm_books", renderer="json")
config.add_subscriber(queue_mail_after_commit, BookAdded)
mails.clear()
app = build(with_transactions)
app.post_json("/tm/ada/books", {"title": "Kept"})
try:
app.post_json("/tm/ada/books", {"title": "!Dropped"}, expect_errors=True)
except ValueError as exc:
print("view failed:", exc)
print(mails)
The output is view failed: rejected by validation and then ['sent: Kept']. Long-running reactions belong in a
queue, as the official documentation suggests: the subscriber only enqueues, and a worker (see the messaging section
of this site, Messaging) does the slow part.
Source: Pyramid: Using Hooks and Registry.notify
Subscriber Predicates
Predicates narrow the circumstances in which a subscriber is called, as view predicates narrow views. Three things are
different for subscribers: there are no built-in subscriber predicates, so you register each one with
add_subscriber_predicate(name, factory); the predicate’s __call__ receives the event instead of a context and a
request; and not every predicate fits every event, because the predicate, not Pyramid, decides which attributes of the
event it reads. A predicate factory is a class (or callable) that takes val and info, with a text() method
describing it for error messages, a phash that tells Pyramid when two registrations are identical (the same as
text when text mentions the name and the value) and the __call__. Since 1.5 the not_ wrapper from
pyramid.config inverts a predicate value, which works for subscribers as for views.
class RequestPathStartsWith:
def __init__(self, val, info):
self.val = val
def text(self):
return f"request_path_startswith = {self.val}"
phash = text
def __call__(self, event):
return event.request.path.startswith(self.val)
hits = []
def only_for_api(event):
hits.append(event.request.path)
def with_predicate(config):
config.add_subscriber_predicate("request_path_startswith", RequestPathStartsWith)
config.add_subscriber(only_for_api, NewRequest, request_path_startswith="/api")
config.add_route("api_shelf", "/api/shelves/{name}")
config.add_view(show_shelf, route_name="api_shelf", renderer="json")
app = build(with_predicate)
app.get("/shelves/ada")
app.get("/api/shelves/ada")
print(hits)
The output is ['/api/shelves/ada']: the first request did not satisfy the predicate. The @subscriber(NewRequest,
request_path_startswith="/api") form takes the same keyword. The predicate above reads event.request, so it fits
NewRequest, BeforeTraversal, ContextFound and NewResponse but not ApplicationCreated, which has no request.
Pyramid does not check this at registration; registering the same predicate for ApplicationCreated makes
make_wsgi_app() fail with AttributeError: 'ApplicationCreated' object has no attribute 'request', which is the
moment the event fires. The responsibility lies with whoever registers the subscriber, as the documentation says.
Source: Pyramid: Subscriber Predicates
Tweens
A tween (from "between") is code that sits between the router’s main request handling function and the server
that calls it. It is similar to WSGI middleware, with three advantages: it runs inside the router, so it has the
Pyramid request, the response object and the application registry; it can use the rendering machinery; and it can be
ordered relative to Pyramid’s own tweens, notably the one that handles exceptions. It is the right tool for behaviour
that must wrap every request. pyramid_tm, the tween behind transaction management, is the best known one.
Writing a Tween
A tween needs a tween factory: a globally importable callable that Pyramid calls once at start-up with two
arguments, handler (the next tween, or the main handler) and registry, and that returns the tween. The tween is a
callable taking the request and returning a response, usually the one from handler(request). Because the factory
sees the settings, it can decide not to take part at all: returning handler itself removes the tween from the
chain with no run-time cost. The factory may be a function returning a closure, which performs slightly better and can
omit itself, or a class with __call__, which suits shared state and subclassing. Avoid mutating the state of a tween
instance: it is called by every thread of the server.
The three tweens below are typical. The timing tween measures the time spent in everything below it and reports it in a
Server-Timing header, only when a setting enables it. The security headers tween adds headers to every response,
error pages included (see below why its position matters). The write limiter is the class style: it short-circuits
by returning a response without calling handler, and guards its counters with a lock because every request thread
shares the one instance.
import threading
import time
from pyramid.settings import asbool
from pyramid.tweens import EXCVIEW
def timing_tween_factory(handler, registry):
if not asbool(registry.settings.get("shelves.timing", "false")):
return handler # the tween disappears from the chain
def timing_tween(request):
start = time.perf_counter()
response = handler(request)
response.headers["Server-Timing"] = f"app;dur={(time.perf_counter() - start) * 1000:.1f}"
return response
return timing_tween
def security_headers_tween_factory(handler, registry):
def security_headers_tween(request):
response = handler(request)
response.headers.setdefault("X-Content-Type-Options", "nosniff")
response.headers.setdefault("Referrer-Policy", "same-origin")
return response
return security_headers_tween
class WriteLimiter:
"""Class-style tween: reject more than `limit` POST requests per client address (demo only)."""
def __init__(self, handler, registry):
self.handler = handler
self.limit = int(registry.settings.get("shelves.write_limit", "2"))
self.counts = {}
self.lock = threading.Lock()
def __call__(self, request):
if request.method == "POST":
with self.lock:
used = self.counts[request.remote_addr] = self.counts.get(request.remote_addr, 0) + 1
if used > self.limit:
return Response("Too many writes", status=429, headers={"Retry-After": "60"})
return self.handler(request)
# A project keeps these in a module such as shelves/tweens.py and registers them by dotted name. This single
# script publishes them under such a module name instead.
sys.modules["shelves_tweens"] = types.SimpleNamespace(
timing_tween_factory=timing_tween_factory,
security_headers_tween_factory=security_headers_tween_factory,
WriteLimiter=WriteLimiter,
)
Source: Pyramid: Creating a Tween
Registering and Ordering Tweens
config.add_tween(tween_factory, under=None, over=None) registers a factory, and the first argument must be the
dotted name of the factory; passing the function raises a ConfigurationError, because the name is the identity
that ordering constraints, conflict detection and ptweens use. The chain is an onion. The names pyramid.tweens.INGRESS
(the outer edge, where the server calls in) and pyramid.tweens.MAIN (the router’s own handler, always at the
bottom) bound it, and pyramid.tweens.EXCVIEW is the name of Pyramid’s exception-view tween,
pyramid.tweens.excview_tween_factory, which is added for you. Here the vocabulary is exact:
-
over=Xplaces the tween closer to the request ingress than X: X is called inside it. -
under=Xplaces it closer to the main application than X: it is called inside X. -
Specifying neither is the same as
under=INGRESS: the tween goes to the outside, in the order of registration. -
Each value may be a dotted name, one of the three constants or an iterable of them; if an iterable is given, the entries that exist are used, which is how a tween says "under
pyramid_tmif it is present, otherwise under `INGRESS`". If none of the named tweens exists it is an error.
These constraints give only an implicit order, best effort. The next example registers the three tweens: the
security headers tween over EXCVIEW (so it is outside the exception handling), the timing tween with no
constraint and the write limiter under EXCVIEW (inside it).
def with_tweens(config):
config.add_tween("shelves_tweens.security_headers_tween_factory", over=EXCVIEW)
config.add_tween("shelves_tweens.timing_tween_factory")
config.add_tween("shelves_tweens.WriteLimiter", under=EXCVIEW)
config.add_route("write", "/write", request_method="POST")
config.add_view(lambda request: Response("saved"), route_name="write")
app = build(with_tweens, **{"shelves.timing": "true", "shelves.write_limit": "2"})
print(app.get("/shelves/ada").headers["X-Content-Type-Options"])
missing = app.get("/shelves/nobody", status=404)
print(missing.status, missing.headers["X-Content-Type-Options"], "Server-Timing" in missing.headers)
print([app.post("/write", expect_errors=True).status_int for _ in range(3)])
The output is nosniff, then 404 Not Found nosniff True and finally [200, 200, 429]. The 404 page has the security
header and the timing because both tweens are outside the exception-view tween: by the time the response reaches
them, the exception view has already produced it. Had the security headers tween been placed under=EXCVIEW, an
exception would pass through it as an exception without a response, and error pages would lack the headers.
That is the main reason to think about placement: a tween above EXCVIEW sees every response, a tween below sees the
exceptions raised by the view. A tween below can therefore catch an exception and re-raise it (or convert it); a
tween above never sees an exception that an exception view can handle.
Source: Pyramid: Registering Tweens and pyramid.tweens
Displaying the Chain with ptweens
The chain you get is not always the chain you imagined, so print it. The ptweens script loads the application from
an .ini file and lists the implicit chain, or the explicit chain if the pyramid.tweens setting is present.
Position 0 is the outermost tween. The command ran against an .ini file with use = call:shelves_app:main and the
registrations of the previous example:
ptweens development.ini
"pyramid.tweens" config value NOT set (implicitly ordered tweens used)
Implicit Tween Chain
Position Name
-------- ----
- INGRESS
0 shelves_tweens.timing_tween_factory
1 shelves_tweens.security_headers_tween_factory
2 pyramid.tweens.excview_tween_factory
3 shelves_tweens.WriteLimiter
- MAIN
Everything between INGRESS and MAIN is a tween. The chain also shows an order that registration alone would not
give: the write limiter, registered last, is the innermost because of under=EXCVIEW. With pyramid_tm included
(config.include("pyramid_tm")) the same command lists pyramid_tm.tm_tween_factory over the exception view
tween, so that an exception view’s response aborts the transaction. In a script or a test, the same information is
available from the registry: config.registry.queryUtility(ITweens) (ITweens from pyramid.interfaces) returns the
object that ptweens reads, with an implicit() method and an explicit attribute. The
Cookiecutter Projects & pserve page lists ptweens among
the scripts that a project installs; the Command Line & p-Scripts page covers them all.
Explicit Tween Order
The deployer, not the developer, may override the whole chain with the pyramid.tweens setting: a list of dotted
names, outermost first, that replaces both the inclusion and the order of everything added with add_tween.
INGRESS and MAIN stay implicit. The consequence to remember is that the exception-view tween is then
included only if it is in the list; a list that omits it switches off exception views.
[app:main]
use = egg:shelves
pyramid.tweens = shelves_tweens.timing_tween_factory
pyramid.tweens.excview_tween_factory
The same effect from Python, with and without the exception-view tween (the setting is a whitespace-separated list of dotted names):
def without_excview():
settings = {"pyramid.tweens": "shelves_tweens.security_headers_tween_factory"}
with Configurator(settings=settings) as config:
config.add_route("shelf", "/shelves/{name}")
config.add_view(show_shelf, route_name="shelf", renderer="json")
return TestApp(config.make_wsgi_app())
def with_excview():
settings = {
"pyramid.tweens": "shelves_tweens.security_headers_tween_factory\npyramid.tweens.excview_tween_factory"
}
with Configurator(settings=settings) as config:
config.add_route("shelf", "/shelves/{name}")
config.add_view(show_shelf, route_name="shelf", renderer="json")
return TestApp(config.make_wsgi_app())
try:
without_excview().get("/shelves/nobody", expect_errors=True)
except HTTPNotFound as exc:
print("no excview tween, the exception escaped:", type(exc).__name__)
print(with_excview().get("/shelves/nobody", expect_errors=True).status)
The output is no excview tween, the exception escaped: HTTPNotFound and 404 Not Found. An explicit list is the
tool of last resort; it also means that an add-on that adds a tween (and most do) is dropped unless you remember to list
it. When you only need one thing in one place, an under or over hint is gentler.
Source: Pyramid: Explicit Tween Ordering
Conflicts, Cycles and Missing Names
Three mistakes show up at start-up and are worth recognizing. A tween factory registered twice conflicts with itself,
because the factory’s dotted name is the discriminator; register distinct names, or commit between the calls.
Constraints that contradict each other raise CyclicDependencyError from make_wsgi_app(), with a message that lists
who sorts before whom. A constraint that names a tween which is not in the configuration is an error too, unless it was
given inside an iterable with a fallback such as INGRESS or MAIN.
from pyramid.exceptions import CyclicDependencyError, ConfigurationConflictError
sys.modules["tw"] = types.SimpleNamespace(a=lambda handler, registry: handler, b=lambda handler, registry: handler)
with Configurator() as config:
config.add_tween("tw.a", under="tw.b")
config.add_tween("tw.b", under="tw.a")
try:
config.make_wsgi_app()
except CyclicDependencyError as exc:
print("cycle:", exc)
with Configurator() as config:
config.add_tween("tw.a")
config.add_tween("tw.a")
try:
config.commit()
except ConfigurationConflictError:
print("conflict: the same factory twice")
The output starts with cycle: Implicit ordering cycle:'tw.a' sorts before ['tw.b']; 'tw.b' sorts before ['tw.a'] and
ends with the conflict line. Both are start-up errors, never request-time surprises.
Tweens, Middleware and Subrequests
A tween is not a replacement for WSGI middleware, and the two combine. Middleware wraps the object returned by
make_wsgi_app(); it sees raw WSGI, which is what you want for proxy headers, compression or code shared with a
non-Pyramid application (see Tornado: WSGI Interoperability for the
mirror image, and FastAPI: Middleware, CORS & Proxies for the
ASGI equivalent). A tween sees Pyramid objects and runs inside the thread-local context. Two behaviours of tweens
catch people out. Tweens run once per request and see the response object before its body has been iterated, so
a tween cannot time a streamed response body, and a header it sets is applied before the first byte is sent, not after
the last. And a subrequest made with request.invoke_subrequest(subrequest) skips the tweens unless you pass
use_tweens=True, although the events (NewRequest, NewResponse) still fire for it, so a subscriber that is not
idempotent runs twice for a request that renders a sub-page.
Source: Pyramid: Subrequests
View Derivers
A view deriver is a tween for views. Every URL processed by Pyramid is matched against a view pipeline built when
the view was registered, and that pipeline is the user’s view callable wrapped by a series of derivers. Unlike a tween,
a deriver is applied once per view, at configuration time, and it has access to the options that were given to
add_view; unlike a decorator, it can apply to every view in the application. Pyramid uses the mechanism for its own
features, which is why the order matters. The built-in derivers are listed here from the outermost to the one
closest to the view.
| Deriver | What it does |
|---|---|
|
Enforces the |
|
Checks the CSRF token when |
|
Invokes the wrapper named by the |
|
Applies the cache headers given by the |
|
Wraps the view with the decorators of the |
|
Turns the value returned by the view into a response, using the renderer. Below this point the result may be any Python object. |
|
Applies the view mapper, and standardizes the call to |
Two more wrappers, attr_wrapped_view and predicated_view, are applied outside all of these with a fixed order and
are not part of the sortable list; they implement the attr option and the view predicates. The introspector and
registry.getUtility(IViewDerivers).sorted() list the sortable derivers.
Writing a Deriver
A deriver is a callable taking the view and an IViewDeriverInfo object, and returning a view (a callable taking
(context, request)) or the original view unchanged. The info object has options (the options of this
add_view), registry, settings, package, predicates, original_view and exception_only. A deriver
declares the view options it understands in an options attribute; once it does, those options are valid
arguments of add_view and @view_config, and an unknown option is a ConfigurationError.
The Bookshelf deriver below marks deprecated endpoints. A view that passes deprecated="<HTTP date>" gets
Deprecation and Sunset headers; views without the option are returned untouched, so they pay nothing.
def deprecated_view(view, info):
sunset = info.options.get("deprecated")
if not sunset:
return view
def wrapper_view(context, request):
response = view(context, request)
response.headers["Deprecation"] = "true"
response.headers["Sunset"] = sunset
return response
return wrapper_view
deprecated_view.options = ("deprecated",)
def legacy_list(request):
return Response("legacy list")
def with_deriver(config):
config.add_view_deriver(deprecated_view)
config.add_route("legacy", "/legacy")
config.add_view(legacy_list, route_name="legacy", deprecated="Wed, 01 Dec 2027 00:00:00 GMT")
app = build(with_deriver)
print(app.get("/legacy").headers["Sunset"], "Sunset" in app.get("/shelves/ada").headers)
with Configurator() as config:
config.add_route("x", "/x")
try:
config.add_view(lambda request: Response("x"), route_name="x", deprecated="soon")
config.commit()
except ConfigurationError as exc:
print(exc)
The output is Wed, 01 Dec 2027 00:00:00 GMT False and then Unknown view options: {'deprecated': 'soon'}: the second
configurator has not registered the deriver, so the option is not valid there. The deriver wraps the view that
produces the response, so it works whether the view returns a Response or, like the show_shelf view, a value for
a renderer; but see the next warning.
Source: Pyramid: Custom View Derivers
A deriver that is added by add_view_deriver lands, by default, between decorated_view and rendered_view.
Derivers placed under rendered_view do not receive a response, only the raw value the view returned (a dictionary
for a renderer), and that is why the example above must return an object that already is a response; for a view with a
renderer, such a deriver would fail on response.headers. A deriver that needs the real response goes above
rendered_view, and one that needs the raw value goes below it. When a deriver is applied to an exception view,
info.exception_only is true; secured_view and csrf_view use this to leave exception views alone, and a custom
deriver that enforces rules (an audit, a quota) should usually do the same.
Ordering Derivers
add_view_deriver(deriver, name=None, under=None, over=None) takes under and over with the names of other
derivers. The sentinels pyramid.viewderivers.INGRESS and pyramid.viewderivers.VIEW name the two edges of the
pipeline. Be careful with the defaults: every deriver is by default over rendered_view and under decorated_view, so
asking for over="secured_view" alone produces an unsatisfiable cycle. The next example asks for a deriver at the very
start of the pipeline and one just before the view mapper, and shows the failure.
from pyramid.interfaces import IViewDerivers
from pyramid.viewderivers import INGRESS
def passthrough(view, info):
return view
def chain(config):
return [name for name, _ in config.registry.getUtility(IViewDerivers).sorted()]
with Configurator() as config:
config.add_view_deriver(passthrough, name="audit")
config.add_view_deriver(passthrough, name="first", under=INGRESS, over="secured_view")
config.add_view_deriver(passthrough, name="late", under="rendered_view", over="mapped_view")
config.commit()
print(chain(config))
with Configurator() as config:
config.add_view_deriver(passthrough, name="too_early", over="secured_view")
try:
config.commit()
except CyclicDependencyError:
print("cycle: over=secured_view needs under=INGRESS")
The first line printed is ['first', 'secured_view', 'csrf_view', 'owrapped_view', 'http_cached_view',
'decorated_view', 'audit', 'rendered_view', 'late', 'mapped_view']: the default position of audit, and the two
requested ones. A deriver cannot be placed under mapped_view, because the mapper is tied to the signature of the
user’s view; if you need the original callable, it is info.original_view.
Source: Pyramid: Ordering View Derivers
Other Hooks
The remaining hooks each replace one piece of the framework. They are small, and they are the reason most Pyramid applications look the same at the top and different below.
Not Found and Forbidden Views
The views that answer for a URL that matches nothing and for a refused permission are ordinary exception views,
registered with add_notfound_view and add_forbidden_view (or the notfound_view_config and
forbidden_view_config decorators). The forbidden view runs only when HTTPForbidden is raised, not when a view
returns one. The full treatment is on the errors page; here the
important thing is that they are hooks like the others, which can be replaced.
def api_not_found(request):
request.response.status = 404
return {"error": "not found", "path": request.path}
def with_not_found(config):
config.add_notfound_view(api_not_found, renderer="json")
print(build(with_not_found).get("/elsewhere", status=404).json)
The output is {'error': 'not found', 'path': '/elsewhere'}.
The Root Factory and Route Factories
The root factory is a callable, given the request, that returns the root of the resource tree. Pass it as
Configurator(root_factory=...) or config.set_root_factory(...). A route may have its own factory, and URL
dispatch is where this hook is most useful: a route factory loads the object the URL names, once, before the view
runs, and becomes request.context. If the object does not exist it raises HTTPNotFound, so no view has to check.
The factory runs after BeforeTraversal and before ContextFound. The
URL Dispatch and Traversal & Resources
pages show the same hook driving authorization and traversal.
class ShelfFactory:
"""Route factory: load the shelf once, before the view, and make it the context."""
def __init__(self, request):
name = request.matchdict["name"]
if name not in SHELVES:
raise HTTPNotFound(f"no shelf called {name}")
self.name, self.books = name, SHELVES[name]
def books_view(request):
return {"shelf": request.context.name, "count": len(request.context.books)}
with Configurator() as config:
config.add_route("count", "/shelves/{name}/count", factory=ShelfFactory)
config.add_view(books_view, route_name="count", renderer="json")
app = TestApp(config.make_wsgi_app())
print(app.get("/shelves/grace/count").json, app.get("/shelves/zed/count", status=404).status)
The output is {'shelf': 'grace', 'count': 1} 404 Not Found.
Source: Pyramid: URL Dispatch
The Request Factory and add_request_method
Pyramid creates the request with a request factory, by default pyramid.request.Request. You can subclass it and
install the subclass with Configurator(request_factory=...) or config.set_request_factory(...), but an
application has only one request factory, so it is a poor tool for add-ons that must compose. The
composable tool is config.add_request_method(callable, name=None, property=False, reify=False): the callable
becomes a method of every request, or, with property=True, a property computed at each access, or, with
reify=True, a property computed on first access and cached for the request. Giving a class as the callable and a
name attaches an instance of it, which is a neat way to group helpers under one attribute. Methods added this way
override same-named attributes of the request factory silently. The Security Policy page of this section builds
request.user like this, and the data stores page exposes
database clients.
from pyramid.decorator import reify
from pyramid.request import Request
class ShelvesRequest(Request):
@property
def wants_json(self):
return self.accept.acceptable_offers(["application/json"]) != []
class Limits:
def __init__(self, request):
self.request = request
@reify
def page_size(self):
print("computing page_size")
return int(self.request.registry.settings.get("shelves.page_size", "20"))
def whoami(request):
return {
"json": request.wants_json,
"path": request.clean_path,
"size": request.limits.page_size + request.limits.page_size,
"total": request.total(1, 2, 3),
}
def with_request_hooks(config):
config.set_request_factory(ShelvesRequest)
config.add_request_method(lambda request: request.path.rstrip("/"), "clean_path", reify=True)
config.add_request_method(Limits, "limits", reify=True)
config.add_request_method(lambda request, *numbers: sum(numbers), "total")
config.add_route("who", "/who/")
config.add_view(whoami, route_name="who", renderer="json")
app = build(with_request_hooks, **{"shelves.page_size": "25"})
print(app.get("/who/", headers={"Accept": "application/json"}).json)
The output is computing page_size once, although the property is read twice, then
{'json': True, 'path': '/who', 'size': 50, 'total': 6}. Since 2.0 a property made with property=True can be assigned
to (it used to be read-only). The request methods are applied when the router creates the request, so they also exist
on the requests created for scripts with pyramid.paster.bootstrap.
Response Callbacks and Finished Callbacks
Pyramid does not create a global response eagerly, so there is no after_request to mutate. A response callback,
added with request.add_response_callback(callback), receives (request, response) and runs after the view
or exception view produced a response; a finished callback, added with request.add_finished_callback(callback),
receives request and runs at the very end, unconditionally, even if an exception prevented any response. Both live
for one request and run in the order added. Response callbacks run before the NewResponse event; neither kind is
a good place for work that can fail, because an error in one is not handled by the exception machinery. A response callback is
not called if an unhandled exception escapes. Finished callbacks are for releasing resources, and mutating the request
there has no meaning, since the response has already been built.
log = []
def cache_policy(request, response):
if request.exception is None:
response.cache_control.max_age = 60
def close_resource(request):
log.append(("finished", request.path))
def stamp(event):
event.request.add_response_callback(cache_policy)
event.request.add_finished_callback(close_resource)
def failing(request):
raise RuntimeError("bug")
def with_callbacks(config):
config.add_subscriber(stamp, NewRequest)
config.add_route("bug", "/bug")
config.add_view(failing, route_name="bug")
app = build(with_callbacks)
print(app.get("/shelves/ada").headers.get("Cache-Control"), app.get("/shelves/zed", status=404).headers.get("Cache-Control"))
try:
app.get("/bug", expect_errors=True)
except RuntimeError:
pass
print(log)
The output is max-age=60 None, then [('finished', '/shelves/ada'), ('finished', '/shelves/zed'), ('finished',
'/bug')]. The cache policy was applied only to the successful response (request.exception identifies the 404), and the
finished callback ran for the request whose view raised and produced no response at all. This is the pattern
pyramid_tm and the database page use to close sessions.
The Response Factory and Response Adapters
The response factory builds the object behind request.response, which a view or an exception view may fill instead
of creating its own. Install one with Configurator(response_factory=...) or config.set_response_factory(...)
(since 1.6). It receives the request, which may be None. A response adapter, registered with
config.add_response_adapter(adapter, type_or_interface), tells Pyramid how to turn a type that a view may return into
a response, so that views can return domain objects directly.
class JsonResponse(Response):
default_content_type = "application/json"
def with_response_hooks(config):
config.set_response_factory(lambda request: JsonResponse())
config.add_response_adapter(lambda text: Response(text, content_type="text/plain"), str)
config.add_route("txt", "/txt")
config.add_view(lambda request: "a bare string", route_name="txt")
config.add_route("fact", "/fact")
config.add_view(lambda request: request.response, route_name="fact")
app = build(with_response_hooks)
print(app.get("/txt").content_type, app.get("/fact").content_type)
The output is text/plain application/json. Adapters make code shorter and views less obvious; use them sparingly,
for types the whole application agrees on. The renderers page, Renderers & JSON,
shows the usual alternative, a renderer with a custom adapter for JSON.
Thread Locals and Why to Avoid get_current_request
During a request Pyramid keeps two thread-local values, the application registry and the request, in a stack managed
by the router: it pushes both before the tweens run and pops them after the finished callbacks, as the figure shows.
pyramid.threadlocal.get_current_request() and get_current_registry() read the top of the stack. Pyramid
itself needs them for historical reasons (pyramid.security helpers used them to find the registry), and the official
documentation is frank about everything else: using them in application code is almost always a mistake. A function
that calls get_current_request() has a hidden input, cannot be tested without a configured thread-local
stack and cannot be run from a script, where the answer is None. The documentation’s rules, in short:
-
never call it inside a view or in code a view calls: the view has the
requestargument; -
never call it in resource code: a view passes the request in when the resource needs it;
-
never call it to shorten an API; pass data derived from the request instead;
-
never call it in a fork of third-party code, which would make the library depend on Pyramid;
-
use it, if at all, in code that is meant to be removed (a compatibility facade during a deprecation), and use
get_current_registry()only in tests.
There is a technical reason too. The values belong to the thread of the router. Code that moves work to another thread (a thread pool, a background task) sees an empty stack there:
import threading
from pyramid import testing
from pyramid.threadlocal import get_current_registry, get_current_request
seen_in = {}
def where_am_i(request):
seen_in["view"] = get_current_request() is request
def in_a_thread():
seen_in["thread"] = get_current_request()
thread = threading.Thread(target=in_a_thread)
thread.start()
thread.join()
return Response("ok")
def with_thread_check(config):
config.add_route("where", "/where")
config.add_view(where_am_i, route_name="where")
build(with_thread_check).get("/where")
print(seen_in, get_current_request())
with testing.testConfig() as config:
config.add_route("home", "/")
request = testing.DummyRequest()
print(request.route_path("home"), get_current_registry() is config.registry)
The output is {'view': True, 'thread': None} None, then / True. In the request thread the value is the request;
in a new thread, and outside any request, it is None. The second half of the example shows the one place where
the documentation accepts the registry: in a test, testing.setUp() (or the testing.testConfig() context manager)
pushes a registry, so that code under test that reads it finds it. The right design is the boring one: a function
takes request (or the setting it needs) as a parameter. The Forms page of this section keeps one legitimate use,
translating inside a Colander schema where no request is available, and the Testing page of this section covers
testing.setUp.
Source: Pyramid: Thread Locals and pyramid.threadlocal
Subrequests and Events
The router does not distinguish a subrequest from a request when it fires events, so subscribers run for both. The example makes a view fetch another page of the same application and prints the order in which the events of the two requests arrive.
sub_events = []
def outer(request):
inner = Request.blank("/shelves/ada")
response = request.invoke_subrequest(inner)
return Response(response.body, content_type="application/json")
def with_subrequest(config):
config.add_subscriber(lambda e: sub_events.append(("NewRequest", e.request.path)), NewRequest)
config.add_subscriber(lambda e: sub_events.append(("NewResponse", e.request.path)), NewResponse)
config.add_route("outer", "/outer")
config.add_view(outer, route_name="outer")
print(build(with_subrequest).get("/outer").json)
print(sub_events)
The first line is the shelf JSON. The second is [('NewRequest', '/outer'), ('NewRequest', '/shelves/ada'),
('NewResponse', '/shelves/ada'), ('NewResponse', '/outer')]: the events nest like the requests. Per-request state
that a NewRequest subscriber sets on request is on the subrequest’s own request object, a fresh one, so the
request id example would generate a second id for the subrequest, and a subscriber that increments a counter would
count twice.
Source: Pyramid: Subrequests
Pitfalls and Security Notes
| Pitfall | What to do |
|---|---|
The decorator form of |
Run |
A subscriber is slow or blocks. |
Subscribers run synchronously in the request. Enqueue the work and return. |
A subscriber of |
The error bypasses the exception views and reaches the server. Keep those subscribers trivial; prefer a tween. |
A side effect (email, message) fires before a transaction aborts. |
Register an after-commit hook with the |
A subscriber predicate fits only some events. |
Register it only for events that have the attributes it reads; Pyramid does not check. |
Two |
The order is undefined. Check |
A tween is registered with the function instead of its dotted name. |
|
Headers do not appear on error pages. |
The tween is under |
An explicit |
Include |
A tween holds mutable state. |
Every thread of the server calls it. Guard shared state with a lock, or avoid it. |
A deriver below |
It receives the raw value of the view. Place it over |
|
Add |
A request method silently replaces a request-factory attribute with the same name. |
Choose names that do not exist on |
|
You are outside a request, or in another thread. Pass the request in. |
A client-supplied header is copied into a header, a log line or a response. |
Validate it first, as the request id example does. Do not echo raw header values. |
Security notes follow from the mechanisms. A tween or a deriver sits on the path of every request or view, so an error
in one is an outage and an omission is a hole: put authentication and authorization in the security policy and in view
permissions, not in an ad hoc tween, and use tweens for headers and limits. If Server-Timing or any similar header is
public, it reveals how long your code takes, so enable it behind a setting, as the example does, and not in a public
production configuration. The debug toolbar is itself a tween and an event subscriber; keep it out of production
configuration files, and see the Logging & Debug Toolbar page of this section for its development use. Behind a proxy
or a load balancer, decide who is allowed to set X-Request-ID and the forwarding headers: a request id from the
client is data, not a fact.
What Changed and Where Older Material Is Outdated
| Version | Change that matters for this page |
|---|---|
1.2 |
Tweens and |
1.4 |
|
1.5 |
|
1.6 |
The response factory ( |
1.7 |
View derivers, the |
1.9 |
|
1.9.1 |
|
2.0 |
|
2.1 |
|
Older books and blog posts show @subscriber functions that take two parameters, custom authentication policies
instead of security policies, and tweens added with a callable instead of a dotted name; none of that works on
2.1. They also still recommend get_current_request() as a shortcut inside models, which the current
documentation discourages. The __init__ of a tween class takes (handler, registry) and nothing else, so code that
builds the class from a Configurator method or passes extra arguments needs a wrapper factory.
Source: Pyramid: Change History
Summary
-
Events are one-way notifications. Subscribe with
add_subscriberor@subscriberplus a scan; return values are ignored, order is undefined and everything is synchronous. Define your own events and fire them withrequest.registry.notify; subscribe to a base class to receive its subclasses. -
The events
NewRequest,BeforeTraversalandContextFoundfire inside the tweens;BeforeRenderfires inside the renderer;NewResponseand the response callbacks fire outside the tweens, and a failure there reaches the server. -
A tween wraps the whole request. Register it by dotted name; order it with
overandunderagainstINGRESS,MAIN,EXCVIEWand other tweens; inspect it withptweens; override it, rarely, withpyramid.tweens. -
A view deriver wraps each view at configuration time and can read view options, with its own ordering.
-
The root, request and response factories,
add_request_method, response and finished callbacks and response adapters replace single parts of the machinery. -
get_current_request()exists for the framework; application code passes the request.
Pages of later groups of this section build on these hooks: Extending & Add-ons packages tweens, derivers and directives into an add-on, Logging & Debug Toolbar shows the toolbar’s tween and a request-logging subscriber, Testing exercises the thread-local stack, and Deployment covers the proxy headers mentioned above.
References
-
Pyramid: Using Hooks (tweens, subscriber predicates, view derivers, request and response factories, callbacks)