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.

flowchart TD start(["make_wsgi_app(): ApplicationCreated, once per process"]) -.-> server server["WSGI server calls the router"] --> tl["router pushes registry and request on the thread-local stack"] tl --> outer["outer tweens: your tweens added over EXCVIEW, pyramid_tm"] outer --> exc["excview tween: exception to exception view"] exc --> inner["inner tweens: added under EXCVIEW"] inner --> e1(["NewRequest"]) e1 --> match["route mapper"] match --> e2(["BeforeTraversal"]) e2 --> trav["root factory, then traverser"] trav --> e3(["ContextFound"]) e3 --> lookup["view lookup and view predicates"] lookup --> der["view derivers, outermost first: secured_view, csrf_view, owrapped_view, http_cached_view, decorated_view, your derivers, rendered_view, mapped_view"] der --> view["view callable"] view --> rend["rendered_view runs the renderer: BeforeRender"] rend --> back["response travels back out through the tweens"] back --> cb["response callbacks"] cb --> e4(["NewResponse"]) e4 --> fin["finished callbacks, then thread locals popped"]
Figure 1. One request through a Pyramid application: tweens, events, view derivers and callbacks

Three consequences follow from the figure, and each one is a source of bugs later in the page.

  1. NewRequest, BeforeTraversal and ContextFound fire 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. NewResponse and the response callbacks run after the outermost tween has returned, so an exception raised there reaches the WSGI server.

  2. 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.

  3. 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 NewRequest

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 try and finally.

Subscriber on ContextFound

Auditing which route and context matched, per-context bookkeeping that needs request.context.

Rejecting the request: raise an HTTP exception from a view or a tween instead.

Subscriber on BeforeRender

Adding names that every template can use.

Per-request database queries, because it runs for every rendered response, error pages included.

Subscriber on NewResponse

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 (pyramid_tm is a tween).

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.

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.

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.

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

ApplicationCreated (IApplicationCreated; older alias WSGIApplicationCreatedEvent)

app

When make_wsgi_app() returns the router. Once per call, hence once per process of a WSGI server. Use it for start-up validation and warm-up; the Configuration page has a worked example.

NewRequest (INewRequest)

request

As the router starts to process a request, before the route mapper runs. The request has no matchdict, matched_route or context yet.

BeforeTraversal (IBeforeTraversal; since 1.7)

request

After the route mapper has tried the routes and before the root factory and traversal. request.matched_route and request.matchdict exist only if a route matched.

ContextFound (IContextFound; older alias AfterTraversal)

request

After traversal found a context and before any view code runs. request.context, request.view_name and request.subpath are set.

NewResponse (INewResponse)

request, response

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, request.exception is set.

BeforeRender (IBeforeRender)

a dictionary of the renderer globals, plus rendering_val

Just before a renderer runs, for every renderer including JSON. A subscriber adds names to the globals with event["name"] = value.

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.

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.

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.

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.

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.

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,
)

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=X places the tween closer to the request ingress than X: X is called inside it.

  • under=X places 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_tm if 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.

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.

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.

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

secured_view

Enforces the permission of the view. A no-op when the view has no permission. With a default permission set by set_default_permission there is always one, except for exception views.

csrf_view

Checks the CSRF token when require_csrf is true for the view. Placed between secured_view and owrapped_view. See CSRF Protection.

owrapped_view

Invokes the wrapper named by the wrapper option.

http_cached_view

Applies the cache headers given by the http_cache option, unless the pyramid.prevent_http_cache setting is enabled.

decorated_view

Wraps the view with the decorators of the decorator option.

rendered_view

Turns the value returned by the view into a response, using the renderer. Below this point the result may be any Python object.

mapped_view

Applies the view mapper, and standardizes the call to (context, request) for everything above it. It is always the closest to your code.

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.

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.

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.

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 request argument;

  • 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.

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.

Pitfalls and Security Notes

Pitfall What to do

The decorator form of @subscriber or @view_config does nothing.

Run config.scan() on a package that contains the module. A silent absence of behaviour is the symptom.

A subscriber is slow or blocks.

Subscribers run synchronously in the request. Enqueue the work and return.

A subscriber of NewResponse raises.

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 transaction package, or publish after the commit.

A subscriber predicate fits only some events.

Register it only for events that have the attributes it reads; Pyramid does not check.

Two BeforeRender subscribers set the same key.

The order is undefined. Check "key" in event first and use distinctive names.

A tween is registered with the function instead of its dotted name.

add_tween needs a dotted name to a globally importable object. Define the factory at module level.

Headers do not appear on error pages.

The tween is under EXCVIEW. Put tweens that must see every response over EXCVIEW, tweens that must see exceptions under it.

An explicit pyramid.tweens list disables exception views, or drops an add-on’s tween.

Include pyramid.tweens.excview_tween_factory and every add-on tween in the list, or avoid the setting.

A tween holds mutable state.

Every thread of the server calls it. Guard shared state with a lock, or avoid it.

A deriver below rendered_view assumes a response object.

It receives the raw value of the view. Place it over rendered_view, or handle both.

over="secured_view" alone fails with a cycle.

Add under=INGRESS, because the default constraints put every deriver under decorated_view.

A request method silently replaces a request-factory attribute with the same name.

Choose names that do not exist on Request.

get_current_request() returns None.

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 ptweens were introduced.

1.4

add_request_method and the subscriber predicates were introduced; subscribers take a single event argument, and older material that subscribed with several arguments is obsolete.

1.5

not_ can invert any view, route or subscriber predicate value.

1.6

The response factory (set_response_factory, response_factory).

1.7

View derivers, the BeforeTraversal event and its interface. The exception-view tween re-raises the original exception when no exception view handles it.

1.9

request.exception and request.exc_info are set only if the exception-view tween generated the response. The Configurator can be used as a context manager that pushes and pops thread locals and commits.

1.9.1

_depth and _category on @subscriber.

2.0

property=True request methods can be assigned to; the scan() categories default to pyramid.

2.1

__script__ is passed to the application factory by pserve, pshell, prequest, proutes, ptweens and pviews; permission checks on exception views were fixed. The events, tweens and derivers described here are unchanged.

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.

Summary

  • Events are one-way notifications. Subscribe with add_subscriber or @subscriber plus a scan; return values are ignored, order is undefined and everything is synchronous. Define your own events and fire them with request.registry.notify; subscribe to a base class to receive its subclasses.

  • The events NewRequest, BeforeTraversal and ContextFound fire inside the tweens; BeforeRender fires inside the renderer; NewResponse and 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 over and under against INGRESS, MAIN, EXCVIEW and other tweens; inspect it with ptweens; override it, rarely, with pyramid.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.