Command Line & p-scripts
|
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 (released 2026-03-11, Python 3.10 or newer), plaster_pastedeploy 1.0.1,
PasteDeploy 3.1.0, hupper 1.12.1 and Waitress 3.0.2, checked on 2026-10-07. Every Python block was executed on Python
3.13 and every command line and its output was produced against the Bookshelf Shelves project of this section
(installed in editable mode, with the files shown below). A web framework is only half of the tooling a team needs: the
other half is the set of commands that start the application, show what it is made of, poke it without a browser and run
maintenance jobs in the same environment as the web process. Pyramid ships seven such commands, collectively called
p-scripts ("Pyramid console scripts"), plus a small API (pyramid.paster) for writing your own. They are Pyramid’s
counterpart of the flask command described in CLI & Shell in Flask, of
manage.py in Django and of the main() plus
tornado.options combination in Running & Deploying Tornado. All of
them read the project layout generated in Cookiecutter Projects & pserve;
this page assumes that page and Settings, Environment & .ini Files.
The Big Picture
The p-scripts have one idea in common: the .ini file is the entry point. None of them imports your application by
name. Each takes a config URI (development.ini, or development.ini#main to pick a section), asks
plaster for a loader for that file, and the loader (the
plaster_pastedeploy plugin for .ini files) builds the WSGI application through PasteDeploy. From then on every
script does something different with the application it got back.
Source: Pyramid: Command-Line Pyramid
The seven commands, with what they print or do:
| Command | Purpose | Needs a running server? |
|---|---|---|
|
Serve the application with the server named in the |
It is the server. |
|
Interactive Python shell with the application, its registry and a request already loaded. |
No |
|
Table of every route: name, pattern, view and request methods. |
No |
|
For one URL, every route and view that could match, with predicates and permission. |
No |
|
The tween chain, implicit and explicit. |
No |
|
Run one request through the application and print the response. |
No (no socket is opened) |
|
Pyramid version, platform and every installed distribution. |
No (does not load the application) |
All of them except pdistreport take the same two kinds of positional arguments: the config URI and, after it, any
number of config variables written name=value. Those variables become PasteDeploy defaults, so a .ini value may
say %(http_port)s and the command line supplies it. Since Pyramid 2.1 every script also passes the name of the
running script to the application factory as __script__ (see Telling the application it runs in a script).
Running a p-script
Each p-script is a console script installed in the bin directory of the virtual environment, so pserve
development.ini works once the environment is active. The same commands are plain modules, which lets you pass options
to the Python interpreter itself (a warning filter, -X dev, a debugger):
pserve development.ini --reload
python -m pyramid.scripts.pserve development.ini --reload
python -X dev -m pyramid.scripts.proutes development.ini
The module form was added in Pyramid 1.5. The module names are pserve, pshell, proutes, pviews, ptweens,
prequest and pdistreport, all under pyramid.scripts.
Source: Pyramid: Command-Line Pyramid
The Demo Project
To make the output below reproducible, the Bookshelf Shelves project generated in the previous pages is reduced to
what the scripts need. The debug toolbar that the cookiecutter adds to development.ini is removed (it is a
development aid, covered in the logging page of this section, and its presence changes the output of proutes and
ptweens). The routes include two routes with the same pattern, distinguished by a request method predicate:
# norun: bookshelf_shelves/routes.py
def includeme(config):
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.add_route('shelves', '/shelves')
config.add_route('shelf', '/shelves/{shelf_id}', request_method='GET')
config.add_route('shelf_archive', '/shelves/{shelf_id}', request_method='POST')
config.add_route('verify', '/verify/{code}')
Source: Pyramid: URL Dispatch
The views read from a small in-memory model. The verify route deliberately has no view yet, which is useful to see how
the scripts report a route that leads nowhere:
# norun: bookshelf_shelves/views/default.py
from pyramid.httpexceptions import HTTPNotFound
from pyramid.view import view_config
from ..models import SHELVES, get_shelf
@view_config(route_name='home', renderer='json')
def home(request):
return {'project': 'Bookshelf Shelves'}
@view_config(route_name='shelves', renderer='json', request_method='GET')
def list_shelves(request):
return {'shelves': list(SHELVES.values())}
@view_config(route_name='shelves', renderer='json', request_method='POST')
def add_shelf(request):
return {'created': request.json_body['name']}
@view_config(route_name='shelf', renderer='json')
def show_shelf(request):
shelf = get_shelf(request.matchdict['shelf_id'])
if shelf is None:
raise HTTPNotFound()
return shelf
@view_config(route_name='shelf_archive', renderer='json', request_param='confirm')
def archive_shelf(request):
return {'archived': request.matchdict['shelf_id']}
Source: Pyramid: View Configuration
A tween records how long each request took, so that ptweens has something to show (tweens are explained in the
events, tweens and hooks page of this section):
# norun: bookshelf_shelves/tweens.py
import time
def timing_tween_factory(handler, registry):
def timing_tween(request):
started = time.perf_counter()
response = handler(request)
elapsed_ms = (time.perf_counter() - started) * 1000
response.headers['X-Elapsed-Ms'] = f'{elapsed_ms:.1f}'
return response
return timing_tween
Source: Pyramid: Using Hooks
The application factory includes the routes, registers the tween and scans for views, exactly as the cookiecutter
main does. The configuration lives in development.ini; the last sections are the ones the p-scripts add to the
cookiecutter file and are explained below:
# norun: bookshelf_shelves/__init__.py
from pyramid.config import Configurator
def main(global_config, **settings):
"""Return the Bookshelf Shelves WSGI application."""
with Configurator(settings=settings) as config:
config.include('.routes')
config.add_tween('bookshelf_shelves.tweens.timing_tween_factory')
config.scan()
return config.make_wsgi_app()
# development.ini (excerpt)
[app:main]
use = egg:bookshelf_shelves
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
[server:main]
use = egg:waitress#main
listen = localhost:6543
[pshell]
setup = bookshelf_shelves.pshell_setup.setup
models = bookshelf_shelves.models
[proutes]
format = name pattern view method
[pserve]
open_url = http://localhost:6543/
The logging sections of the file ([loggers], [handlers], [formatters]) are unchanged from the cookiecutter and are
read by pserve, pshell, proutes, pviews, ptweens and prequest through the loader. The package is installed
with pip install -e ., which is what makes egg:bookshelf_shelves resolvable and what generates the console scripts
described in Exposing a Script as a Command.
pserve: Serving the Application
pserve reads the [server:main] section to find a server and the [app:main] section to find the application,
builds both, and calls the server with the application. Its options (all verified against pserve --help of 2.1):
| Option | Meaning |
|---|---|
|
The |
|
Zero or more |
|
Load the application section with this name instead of |
|
Use the |
|
Use the named server type. Do not rely on it (see the pitfalls below). |
|
Restart the process when a watched file changes. |
|
Seconds between checks when the file monitor has to poll. |
|
Open the page named by |
|
More or less of pserve’s own messages. |
The plain form starts Waitress on the address of the listen setting. The output shows the process id from pserve and
one Serving on line per address Waitress binds:
pserve development.ini
Starting server in PID 61363.
2026-10-07 18:32:50,250 INFO [waitress:449][MainThread] Serving on http://[::1]:18765
2026-10-07 18:32:50,250 INFO [waitress:449][MainThread] Serving on http://127.0.0.1:18765
Source: Pyramid: pserve
Config variables
A value that changes between machines (a port, a path) can be left as an interpolation in the file and supplied when
the command runs, the way the official page describes with http_port=8080:
[server:main]
use = egg:waitress#main
listen = localhost:%(http_port)s
pserve development.ini http_port=8080
The same variables reach your application factory in the global_config argument, and they are what the options
argument of pyramid.paster.get_app() and bootstrap() stands for in scripts. For anything secret, prefer environment
variables read in the application over command-line variables, which are visible in the process list.
Source: Pyramid: pserve
Reloading while you develop
--reload starts a monitor process that runs the real server in a child and restarts it when a Python module the child
imported changes. Since Pyramid 1.8 the monitor is the hupper
library. It also watches the .ini file itself and the server’s configuration file. Touching a module produces:
Starting monitor for PID 61433.
Starting server in PID 61433.
...Serving on http://127.0.0.1:18766
.../bookshelf_shelves/models.py changed; reloading ...
Gracefully killing the server.
Starting monitor for PID 61472.
Starting server in PID 61472.
...Serving on http://127.0.0.1:18766
Files that are not Python imports, such as templates compiled lazily or data files, are not watched by default. A
[pserve] section tells the monitor about them (watch_files, relative to the .ini file or an asset specification
like package:path) or to ignore noisy ones (ignore_files). If the optional watchdog package is installed hupper
uses operating-system notifications instead of polling, and --reload-interval then matters much less:
[pserve]
watch_files =
data/shelves.csv
ignore_files =
*.swp
open_url = http://localhost:6543/
--reload is a development tool. It doubles the number of processes and exists to restart the whole interpreter, so
do not put it in a production.ini startup command.
Pitfalls
-
-sis unreliable. In the 2.1 baseline of this sectionpserve -s SERVER_TYPEraised anAttributeError. Name the server in the file instead, asuse = egg:waitress#mainoruse = egg:waitress#wsgiref, and select a different section with--server-name. -
-bneedsopen_url. The browser is opened for the URL in the[pserve]section. Without it pserve only tries aportsetting of the server section, which a Waitresslistenline does not provide, so it prints a warning that it could not determine the server URL and opens nothing. -
pserve is not a daemon manager.
--daemon,--pid-file,--log-file,--monitor-restart,--status,--user,--groupand--stop-daemonand thestart,stop,restartandstatuscommands were removed in Pyramid 1.8 after a deprecation period. Old tutorials that use them predate that. Use a process manager (systemd, a container runtime, Kubernetes) instead, as described in Docker and Kubernetes. -
The messages go to stderr. Since Pyramid 2.0 pserve writes its own verbose messages to standard error, to avoid the buffering problems of standard output.
-qsilences those messages but not the server’s own logging, which is configured by the logging sections of the file. -
Production often does not use pserve at all. A WSGI server such as Gunicorn can load the same
.inifile, which is covered by the deployment page of this section; the.inifile stays the single source of configuration.
pshell: The Interactive Shell
pshell builds the application exactly as pserve would and drops you into a Python prompt with the pieces already
bound. It is the quickest way to try a query, to see what route_url generates or to call a view by hand:
pshell development.ini
Python 3.13.15 (main, Aug 25 2026, 13:50:12) [Clang 22.1.3 ] on darwin
Type "help" for more information.
Environment:
app The WSGI application.
registry Active Pyramid registry.
request Active request object.
root Root of the default resource tree.
root_factory Default root factory used to create `root`.
Custom Variables:
models bookshelf_shelves.models
>>> request.route_url('shelf', shelf_id='1')
'https://shelves.example.com/shelves/1'
>>> models.get_shelf('2')
{'id': '2', 'name': 'Poetry', 'books': 1}
The Environment block is fixed; the Custom Variables block comes from the [pshell] section. Its options are:
| Option | Meaning |
|---|---|
|
The application to load ( |
|
Choose a shell implementation, such as |
|
Print the shells found by entry point and exit. |
|
A setup callable; overrides the |
The [pshell] section
Every key of the [pshell] section is a variable name and every value a dotted Python name resolved at start-up, which
is how the file above exposes models. Two keys are special: setup names a callable that receives the dictionary of
variables before the shell starts, and default_shell lists the preferred shells in order (added in Pyramid 1.6). The
callable may change the request, add objects or start a transaction:
# norun: bookshelf_shelves/pshell_setup.py
from webtest import TestApp
def setup(env):
request = env['request']
request.host = 'shelves.example.com'
request.scheme = 'https'
env['testapp'] = TestApp(env['app'])
Source: Pyramid: Command-Line Pyramid
Setting request.host and request.scheme is what made route_url print https://shelves.example.com/... above; without
it the request pretends to be http://localhost (see Anchoring the request). The setup callable can also be a generator,
in which case the code after yield runs when the shell exits, which is the place to roll a transaction back or close a
client. The following callable, given as --setup, prints its messages before the prompt and after it is left:
# norun: bookshelf_shelves/pshell_gen.py
def setup(env):
print('opening shelf session')
env['shelf_count'] = 2
try:
yield
finally:
print('closing shelf session')
pshell --setup bookshelf_shelves.pshell_gen.setup development.ini
opening shelf session
>>> shelf_count
2
>>> exit()
now exiting InteractiveConsole...
closing shelf session
Source: Pyramid: Command-Line Pyramid
A setup callable can be tested without a terminal, because it only needs a dictionary with a request and an application.
The following block prepares the same pieces pshell provides, using the bootstrap() function introduced in
bootstrap:
import pathlib
import tempfile
from pyramid.paster import bootstrap
from bookshelf_shelves.pshell_setup import setup
ini = pathlib.Path(tempfile.mkdtemp()) / 'script.ini'
ini.write_text("""\
[app:main]
use = egg:bookshelf_shelves
pyramid.default_locale_name = en
[app:readonly]
use = egg:bookshelf_shelves
bookshelf.read_only = true
""")
with bootstrap(str(ini)) as env:
setup(env)
print(env['request'].route_url('shelf', shelf_id='1'))
print(env['testapp'].get('/shelves/2').json)
https://shelves.example.com/shelves/1
{'id': '2', 'name': 'Poetry', 'books': 1}
Source: Pyramid: Command-Line Pyramid
Alternative shells
By default pshell uses the standard library console. The pyramid_ipython and pyramid_bpython packages register an
IPython and a bpython shell (both were moved out of Pyramid in 1.6) and, when one is installed, pshell selects it
without further options. pshell --list-shells shows what is available; in a plain environment it prints:
Available shells:
python
A different REPL is a Python function registered under the pyramid.pshell_runner entry point group. The factory returns
a function that takes the variables env and a help text. In a pyproject.toml project the entry point is declared
like any other (the official page shows the equivalent setup.py form):
[project.entry-points."pyramid.pshell_runner"]
ptpython = "bookshelf_shelves.shells:ptpython_shell_factory"
Source: Pyramid: pshell
Pitfalls
-
The shell is the real application. It reads the same
.inifile as the server, so a shell started withproduction.initalks to the production database. Open shells with a development or staging file, and close any transaction you begin (a generator setup is the safe place for that; the transaction side of this is covered in SQLAlchemy, Transactions & Migrations). -
Variables are described by their docstring. The
EnvironmentandCustom Variableshelp prints the docstring of each object you add, so adding awebtest.TestAppor anintprints a long paragraph. The variables still work; the noise is cosmetic. -
The request is synthetic. It has no body, no cookies and no matched route. Call
request.route_urlfreely, but do not expectrequest.matchdictorrequest.sessionto contain anything.
proutes: Listing Routes
proutes prints a table of every route registered in the application and, for each, the view that answers it:
proutes development.ini
Name Pattern View Method
---- ------- ---- ------
__static/ /static/*subpath bookshelf_shelves:static/ *
home / bookshelf_shelves.views.default.home *
shelves /shelves bookshelf_shelves.views.default.list_shelves GET
shelves /shelves bookshelf_shelves.views.default.add_shelf POST
shelf /shelves/{shelf_id} bookshelf_shelves.views.default.show_shelf GET
shelf_archive /shelves/{shelf_id} bookshelf_shelves.views.default.archive_shelf POST
verify /verify/{code} <unknown> *
Source: Pyramid: proutes
Reading the table:
-
The
Namecolumn is the route name. A static view appears as a route named__static/with the pattern/static/*subpathand the asset it serves, which is how static views are implemented. -
The
Viewcolumn shows the dotted name of the view callable that will be invoked.<unknown>means a route with no view attached, as the deliberateverifyroute here; a request for it would end in a 404. -
The
Methodcolumn shows the request methods the view accepts. A*means any method. When a route is attached to views for several methods (shelvesabove), it appears once per view.<route mismatch>appears when the view cannot accept any method the route allows.
Choosing columns and filtering
The columns are name, pattern, view and method. -f (--format) takes a comma separated list that selects and
orders them for one run, and the [proutes] section sets the default for the project (the demo file above sets
format = name pattern view method; a comma or whitespace separator both work). -g (--glob) keeps only the routes
whose name or pattern matches a shell-style glob:
proutes -f name,pattern,method -g '/shelves*' development.ini
Name Pattern Method
---- ------- ------
shelves /shelves GET
shelves /shelves POST
shelf /shelves/{shelf_id} GET
shelf_archive /shelves/{shelf_id} POST
Quote the glob so that the shell does not expand it. Two routes may share a pattern (shelf and shelf_archive), so
proutes is also the quickest way to see whether a predicate on one route will shadow another. Routes are matched in
the order they were added; the table is in that order too.
Pitfalls
-
prouteslists URL dispatch routes only. A resource tree used with traversal has no route table; the structure is the tree itself (see Traversal & Resources). -
It shows what the application registered at start-up. A route added dynamically while serving is not listed.
-
The output has trailing spaces on every line because the columns are padded; strip them before comparing the text.
pviews: Which View Answers a URL
pviews answers a different question from proutes: given a concrete URL path, which routes match and which views would
be selected, with their predicates and permissions. The arguments are the config URI and the path:
pviews development.ini /shelves/1
URL = /shelves/1
context: <pyramid.traversal.DefaultRootFactory object at 0x10705cb90>
view name: shelves
Route:
------
route name: shelf
route pattern: /shelves/{shelf_id}
route path: /shelves/{shelf_id}
subpath: 1
route predicates (request_method = GET,HEAD)
View:
-----
bookshelf_shelves.views.default.show_shelf.
Route:
------
route name: shelf_archive
route pattern: /shelves/{shelf_id}
route path: /shelves/{shelf_id}
subpath: 1
route predicates (request_method = POST)
View:
-----
bookshelf_shelves.views.default.archive_shelf
view predicates (request_param confirm)
Both routes are shown because both match the path; the predicates tell you which one a GET and which one a POST
would reach, and the view predicate of archive_shelf (request_param confirm) tells you that a POST without a
confirm parameter will not find a view. A GET route also lists HEAD, because Pyramid treats GET as accepting
HEAD. For a path that matches nothing, the output is a single line:
URL = /nothing
Not found.
The context, view name and subpath lines come from traversal run on the root factory (the default root factory in
this project has no children), not from the route, so for a URL dispatch application read the Route blocks and ignore
those three lines. The official page uses development.ini#tutorial (a named section) in its examples; the section name
may be omitted, as above.
Source: Pyramid: Command-Line Pyramid
ptweens: The Tween Chain
A tween is a piece of code between the router and the application that runs for every request. ptweens prints the order
in which they wrap the application:
ptweens development.ini
"pyramid.tweens" config value NOT set (implicitly ordered tweens used)
Implicit Tween Chain
Position Name
-------- ----
- INGRESS
0 bookshelf_shelves.tweens.timing_tween_factory
1 pyramid.tweens.excview_tween_factory
- MAIN
INGRESS is where the request enters and MAIN is the router that finds the view; position 0 is the outermost tween.
The implicit chain is computed from the over and under arguments of add_tween() calls. An explicit chain is set
by the pyramid.tweens setting, which lists every tween (including pyramid.tweens.excview_tween_factory, which turns
exceptions into responses) one per line, outermost first. When it is set, ptweens prints both chains and says which one
is used:
[app:main]
use = egg:bookshelf_shelves
pyramid.tweens = bookshelf_shelves.tweens.timing_tween_factory
pyramid.tweens.excview_tween_factory
"pyramid.tweens" config value set (explicitly ordered tweens used)
Explicit Tween Chain (used)
Position Name
-------- ----
- INGRESS
0 bookshelf_shelves.tweens.timing_tween_factory
1 pyramid.tweens.excview_tween_factory
- MAIN
Implicit Tween Chain (not used)
...
The pitfall that makes ptweens worth running is the explicit chain: once pyramid.tweens is set, it replaces the
implicit ordering completely, so a tween added by an add-on and not listed in the setting is silently left out. A tween
that is in the file but missing from the output is the symptom. The official example, which has the debug toolbar
enabled in development.ini, shows it as pyramid_debugtoolbar.toolbar.toolbar_tween_factory with the alias pdbt at
position 0; in production that line must not be there.
Source: Pyramid: ptweens
prequest: One Request Without a Server
prequest runs a single request through the application and prints the response body, without opening a socket. The
arguments are the config URI and the path (with an optional query string):
prequest -d development.ini /shelves/1
200 OK
Content-Type: application/json
Content-Length: 43
X-Elapsed-Ms: 0.2
b'{"id": "1", "name": "Classics", "books": 3}'
The X-Elapsed-Ms header is the one the demo tween adds. The options are:
| Option | Meaning |
|---|---|
|
Print the status and headers before the body. |
|
|
|
Add a request header; repeat for several. |
|
Send an HTTP Basic |
|
The application section to load ( |
A request a view rejects is reported like any other. A missing shelf and a predicate mismatch both come out as 404, and the second also says why:
prequest -d -m POST development.ini /shelves/1
404 Not Found
X-Elapsed-Ms: 0.3
Content-Type: text/html; charset=UTF-8
Content-Length: 217
<html>
<head>
<title>404 Not Found</title>
</head>
<body>
<h1>404 Not Found</h1>
The resource could not be found.<br/><br/>
predicate mismatch for view archive_shelf (request_param confirm)
...
The same request with the path /shelves/1?confirm=yes returns {"archived": "1"}.
Source: Pyramid: prequest
What prequest is and is not
-
It builds a minimal request. The WSGI environment is synthetic: host
localhost, port 80, schemehttp, protocolHTTP/1.0, client address127.0.0.1, and anAcceptheader oftext/plain;q=1.0, */*;q=0.1. There are no proxy headers and no cookies, so it cannot reproduce a problem that depends onX-Forwarded-*, on HTTPS or on a session. -
A response without a charset is printed as a bytes literal. JSON responses have no charset, which is why the body is shown as
b'...'above. Text responses with a charset print as text. -
Posting a body from standard input does not work reliably in 2.1. The official page shows
prequest -mPOST development.ini / < somefile, and the script passes standard input to the application as the request body. On Python 3 that stream is a text stream: in the 2.1 baseline of this section a view that readrequest.json_bodysaw an empty body and a view that readrequest.body_fileraised aTypeError. Usecurlagainstpserve, or WebTest (see the testing page of this section), for requests with bodies. The method list on the official narrative page (GET, HEAD, POST, DELETE) is also shorter than the one in the 2.1 command (--helplists eight methods). -
Use it for smoke tests and for reading a response. A health check in CI or a container, or confirming that a page renders in a production-like configuration, are good uses; anything with state belongs in a real test.
pdistreport: What Is Installed
pdistreport is the one p-script that does not load your application. It prints the Pyramid version, the platform and
every distribution found in the environment, which is what a maintainer asks for when you report a bug. It takes no
options and no config URI:
pdistreport
Pyramid version: 2.1
Platform: macOS-27.0.1-arm64-arm-64bit-Mach-O
Packages:
alembic 1.20.0
A database migration tool for SQLAlchemy.
annotated-types 0.8.0
Reusable constraint types to use with typing.Annotated
...
It was added in Pyramid 1.5 and, since 2.1, no longer depends on pkg_resources (it reads importlib.metadata). One
caveat from the 2.1 source: for each distribution it writes the Summary metadata field, and a distribution without that
field makes the command fail with TypeError: write() argument must be str, not None. In the environment used for this
page the ZEO distribution has no summary, so pdistreport stopped before printing the list. Fall back to
python -m pip list or python -m pip freeze in that case, and report the exact versions yourself.
Source: Pyramid: pdistreport
Writing Your Own Scripts
Maintenance jobs (import a catalogue, send a digest, recompute counters) want the same configuration and the same
application code as the web process. A plain script that imports your modules does not have that environment: the
registry is missing, request.route_url has no application to resolve routes, renderers cannot find their settings.
Pyramid solves it with pyramid.paster, which loads the application from an .ini file and then sets up the environment
a request would have.
bootstrap
bootstrap(config_uri, request=None, options=None) loads the application, builds a request, pushes it and the registry
on the thread-local stack and returns a dictionary. Used as a context manager (since 1.8) it cleans up for you:
from pyramid.paster import bootstrap
with bootstrap(str(ini)) as env:
print(sorted(env))
print(env['request'].route_url('shelf', shelf_id='1'))
['app', 'closer', 'registry', 'request', 'root', 'root_factory']
http://localhost/shelves/1
Source: Pyramid API: pyramid.paster
The keys are:
| Key | Value |
|---|---|
|
A |
|
The WSGI application (a router, for a plain Pyramid section). |
|
The resource root produced by the root factory. |
|
The application registry; |
|
A no-argument callable that pops the thread-local stack. It is called for you by the |
|
The root factory used for |
If you do not use with, call the closer yourself when the job ends:
env = bootstrap(str(ini))
try:
...
finally:
env['closer']()
Since 2.0 the closer also runs the request’s finished callbacks (see the request and response page of this section for
add_finished_callback), so resources released that way, such as database sessions, are released in scripts too, but only
if you actually close.
Source: Pyramid API: pyramid.paster
Anchoring the request
By default the request is for http://localhost:80/, so every URL generated in a script, such as a link in an e-mail,
points at localhost. This is the most common mistake in Pyramid scripts. Tell Pyramid where the application really
lives by building the request yourself and passing it in:
from pyramid.request import Request
request = Request.blank('/', base_url='https://shelves.example.com/app')
with bootstrap(f'{ini}#readonly', request=request) as env:
request = env['request']
print(request.application_url)
print(request.route_url('verify', code='1337'))
print(env['registry'].settings['bookshelf.read_only'])
https://shelves.example.com/app
https://shelves.example.com/app/verify/1337
true
The example also loads the section named readonly of the file (the #readonly fragment), which is how a project keeps
one .ini file with several applications, for instance a job configuration next to the web one. Without a fragment the
section is main. The options argument supplies config variables, like the command-line ones of pserve. In a real
job read the base URL from your own settings or an environment variable instead of hardcoding it.
Source: Pyramid: Command-Line Pyramid
get_app, get_appsettings and prepare
bootstrap() is built from smaller functions that are useful on their own:
| Function | Use |
|---|---|
|
Return the WSGI application, for example to give it to WebTest or a WSGI server, without the request environment. |
|
Return the key/value pairs of the app section as a dictionary-like object, without building the application. Cheap and side-effect free, ideal for a script that only needs a setting. |
|
Push a request and registry onto the thread-local stack and return a dictionary with |
|
Return |
from pyramid.paster import get_app, get_appsettings
settings = get_appsettings(str(ini))
print(settings['pyramid.default_locale_name'], type(settings).__name__)
app = get_app(f'{ini}#readonly')
print(type(app).__name__)
en ConfigDict
Router
For a file that loads more than one Pyramid application, bootstrap() sets up the environment of the last one loaded. The
API page describes loading a specific one with get_app() and prepare() together with pyramid.config.global_registries.
Source: Pyramid API: pyramid.scripting
Logging in scripts
bootstrap() does not configure logging. The [loggers], [handlers] and [formatters] sections are read by the
p-scripts that ship with Pyramid, but a script of your own has to ask for them with setup_logging(); before that call
only the Python defaults apply, which means warnings and nothing more:
from pyramid.paster import setup_logging
setup_logging(str(ini))
setup_logging(config_uri, global_conf=None) takes the same file#name form. Call it first, before
bootstrap(), so that the messages emitted while the application is built are captured too. A file with no logging
sections is accepted. The logging page of this section covers the logging configuration itself.
Source: Pyramid: Logging
Telling the application it runs in a script
Since Pyramid 2.1 every script (all p-scripts, and bootstrap() with the value bootstrap) adds an option named
__script__ to the variables used to load the application. With plaster_pastedeploy it arrives in global_config, so
an application factory can skip work that makes no sense outside a server, such as starting a background thread or
connecting to a message broker:
# norun: application factory sketch
def main(global_config, **settings):
script = global_config.get('__script__') # None under a plain WSGI server
with Configurator(settings=settings) as config:
config.include('.routes')
if script is None:
... # start workers only when serving requests
config.scan()
return config.make_wsgi_app()
The values seen in this project were proutes when started by proutes, prequest when started by prequest and
bootstrap when started by bootstrap(); pserve sets pserve. A WSGI server that loads the file itself does not set
it, so the value is None there.
Exposing a Script as a Command
A script becomes a first-class command by declaring it as a console-script entry point. The function takes the
command-line arguments, calls setup_logging() and bootstrap(), and returns the exit status. The official page uses
optparse; argparse is the modern equivalent and is what the p-scripts themselves use since Pyramid 1.8:
# norun: bookshelf_shelves/scripts.py
"""Console scripts for Bookshelf Shelves."""
import argparse
import sys
from pyramid.paster import bootstrap, setup_logging
from pyramid.request import Request
from .models import SHELVES
def shelf_report(argv=None):
parser = argparse.ArgumentParser(description='Print a report of all shelves.')
parser.add_argument('config_uri', help='for example development.ini')
parser.add_argument('--base-url', default='http://localhost:6543',
help='public base URL used when generating links')
args = parser.parse_args(argv)
setup_logging(args.config_uri)
request = Request.blank('/', base_url=args.base_url)
with bootstrap(args.config_uri, request=request) as env:
request = env['request']
for shelf in SHELVES.values():
url = request.route_url('shelf', shelf_id=shelf['id'])
print(f"{shelf['name']:<10} {shelf['books']:>3} books {url}")
return 0
if __name__ == '__main__':
sys.exit(shelf_report())
With a pyproject.toml project (the cookiecutter 2.1 branch generates one, not a setup.py), the command is declared in
[project.scripts], next to the paste.app_factory entry point that makes egg:bookshelf_shelves work:
[project.entry-points."paste.app_factory"]
main = "bookshelf_shelves:main"
[project.scripts]
shelf-report = "bookshelf_shelves.scripts:shelf_report"
The older setup.py form shown in the official page is entry_points={'console_scripts': ['show_settings =
myproject.scripts:settings_show']}; both produce the same executable. After editing the entry points, reinstall the
project (pip install -e .) so that the new command is generated in the environment’s bin directory. Then:
shelf-report development.ini --base-url https://shelves.example.com/app
Classics 3 books https://shelves.example.com/app/shelves/1
Poetry 1 books https://shelves.example.com/app/shelves/2
Testing a script
Because the function takes argv and returns the exit status, it can be called from a test with a temporary .ini file.
Capture the output with contextlib.redirect_stdout:
import contextlib
import io
from bookshelf_shelves.scripts import shelf_report
out = io.StringIO()
with contextlib.redirect_stdout(out):
status = shelf_report([str(ini), '--base-url', 'https://shelves.example.com/app'])
assert status == 0
assert out.getvalue().splitlines()[0] == (
'Classics 3 books https://shelves.example.com/app/shelves/1'
)
print(out.getvalue(), end='')
Classics 3 books https://shelves.example.com/app/shelves/1
Poetry 1 books https://shelves.example.com/app/shelves/2
The pytest fixtures of the cookiecutter project use the same testing.ini and bootstrap-style loading, which the
testing page of this section describes.
Pitfalls in scripts
-
Always close the environment. Without the
withblock orcloser(), the thread-local stack keeps a reference to the request and registry, and finished callbacks (including those that close database sessions) never run. This matters in long-running workers that callbootstrap()repeatedly. -
Commit explicitly. A script has no request-wide transaction manager unless you start one; code that relies on
pyramid_tmcommitting at the end of a request does nothing in a script. Begin and commit (or abort) the transaction in the script, as described in SQLAlchemy, Transactions & Migrations. -
Do not use a web
.iniblindly. The application section ofdevelopment.inimay include development aids (the debug toolbar, template reloading). A script should load a section or file meant for it, which is the#readonlypattern above or a dedicated file. -
Secrets stay in the environment. A script that takes a database URL or API key on the command line leaks it into the process list; read it from the settings or an environment variable instead, as in Settings, Environment & .ini Files.
-
Containers and schedulers. A Kubernetes
Jobor a containercommandcan run a console script exactly likepserve; see Docker and Kubernetes for the packaging side, and keep the script idempotent because schedulers may retry it.
What Changed Over the Versions
| Version | Change |
|---|---|
1.1 |
|
1.5 |
The p-scripts can be run as |
1.6 |
|
1.8 |
|
2.0 |
|
2.1 |
The |
Where old material is out of date: tutorials and blog posts that start a project with pcreate, use setup.py develop,
run pserve --daemon or pserve start, or write [server:main] with use = egg:Paste#http predate Pyramid 1.8 and the
cookiecutter workflow. The pshell and proutes examples in the official narrative pages show older interpreter
banners and older default columns; run the command in your own project and trust that output.