summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml13
-rw-r--r--BFG_HISTORY.rst (renamed from BFG_HISTORY.txt)0
-rw-r--r--CHANGES.rst44
-rw-r--r--CHANGES.txt21
-rw-r--r--CONTRIBUTORS.txt24
-rw-r--r--HACKING.txt7
-rw-r--r--HISTORY.rst (renamed from HISTORY.txt)205
-rw-r--r--MANIFEST.in16
-rw-r--r--README.rst20
-rw-r--r--RELEASING.txt70
-rw-r--r--contributing.md10
-rw-r--r--docs/_static/pyramid_request_processing.graffle244
-rw-r--r--docs/_static/pyramid_request_processing.pngbin130688 -> 135179 bytes
-rw-r--r--docs/_static/pyramid_request_processing.svg2
-rw-r--r--docs/api/config.rst2
-rw-r--r--docs/api/csrf.rst23
-rw-r--r--docs/api/interfaces.rst6
-rw-r--r--docs/api/paster.rst6
-rw-r--r--docs/api/security.rst22
-rw-r--r--docs/api/session.rst4
-rw-r--r--docs/api/url.rst2
-rw-r--r--docs/changes.rst6
-rw-r--r--docs/conf.py39
-rw-r--r--docs/copyright.rst4
-rw-r--r--docs/designdefense.rst26
-rw-r--r--docs/glossary.rst90
-rw-r--r--docs/index.rst15
-rw-r--r--docs/narr/advanced-features.rst330
-rw-r--r--docs/narr/configuration.rst12
-rw-r--r--docs/narr/firstapp.rst20
-rw-r--r--docs/narr/helloworld.py9
-rw-r--r--docs/narr/hooks.rst6
-rw-r--r--docs/narr/i18n.rst6
-rw-r--r--docs/narr/install.rst10
-rw-r--r--docs/narr/introduction.rst984
-rw-r--r--docs/narr/logging.rst42
-rw-r--r--docs/narr/myproject/README.txt2
-rw-r--r--docs/narr/myproject/development.ini6
-rw-r--r--docs/narr/myproject/myproject/templates/layout.jinja212
-rw-r--r--docs/narr/myproject/myproject/templates/mytemplate.jinja22
-rw-r--r--docs/narr/myproject/production.ini4
-rw-r--r--docs/narr/myproject/setup.py1
-rw-r--r--docs/narr/paste.rst14
-rw-r--r--docs/narr/project.rst33
-rw-r--r--docs/narr/scaffolding.rst2
-rw-r--r--docs/narr/security.rst216
-rw-r--r--docs/narr/sessions.rst187
-rw-r--r--docs/narr/startup.rst21
-rw-r--r--docs/narr/subrequest.rst2
-rw-r--r--docs/narr/templates.rst12
-rw-r--r--docs/narr/testing.rst14
-rw-r--r--docs/narr/webob.rst4
-rw-r--r--docs/quick_tour.rst55
-rw-r--r--docs/quick_tour/hello_world/app.py8
-rw-r--r--docs/quick_tour/jinja2/app.py10
-rw-r--r--docs/quick_tour/json/app.py14
-rw-r--r--docs/quick_tour/logging/README.txt2
-rw-r--r--docs/quick_tour/logging/development.ini6
-rw-r--r--docs/quick_tour/logging/hello_world/templates/layout.jinja212
-rw-r--r--docs/quick_tour/logging/hello_world/templates/mytemplate.jinja22
-rw-r--r--docs/quick_tour/logging/production.ini4
-rw-r--r--docs/quick_tour/logging/setup.py1
-rw-r--r--docs/quick_tour/package/README.txt2
-rw-r--r--docs/quick_tour/package/development.ini6
-rw-r--r--docs/quick_tour/package/hello_world/templates/layout.jinja212
-rw-r--r--docs/quick_tour/package/hello_world/templates/mytemplate.jinja22
-rw-r--r--docs/quick_tour/package/production.ini4
-rw-r--r--docs/quick_tour/package/setup.py1
-rw-r--r--docs/quick_tour/requests/app.py8
-rw-r--r--docs/quick_tour/routing/app.py10
-rw-r--r--docs/quick_tour/sessions/README.txt2
-rw-r--r--docs/quick_tour/sessions/development.ini6
-rw-r--r--docs/quick_tour/sessions/hello_world/templates/layout.jinja212
-rw-r--r--docs/quick_tour/sessions/hello_world/templates/mytemplate.jinja22
-rw-r--r--docs/quick_tour/sessions/production.ini4
-rw-r--r--docs/quick_tour/sessions/setup.py1
-rw-r--r--docs/quick_tour/sqla_demo/README.txt2
-rw-r--r--docs/quick_tour/sqla_demo/development.ini8
-rw-r--r--docs/quick_tour/sqla_demo/production.ini6
-rw-r--r--docs/quick_tour/sqla_demo/setup.py6
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py4
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/models/meta.py2
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja212
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja22
-rw-r--r--docs/quick_tour/static_assets/app.py12
-rw-r--r--docs/quick_tour/templating/app.py10
-rw-r--r--docs/quick_tour/view_classes/app.py14
-rw-r--r--docs/quick_tour/views/app.py14
-rw-r--r--docs/quick_tour/views/views.py6
-rw-r--r--docs/quick_tutorial/authentication.rst2
-rw-r--r--docs/quick_tutorial/authentication/development.ini4
-rw-r--r--docs/quick_tutorial/authentication/setup.py1
-rw-r--r--docs/quick_tutorial/authentication/tutorial/views.py3
-rw-r--r--docs/quick_tutorial/authorization/development.ini4
-rw-r--r--docs/quick_tutorial/authorization/setup.py1
-rw-r--r--docs/quick_tutorial/authorization/tutorial/views.py3
-rw-r--r--docs/quick_tutorial/cookiecutters.rst16
-rw-r--r--docs/quick_tutorial/cookiecutters/README.txt2
-rw-r--r--docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja212
-rw-r--r--docs/quick_tutorial/cookiecutters/cc_starter/templates/mytemplate.jinja22
-rw-r--r--docs/quick_tutorial/cookiecutters/development.ini6
-rw-r--r--docs/quick_tutorial/cookiecutters/production.ini4
-rw-r--r--docs/quick_tutorial/databases/development.ini4
-rw-r--r--docs/quick_tutorial/databases/setup.py1
-rw-r--r--docs/quick_tutorial/debugtoolbar.rst2
-rw-r--r--docs/quick_tutorial/debugtoolbar/development.ini4
-rw-r--r--docs/quick_tutorial/debugtoolbar/setup.py1
-rw-r--r--docs/quick_tutorial/forms.rst2
-rw-r--r--docs/quick_tutorial/forms/development.ini4
-rw-r--r--docs/quick_tutorial/forms/setup.py1
-rw-r--r--docs/quick_tutorial/functional_testing.rst2
-rw-r--r--docs/quick_tutorial/functional_testing/development.ini4
-rw-r--r--docs/quick_tutorial/functional_testing/setup.py1
-rw-r--r--docs/quick_tutorial/hello_world.rst2
-rw-r--r--docs/quick_tutorial/hello_world/app.py13
-rw-r--r--docs/quick_tutorial/ini.rst14
-rw-r--r--docs/quick_tutorial/ini/development.ini4
-rw-r--r--docs/quick_tutorial/ini/setup.py1
-rw-r--r--docs/quick_tutorial/jinja2.rst2
-rw-r--r--docs/quick_tutorial/jinja2/development.ini4
-rw-r--r--docs/quick_tutorial/jinja2/setup.py1
-rw-r--r--docs/quick_tutorial/json/development.ini4
-rw-r--r--docs/quick_tutorial/json/setup.py1
-rw-r--r--docs/quick_tutorial/logging/development.ini4
-rw-r--r--docs/quick_tutorial/logging/setup.py1
-rw-r--r--docs/quick_tutorial/more_view_classes/development.ini4
-rw-r--r--docs/quick_tutorial/more_view_classes/setup.py1
-rw-r--r--docs/quick_tutorial/package.rst2
-rw-r--r--docs/quick_tutorial/package/tutorial/app.py15
-rw-r--r--docs/quick_tutorial/request_response/development.ini4
-rw-r--r--docs/quick_tutorial/request_response/setup.py1
-rw-r--r--docs/quick_tutorial/requirements.rst9
-rw-r--r--docs/quick_tutorial/retail_forms/development.ini4
-rw-r--r--docs/quick_tutorial/retail_forms/setup.py1
-rw-r--r--docs/quick_tutorial/routing.rst2
-rw-r--r--docs/quick_tutorial/routing/development.ini4
-rw-r--r--docs/quick_tutorial/routing/setup.py1
-rw-r--r--docs/quick_tutorial/sessions/development.ini4
-rw-r--r--docs/quick_tutorial/sessions/setup.py1
-rw-r--r--docs/quick_tutorial/static_assets.rst11
-rw-r--r--docs/quick_tutorial/static_assets/development.ini4
-rw-r--r--docs/quick_tutorial/static_assets/setup.py1
-rw-r--r--docs/quick_tutorial/static_assets/tutorial/tests.py4
-rw-r--r--docs/quick_tutorial/templating/development.ini4
-rw-r--r--docs/quick_tutorial/templating/setup.py1
-rw-r--r--docs/quick_tutorial/unit_testing.rst2
-rw-r--r--docs/quick_tutorial/unit_testing/development.ini4
-rw-r--r--docs/quick_tutorial/unit_testing/setup.py1
-rw-r--r--docs/quick_tutorial/view_classes.rst2
-rw-r--r--docs/quick_tutorial/view_classes/development.ini4
-rw-r--r--docs/quick_tutorial/view_classes/setup.py1
-rw-r--r--docs/quick_tutorial/views/development.ini4
-rw-r--r--docs/quick_tutorial/views/setup.py1
-rw-r--r--docs/tutorials/modwsgi/index.rst18
-rw-r--r--docs/tutorials/wiki/authorization.rst10
-rw-r--r--docs/tutorials/wiki/basiclayout.rst24
-rw-r--r--docs/tutorials/wiki/definingviews.rst2
-rw-r--r--docs/tutorials/wiki/installation.rst25
-rw-r--r--docs/tutorials/wiki/src/authorization/README.txt4
-rw-r--r--docs/tutorials/wiki/src/authorization/development.ini8
-rw-r--r--docs/tutorials/wiki/src/authorization/production.ini6
-rw-r--r--docs/tutorials/wiki/src/authorization/setup.py4
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/__init__.py21
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt10
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt10
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt10
-rw-r--r--docs/tutorials/wiki/src/basiclayout/README.txt6
-rw-r--r--docs/tutorials/wiki/src/basiclayout/development.ini8
-rw-r--r--docs/tutorials/wiki/src/basiclayout/production.ini6
-rw-r--r--docs/tutorials/wiki/src/basiclayout/setup.py4
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py17
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt12
-rw-r--r--docs/tutorials/wiki/src/installation/README.txt6
-rw-r--r--docs/tutorials/wiki/src/installation/development.ini8
-rw-r--r--docs/tutorials/wiki/src/installation/production.ini6
-rw-r--r--docs/tutorials/wiki/src/installation/setup.py4
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/__init__.py17
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt12
-rw-r--r--docs/tutorials/wiki/src/models/README.txt6
-rw-r--r--docs/tutorials/wiki/src/models/development.ini8
-rw-r--r--docs/tutorials/wiki/src/models/production.ini6
-rw-r--r--docs/tutorials/wiki/src/models/setup.py4
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/__init__.py17
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt12
-rw-r--r--docs/tutorials/wiki/src/tests/README.txt4
-rw-r--r--docs/tutorials/wiki/src/tests/development.ini8
-rw-r--r--docs/tutorials/wiki/src/tests/production.ini6
-rw-r--r--docs/tutorials/wiki/src/tests/setup.py4
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/__init__.py21
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt10
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/login.pt10
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt16
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/view.pt10
-rw-r--r--docs/tutorials/wiki/src/views/README.txt4
-rw-r--r--docs/tutorials/wiki/src/views/development.ini8
-rw-r--r--docs/tutorials/wiki/src/views/production.ini6
-rw-r--r--docs/tutorials/wiki/src/views/setup.py4
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/__init__.py17
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/edit.pt10
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/view.pt10
-rw-r--r--docs/tutorials/wiki2/authentication.rst4
-rw-r--r--docs/tutorials/wiki2/installation.rst23
-rw-r--r--docs/tutorials/wiki2/src/authentication/README.txt6
-rw-r--r--docs/tutorials/wiki2/src/authentication/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/authentication/production.ini6
-rw-r--r--docs/tutorials/wiki2/src/authentication/setup.py6
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py4
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py2
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja210
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/views/default.py6
-rw-r--r--docs/tutorials/wiki2/src/authorization/README.txt6
-rw-r--r--docs/tutorials/wiki2/src/authorization/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/authorization/production.ini6
-rw-r--r--docs/tutorials/wiki2/src/authorization/setup.py6
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py4
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py2
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja210
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views/default.py6
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/README.txt6
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/production.ini6
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/setup.py6
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py4
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja212
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja22
-rw-r--r--docs/tutorials/wiki2/src/installation/README.txt6
-rw-r--r--docs/tutorials/wiki2/src/installation/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/installation/production.ini6
-rw-r--r--docs/tutorials/wiki2/src/installation/setup.py6
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py4
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/models/meta.py2
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja212
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja22
-rw-r--r--docs/tutorials/wiki2/src/models/README.txt6
-rw-r--r--docs/tutorials/wiki2/src/models/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/models/production.ini6
-rw-r--r--docs/tutorials/wiki2/src/models/setup.py6
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models/__init__.py4
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models/meta.py2
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja212
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja22
-rw-r--r--docs/tutorials/wiki2/src/tests/README.txt6
-rw-r--r--docs/tutorials/wiki2/src/tests/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/tests/production.ini6
-rw-r--r--docs/tutorials/wiki2/src/tests/setup.py6
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py4
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models/meta.py2
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja210
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/default.py6
-rw-r--r--docs/tutorials/wiki2/src/views/README.txt6
-rw-r--r--docs/tutorials/wiki2/src/views/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/views/production.ini6
-rw-r--r--docs/tutorials/wiki2/src/views/setup.py6
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models/__init__.py4
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models/meta.py2
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja210
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views/default.py6
-rw-r--r--docs/tutorials/wiki2/tests.rst2
-rw-r--r--docs/typographical-conventions.rst2
-rw-r--r--docs/whatsnew-1.8.rst7
-rw-r--r--docs/whatsnew-1.9.rst72
-rw-r--r--pyramid/authentication.py10
-rw-r--r--pyramid/compat.py5
-rw-r--r--pyramid/config/__init__.py52
-rw-r--r--pyramid/config/factories.py32
-rw-r--r--pyramid/config/security.py36
-rw-r--r--pyramid/config/settings.py3
-rw-r--r--pyramid/config/util.py17
-rw-r--r--pyramid/config/views.py27
-rw-r--r--pyramid/csrf.py332
-rw-r--r--pyramid/encode.py45
-rw-r--r--pyramid/events.py27
-rw-r--r--pyramid/interfaces.py126
-rw-r--r--pyramid/paster.py59
-rw-r--r--pyramid/path.py2
-rw-r--r--pyramid/predicates.py2
-rw-r--r--pyramid/registry.py6
-rw-r--r--pyramid/renderers.py5
-rw-r--r--pyramid/request.py2
-rw-r--r--pyramid/response.py30
-rw-r--r--pyramid/router.py106
-rw-r--r--pyramid/scaffolds/__init__.py6
-rw-r--r--pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl1
-rw-r--r--pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl12
-rw-r--r--pyramid/scaffolds/alchemy/development.ini_tmpl6
-rw-r--r--pyramid/scaffolds/alchemy/production.ini_tmpl4
-rw-r--r--pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl12
-rw-r--r--pyramid/scaffolds/starter/development.ini_tmpl6
-rw-r--r--pyramid/scaffolds/starter/production.ini_tmpl4
-rw-r--r--pyramid/scaffolds/zodb/+package+/__init__.py2
-rw-r--r--pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl12
-rw-r--r--pyramid/scaffolds/zodb/development.ini_tmpl6
-rw-r--r--pyramid/scaffolds/zodb/production.ini_tmpl4
-rw-r--r--pyramid/scripts/common.py27
-rw-r--r--pyramid/scripts/prequest.py19
-rw-r--r--pyramid/scripts/proutes.py35
-rw-r--r--pyramid/scripts/pserve.py162
-rw-r--r--pyramid/scripts/pshell.py31
-rw-r--r--pyramid/scripts/ptweens.py8
-rw-r--r--pyramid/scripts/pviews.py10
-rw-r--r--pyramid/security.py114
-rw-r--r--pyramid/session.py169
-rw-r--r--pyramid/static.py7
-rw-r--r--pyramid/testing.py3
-rw-r--r--pyramid/tests/pkgs/subrequestapp/__init__.py4
-rw-r--r--pyramid/tests/test_config/test_factories.py19
-rw-r--r--pyramid/tests/test_config/test_init.py26
-rw-r--r--pyramid/tests/test_config/test_settings.py25
-rw-r--r--pyramid/tests/test_config/test_util.py10
-rw-r--r--pyramid/tests/test_config/test_views.py4
-rw-r--r--pyramid/tests/test_csrf.py406
-rw-r--r--pyramid/tests/test_encode.py11
-rw-r--r--pyramid/tests/test_events.py15
-rw-r--r--pyramid/tests/test_integration.py30
-rw-r--r--pyramid/tests/test_paster.py190
-rw-r--r--pyramid/tests/test_renderers.py8
-rw-r--r--pyramid/tests/test_response.py21
-rw-r--r--pyramid/tests/test_router.py109
-rw-r--r--pyramid/tests/test_scaffolds/fixture_scaffold/+package+/templates/mytemplate.pt_tmpl2
-rw-r--r--pyramid/tests/test_scripts/dummy.py70
-rw-r--r--pyramid/tests/test_scripts/test_prequest.py62
-rw-r--r--pyramid/tests/test_scripts/test_proutes.py74
-rw-r--r--pyramid/tests/test_scripts/test_pserve.py102
-rw-r--r--pyramid/tests/test_scripts/test_pshell.py55
-rw-r--r--pyramid/tests/test_scripts/test_ptweens.py3
-rw-r--r--pyramid/tests/test_scripts/test_pviews.py3
-rw-r--r--pyramid/tests/test_security.py28
-rw-r--r--pyramid/tests/test_session.py138
-rw-r--r--pyramid/tests/test_tweens.py17
-rw-r--r--pyramid/tests/test_url.py32
-rw-r--r--pyramid/tests/test_util.py14
-rw-r--r--pyramid/tests/test_view.py117
-rw-r--r--pyramid/tests/test_viewderivers.py1
-rw-r--r--pyramid/threadlocal.py29
-rw-r--r--pyramid/traversal.py3
-rw-r--r--pyramid/tweens.py76
-rw-r--r--pyramid/url.py185
-rw-r--r--pyramid/util.py14
-rw-r--r--pyramid/view.py144
-rw-r--r--pyramid/viewderivers.py2
-rw-r--r--setup.cfg13
-rw-r--r--setup.py36
-rw-r--r--tox.ini17
350 files changed, 4850 insertions, 3150 deletions
diff --git a/.travis.yml b/.travis.yml
index b0e63ba97..9e296f987 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,16 +10,17 @@ matrix:
env: TOXENV=py34
- python: 3.5
env: TOXENV=py35
- - python: pypy
+ - python: 3.6
+ env: TOXENV=py36
+ # aws s3 ls s3://travis-python-archives/binaries/ubuntu/14.04/x86_64/ | grep 'pypy.*bz2$'
+ - python: pypy-5.4.1
env: TOXENV=pypy
- python: 3.5
env: TOXENV=py2-cover,py3-cover,coverage
- python: 3.5
env: TOXENV=docs
- python: 3.5
- env: TOXENV=pep8
- - python: 3.6
- env: TOXENV=py36
+ env: TOXENV=lint
- python: nightly
env: TOXENV=py37
allow_failures:
@@ -31,6 +32,10 @@ install:
script:
- travis_retry tox
+cache:
+ directories:
+ - $HOME/.cache/pip
+
notifications:
email:
- pyramid-checkins@lists.repoze.org
diff --git a/BFG_HISTORY.txt b/BFG_HISTORY.rst
index 8a2d40920..8a2d40920 100644
--- a/BFG_HISTORY.txt
+++ b/BFG_HISTORY.rst
diff --git a/CHANGES.rst b/CHANGES.rst
new file mode 100644
index 000000000..7a8fa31f2
--- /dev/null
+++ b/CHANGES.rst
@@ -0,0 +1,44 @@
+unreleased
+==========
+
+Features
+--------
+
+- Add a ``_depth`` and ``_category`` arguments to all of the venusian
+ decorators. The ``_category`` argument can be used to affect which actions
+ are registered when performing a ``config.scan(..., category=...)`` with a
+ specific category. The ``_depth`` argument should be used when wrapping
+ the decorator in your own. This change affects ``pyramid.view.view_config``,
+ ``pyramid.view.exception_view_config``,
+ ``pyramid.view.forbidden_view_config``, ``pyramid.view.notfound_view_config``,
+ ``pyramid.events.subscriber`` and ``pyramid.response.response_adapter``
+ decorators. See https://github.com/Pylons/pyramid/pull/3105 and
+ https://github.com/Pylons/pyramid/pull/3122
+
+- Fix the ``pyramid.request.Request`` class name after using
+ ``set_property`` or ``config.add_request_method`` such that the
+ ``str(request.__class__)`` would appear as ``pyramid.request.Request``
+ instead of ``pyramid.util.Request``.
+ See https://github.com/Pylons/pyramid/pull/3129
+
+- In ``cherrypy_server_runner``, prefer imports from the ``cheroot`` package over the legacy
+ imports from `cherrypy.wsgiserver`.
+ See https://github.com/Pylons/pyramid/pull/3235
+
+Bug Fixes
+---------
+
+Deprecations
+------------
+
+Backward Incompatibilities
+--------------------------
+
+- On Python 3.4+ the ``repoze.lru`` dependency is dropped. If you were using
+ this package directly in your apps you should make sure that you are
+ depending on it directly within your project.
+ See https://github.com/Pylons/pyramid/pull/3140
+
+Documentation Changes
+---------------------
+
diff --git a/CHANGES.txt b/CHANGES.txt
deleted file mode 100644
index 59a733bcd..000000000
--- a/CHANGES.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-unreleased
-==========
-
-Features
---------
-
-Bug Fixes
----------
-
-- HTTPException's accepts a detail kwarg that may be used to pass additional
- details to the exception. You may now pass objects so long as they have a
- valid __str__ method. See https://github.com/Pylons/pyramid/pull/2951
-
-Deprecations
-------------
-
-Backward Incompatibilities
---------------------------
-
-Documentation Changes
----------------------
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index d5c178418..b6dbcff2c 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -291,4 +291,28 @@ Contributors
- Mikko Ohtamaa, 2016/12/6
+- Jure Cerjak, 2016/12/7
+
- Martin Frlin, 2016/12/7
+
+- Kirill Kuzminykh, 2017/03/01
+
+- Aleph Melo, 2017/04/16
+
+- Jeremy(Ching-Rui) Chen, 2017/04/19
+
+- Russell Ballestrini, 2017/05/06
+
+- Fang-Pen Lin, 2017/05/22
+
+- Volker Diels-Grabsch, 2017/06/09
+
+- Denis Rykov, 2017/06/15
+
+- Tosh Lyons, 2017/06/27
+
+- Lars Blumberg, 2017/08/14
+
+- Deneys Maartens, 2017/11/03
+
+- Heron Rossi, 2018/03/08
diff --git a/HACKING.txt b/HACKING.txt
index bbebb5165..ad89fc490 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -51,11 +51,6 @@ repo, from which you can submit a pull request.
To use the instructions in the steps that follow literally, use the
``export VENV=~/hack-on-pyramid/env`` command.
-- Install ``setuptools-git`` into the virtual environment (for good measure, as
- we're using git to do version control):
-
- $ $VENV/bin/pip install setuptools-git
-
- Install Pyramid from the checkout into the virtual environment, where the
current working directory is the ``pyramid`` checkout directory. We will
install Pyramid in editable (development) mode as well as its testing
@@ -140,7 +135,7 @@ Coding Style
- PEP8 compliance. Whitespace rules are relaxed: not necessary to put two
newlines between classes. But 79-column lines, in particular, are mandatory.
- See http://docs.pylonsproject.org/en/latest/community/codestyle.html for more
+ See https://pylonsproject.org/community-coding-style-standards.html for more
information.
- Please do not remove trailing whitespace. Configure your editor to reduce
diff --git a/HISTORY.txt b/HISTORY.rst
index c10747af4..8a92eadcc 100644
--- a/HISTORY.txt
+++ b/HISTORY.rst
@@ -1,3 +1,202 @@
+1.9 (2017-06-26)
+================
+
+- No major changes from 1.9b1.
+
+- Updated documentation links for ``docs.pylonsproject.org`` to use HTTPS.
+
+1.9b1 (2017-06-19)
+==================
+
+- Add an informative error message when unknown predicates are supplied. The
+ new message suggests alternatives based on the list of known predicates.
+ See https://github.com/Pylons/pyramid/pull/3054
+
+- Added integrity attributes for JavaScripts in cookiecutters, scaffolds, and
+ resulting source files in tutorials.
+ See https://github.com/Pylons/pyramid/issues/2548
+
+- Update RELEASING.txt for updating cookiecutters. Change cookiecutter URLs to
+ use shortcut.
+ See https://github.com/Pylons/pyramid/issues/3042
+
+- Ensure the correct threadlocals are pushed during view execution when
+ invoked from ``request.invoke_exception_view``.
+ See https://github.com/Pylons/pyramid/pull/3060
+
+- Fix a bug in which ``pyramid.security.ALL_PERMISSIONS`` failed to return
+ a valid iterator in its ``__iter__`` implementation.
+ See https://github.com/Pylons/pyramid/pull/3074
+
+- Normalize the permission results to a proper class hierarchy.
+ ``pyramid.security.ACLAllowed`` is now a subclass of
+ ``pyramid.security.Allowed`` and ``pyramid.security.ACLDenied`` is now a
+ subclass of ``pyramid.security.Denied``.
+ See https://github.com/Pylons/pyramid/pull/3084
+
+- Add a ``quote_via`` argument to ``pyramid.encode.urlencode`` to follow
+ the stdlib's version and enable custom quoting functions.
+ See https://github.com/Pylons/pyramid/pull/3088
+
+- Support `_query=None` and `_anchor=None` in ``request.route_url`` as well
+ as ``query=None`` and ``anchor=None`` in ``request.resource_url``.
+ Previously this would cause an `?` and a `#`, respectively, in the url
+ with nothing after it. Now the unnecessary parts are dropped from the
+ generated URL. See https://github.com/Pylons/pyramid/pull/3034
+
+- Revamp the ``IRouter`` API used by ``IExecutionPolicy`` to force
+ pushing/popping the request threadlocals. The
+ ``IRouter.make_request(environ)`` API has been replaced by
+ ``IRouter.request_context(environ)`` which should be used as a context
+ manager. See https://github.com/Pylons/pyramid/pull/3086
+
+1.9a2 (2017-05-09)
+==================
+
+Backward Incompatibilities
+--------------------------
+
+- ``request.exception`` and ``request.exc_info`` will only be set if the
+ response was generated by the EXCVIEW tween. This is to avoid any confusion
+ where a response was generated elsewhere in the pipeline and not in
+ direct relation to the original exception. If anyone upstream wants to
+ catch and render responses for exceptions they should set
+ ``request.exception`` and ``request.exc_info`` themselves to indicate
+ the exception that was squashed when generating the response.
+
+ Similar behavior occurs with ``request.invoke_exception_view`` in which
+ the exception properties are set to reflect the exception if a response
+ is successfully generated by the method.
+
+ This is a very minor incompatibility. Most tweens right now would give
+ priority to the raised exception and ignore ``request.exception``. This
+ change just improves and clarifies that bookkeeping by trying to be
+ more clear about the relationship between the response and its squashed
+ exception. See https://github.com/Pylons/pyramid/pull/3029 and
+ https://github.com/Pylons/pyramid/pull/3031
+
+1.9a1 (2017-05-01)
+==================
+
+Major Features
+--------------
+
+- The file format used by all ``p*`` command line scripts such as ``pserve``
+ and ``pshell``, as well as the ``pyramid.paster.bootstrap`` function
+ is now replaceable thanks to a new dependency on
+ `plaster <https://docs.pylonsproject.org/projects/plaster/en/latest/>`_.
+
+ For now, Pyramid is still shipping with integrated support for the
+ PasteDeploy INI format by depending on the
+ `plaster_pastedeploy <https://github.com/Pylons/plaster_pastedeploy>`_
+ binding library. This may change in the future.
+
+ See https://github.com/Pylons/pyramid/pull/2985
+
+- Added an execution policy hook to the request pipeline. An execution
+ policy has the ability to control creation and execution of the request
+ objects before they enter the rest of the pipeline. This means for a single
+ request environ the policy may create more than one request object.
+
+ The first library to use this feature is
+ `pyramid_retry
+ <https://docs.pylonsproject.org/projects/pyramid-retry/en/latest/>`_.
+
+ See https://github.com/Pylons/pyramid/pull/2964
+
+- CSRF support has been refactored out of sessions and into its own
+ independent API in the ``pyramid.csrf`` module. It supports a pluggable
+ ``pyramid.interfaces.ICSRFStoragePolicy`` which can be used to define your
+ own mechanism for generating and validating CSRF tokens. By default,
+ Pyramid continues to use the ``pyramid.csrf.LegacySessionCSRFStoragePolicy``
+ that uses the ``request.session.get_csrf_token`` and
+ ``request.session.new_csrf_token`` APIs under the hood to preserve
+ compatibility. Two new policies are shipped as well,
+ ``pyramid.csrf.SessionCSRFStoragePolicy`` and
+ ``pyramid.csrf.CookieCSRFStoragePolicy`` which will store the CSRF tokens
+ in the session and in a standalone cookie, respectively. The storage policy
+ can be changed by using the new
+ ``pyramid.config.Configurator.set_csrf_storage_policy`` config directive.
+
+ CSRF tokens should be used via the new ``pyramid.csrf.get_csrf_token``,
+ ``pyramid.csrf.new_csrf_token`` and ``pyramid.csrf.check_csrf_token`` APIs
+ in order to continue working if the storage policy is changed. Also, the
+ ``pyramid.csrf.get_csrf_token`` function is injected into templates to be
+ used conveniently in UI code.
+
+ See https://github.com/Pylons/pyramid/pull/2854 and
+ https://github.com/Pylons/pyramid/pull/3019
+
+Minor Features
+--------------
+
+- Support an ``open_url`` config setting in the ``pserve`` section of the
+ config file. This url is used to open a web browser when ``pserve --browser``
+ is invoked. When this setting is unavailable the ``pserve`` script will
+ attempt to guess the port the server is using from the
+ ``server:<server_name>`` section of the config file but there is no
+ requirement that the server is being run in this format so it may fail.
+ See https://github.com/Pylons/pyramid/pull/2984
+
+- The ``pyramid.config.Configurator`` can now be used as a context manager
+ which will automatically push/pop threadlocals (similar to
+ ``config.begin()`` and ``config.end()``). It will also automatically perform
+ a ``config.commit()`` and thus it is only recommended to be used at the
+ top-level of your app. See https://github.com/Pylons/pyramid/pull/2874
+
+- The threadlocals are now available inside any function invoked via
+ ``config.include``. This means the only config-time code that cannot rely
+ on threadlocals is code executed from non-actions inside the main. This
+ can be alleviated by invoking ``config.begin()`` and ``config.end()``
+ appropriately or using the new context manager feature of the configurator.
+ See https://github.com/Pylons/pyramid/pull/2989
+
+Bug Fixes
+---------
+
+- HTTPException's accepts a detail kwarg that may be used to pass additional
+ details to the exception. You may now pass objects so long as they have a
+ valid __str__ method. See https://github.com/Pylons/pyramid/pull/2951
+
+- Fix a reference cycle causing memory leaks in which the registry
+ would keep a ``Configurator`` instance alive even after the configurator
+ was discarded. Another fix was also added for the ``global_registries``
+ object in which the registry was stored in a closure preventing it from
+ being deallocated. See https://github.com/Pylons/pyramid/pull/2967
+
+- Fix a bug directly invoking ``pyramid.scripts.pserve.main`` with the
+ ``--reload`` option in which ``sys.argv`` is always used in the subprocess
+ instead of the supplied ``argv``.
+ See https://github.com/Pylons/pyramid/pull/2962
+
+Deprecations
+------------
+
+- Pyramid currently depends on ``plaster_pastedeploy`` to simplify the
+ transition to ``plaster`` by maintaining integrated support for INI files.
+ This dependency on ``plaster_pastedeploy`` should be considered subject to
+ Pyramid's deprecation policy and may be removed in the future.
+ Applications should depend on the appropriate plaster binding to satisfy
+ their needs.
+
+- Retrieving CSRF token from the session has been deprecated in favor of
+ equivalent methods in the ``pyramid.csrf`` module. The CSRF methods
+ (``ISession.get_csrf_token`` and ``ISession.new_csrf_token``) are no longer
+ required on the ``ISession`` interface except when using the default
+ ``pyramid.csrf.LegacySessionCSRFStoragePolicy``.
+
+ Also, ``pyramid.session.check_csrf_token`` is now located at
+ ``pyramid.csrf.check_csrf_token``.
+
+ See https://github.com/Pylons/pyramid/pull/2854 and
+ https://github.com/Pylons/pyramid/pull/3019
+
+Documentation Changes
+---------------------
+
+- Added the execution policy to the routing diagram in the Request Processing
+ chapter. See https://github.com/Pylons/pyramid/pull/2993
+
1.8 (2017-01-21)
================
@@ -198,6 +397,12 @@ Features
See https://github.com/Pylons/pyramid/pull/2873
+- Added a new ``callback`` option to ``config.set_default_csrf_options`` which
+ can be used to determine per-request whether CSRF checking should be enabled
+ to allow for a mix authentication methods. Only cookie-based methods
+ generally require CSRF checking.
+ See https://github.com/Pylons/pyramid/pull/2778
+
Bug Fixes
---------
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 000000000..2e18ad5fe
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,16 @@
+graft pyramid
+graft docs
+prune docs/_build
+
+include README.rst
+include CHANGES.rst HISTORY.rst BFG_HISTORY.rst
+include CONTRIBUTORS.txt LICENSE.txt COPYRIGHT.txt
+
+include contributing.md RELEASING.txt
+include tox.ini appveyor.yml .travis.yml rtd.txt
+
+include HACKING.txt hacking-tox.ini
+include builddocs.sh coverage.sh scaffoldtests.sh
+include TODO.txt
+
+global-exclude __pycache__ *.py[cod]
diff --git a/README.rst b/README.rst
index f05dd8bbf..ecc8eb32f 100644
--- a/README.rst
+++ b/README.rst
@@ -6,12 +6,8 @@ Pyramid
:alt: master Travis CI Status
.. image:: https://readthedocs.org/projects/pyramid/badge/?version=master
- :target: http://docs.pylonsproject.org/projects/pyramid/en/master/
- :alt: Master Documentation Status
-
-.. image:: https://readthedocs.org/projects/pyramid/badge/?version=latest
- :target: http://docs.pylonsproject.org/projects/pyramid/en/latest/
- :alt: Latest Documentation Status
+ :target: https://docs.pylonsproject.org/projects/pyramid/en/master
+ :alt: master Documentation Status
.. image:: https://img.shields.io/badge/irc-freenode-blue.svg
:target: https://webchat.freenode.net/?channels=pyramid
@@ -31,20 +27,20 @@ and deployment more fun, more predictable, and more productive.
return Response('Hello %(name)s!' % request.matchdict)
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/hello/{name}')
- config.add_view(hello_world, route_name='hello')
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_route('hello', '/hello/{name}')
+ config.add_view(hello_world, route_name='hello')
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()
-Pyramid is a project of the `Pylons Project <http://pylonsproject.org/>`_.
+Pyramid is a project of the `Pylons Project <https://pylonsproject.org>`_.
Support and Documentation
-------------------------
See `Pyramid Support and Development
-<http://docs.pylonsproject.org/projects/pyramid/en/latest/#support-and-development>`_
+<https://docs.pylonsproject.org/projects/pyramid/en/latest/#support-and-development>`_
for documentation, reporting bugs, and getting support.
Developing and Contributing
diff --git a/RELEASING.txt b/RELEASING.txt
index c7a23309b..f38903985 100644
--- a/RELEASING.txt
+++ b/RELEASING.txt
@@ -38,18 +38,26 @@ Prepare new release branch
$ ./scaffoldtests.sh
-- Ensure all features of the release are documented (audit CHANGES.txt or
+- For each ``pyramid-cookiecutter-*``, make a new branch off "master" with the
+ same name to align with the new Pyramid release branch name.
+
+- In the docs, update the ``cookiecutter`` command with the new branch name,
+ for example, ``cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout
+ x.y-branch``. A search for ``cookiecutter gh:Pylons/pyramid-cookiecutter-``
+ should return all instances to be updated.
+
+- Ensure all features of the release are documented (audit CHANGES.rst or
communicate with contributors).
-- Change CHANGES.txt heading to reflect the new version number.
+- Change CHANGES.rst heading to reflect the new version number.
-- Copy relevant changes (delta bug fixes) from CHANGES.txt to
+- Copy relevant changes (delta bug fixes) from CHANGES.rst to
docs/whatsnew-X.X (if it's a major release). Minor releases should
include a link under "Bug Fix Releases" to the minor feature
- changes in CHANGES.txt.
+ changes in CHANGES.rst.
-- Update README.rst to use correct versions of badges and URLs according to
- each branch and context, i.e., RTD "latest" == GitHub/Travis "1.x-branch".
+- Update README.rst to use correct versions of badges, URLs, and ALT option
+ according to the new release branch name.
- Update whatsnew-X.X.rst in docs to point at change log entries for individual
releases if applicable.
@@ -63,15 +71,9 @@ Prepare new release branch
- Change setup.py version to the release version number.
-- Make sure PyPI long description renders (requires ``readme`` installed
- into your Python)::
-
- $ python setup.py check -r -s -m
-
- Create a release tag.
-- Make sure your Python has ``setuptools-git``, ``twine``, and ``wheel``
- installed and release to PyPI::
+- Build and publish to PyPI::
$ python setup.py sdist bdist_wheel
$ twine upload dist/pyramid-X.X-*
@@ -83,11 +85,11 @@ Prepare master for further development (major releases only)
- Checkout master.
-- In CHANGES.txt, preserve headings but clear out content. Add heading
+- In CHANGES.rst, preserve headings but clear out content. Add heading
"unreleased" for the version number.
-- From the release branch, forward port the changes in CHANGES.txt to
- HISTORY.txt.
+- From the release branch, forward port the changes in CHANGES.rst to
+ HISTORY.rst.
- In contributing.md, forward port branch descriptions from release branch.
@@ -96,6 +98,14 @@ Prepare master for further development (major releases only)
- Change setup.py version to the next version number.
+- Update README.rst to use correct versions of badges, URLs, and ALT option
+ for "master" instead of the major release version.
+
+- In the docs, update the ``cookiecutter`` command with ``master``,
+ for example, ``cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout
+ master``. A search for ``cookiecutter gh:Pylons/pyramid-cookiecutter-``
+ should return all instances to be updated.
+
Update previous version (final releases only)
---------------------------------------------
@@ -105,17 +115,24 @@ Update previous version (final releases only)
- Configure RTD to point the "latest" alias to the new release version of the
docs.
+
+Cookiecutters
+-------------
+
+- For each cookiecutter, clone the newly released branch to "latest" branch.
+
+
Marketing and communications
----------------------------
-- Edit Pylons/trypyramid.com/src/templates/resources.html for major releases
- only.
-
-- Edit Pylons/pylonsrtd/pylonsrtd/docs/pyramid.rst for major releases only.
+- Edit Pylons/trypyramid.com/src/templates/resources.html for major releases,
+ pre-releases, and once pre-releases are final.
- Edit `http://wiki.python.org/moin/WebFrameworks
<http://wiki.python.org/moin/WebFrameworks>`_.
+- Edit `https://en.wikipedia.org/wiki/Pylons_project <https://en.wikipedia.org/wiki/Pylons_project>`_.
+
- Announce to Twitter.
```
@@ -126,10 +143,10 @@ https://pypi.python.org/pypi/pyramid/1.x
=== One time only for new version, first pre-release ===
What's New
-http://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/whatsnew-1.X.html
+https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/whatsnew-1.X.html
=== For all subsequent pre-releases ===
Changes
-http://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/changes.html#version-yyyy-mm-dd
+https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/changes.html#version-yyyy-mm-dd
Issues
https://github.com/Pylons/pyramid/issues
@@ -140,16 +157,15 @@ https://github.com/Pylons/pyramid/issues
```
Pyramid 1.X.X has been released.
-Here are the changes:
-
-<<changes>>
+The full changelog is here:
+https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/changes.html
What's New In Pyramid 1.X:
-http://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/whatsnew-1.X.html
+https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/whatsnew-1.X.html
1.X release documentation (across all alphas and betas, as well as when it gets
to final release):
-http://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/
+https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/
You can install it via PyPI:
diff --git a/contributing.md b/contributing.md
index 9deeee035..3b960c1e1 100644
--- a/contributing.md
+++ b/contributing.md
@@ -3,9 +3,9 @@ Contributing
All projects under the Pylons Projects, including this one, follow the
guidelines established at [How to
-Contribute](http://www.pylonsproject.org/community/how-to-contribute) and
+Contribute](https://pylonsproject.org/community-how-to-contribute.html) and
[Coding Style and
-Standards](http://docs.pylonsproject.org/en/latest/community/codestyle.html).
+Standards](https://pylonsproject.org/community-coding-style-standards.html).
You can contribute to this project in several ways.
@@ -26,9 +26,9 @@ listed below.
* [master](https://github.com/Pylons/pyramid/) - The branch on which further
development takes place. The default branch on GitHub.
-* [1.8-branch](https://github.com/Pylons/pyramid/tree/1.8-branch) - The branch
+* [1.9-branch](https://github.com/Pylons/pyramid/tree/1.9-branch) - The branch
classified as "stable" or "latest".
-* [1.7-branch](https://github.com/Pylons/pyramid/tree/1.7-branch) - The oldest
+* [1.8-branch](https://github.com/Pylons/pyramid/tree/1.8-branch) - The oldest
actively maintained and stable branch.
Older branches are not actively maintained. In general, two stable branches and
@@ -49,7 +49,7 @@ Building documentation for a Pylons Project project
improve the process for Windows users are welcome by submitting an issue or a
pull request. Windows users may find it helpful to follow the guide [Installing
Pyramid on a Windows
-System](http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/install.html#installing-pyramid-on-a-windows-system).
+System](https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/install.html#installing-pyramid-on-a-windows-system).
1. Fork the repo on GitHub by clicking the [Fork] button.
2. Clone your fork into a workspace on your local machine.
diff --git a/docs/_static/pyramid_request_processing.graffle b/docs/_static/pyramid_request_processing.graffle
index 56e4e13f2..5ccbf4ea4 100644
--- a/docs/_static/pyramid_request_processing.graffle
+++ b/docs/_static/pyramid_request_processing.graffle
@@ -59,8 +59,128 @@
<key>GraphicsList</key>
<array>
<dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>ControlPoints</key>
+ <array>
+ <string>{0, 6.9840087890625}</string>
+ <string>{0, -9}</string>
+ </array>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169378</integer>
+ <key>Info</key>
+ <integer>2</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169517</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{155.00000381469727, 109.82143211364746}</string>
+ <string>{155.00000254313238, 133.68303707668386}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Bezier</key>
+ <true/>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>SharpArrow</string>
+ <key>Legacy</key>
+ <true/>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169516</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{102.16667175292969, 87.276790618896484}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169516</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999208</string>
+ <key>g</key>
+ <string>0.811343</string>
+ <key>r</key>
+ <string>0.644457</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 execution policy}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
<key>Bounds</key>
- <string>{{238.74999618530273, 294.65604172230951}, {105.66668701171875, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 325.15604172230951}, {105.66668701171875, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FontInfo</key>
@@ -146,8 +266,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{154.9999760464211, 209.11365574251681}</string>
- <string>{239.8333613077798, 209.14732074737549}</string>
+ <string>{154.99997604642331, 241.21656873817005}</string>
+ <string>{239.8333613077798, 240.64732074737549}</string>
</array>
<key>Style</key>
<dict>
@@ -182,7 +302,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{239.83336130777977, 197.875}, {105.66666412353516, 22.544641494750977}}</string>
+ <string>{{239.83336130777977, 229.375}, {105.66666412353516, 22.544641494750977}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -259,8 +379,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{344.41668319702148, 411.88506673894034}</string>
- <string>{375.5, 411.77232108797347}</string>
+ <string>{344.41668319702148, 442.38506673894034}</string>
+ <string>{375.5, 442.27232108797347}</string>
</array>
<key>Style</key>
<dict>
@@ -317,8 +437,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{155.00000254313238, 459.27667544230695}</string>
- <string>{238.5002713470962, 456.52468399152298}</string>
+ <string>{155.00000254313238, 489.77667544230695}</string>
+ <string>{238.5002713470962, 487.02468399152298}</string>
</array>
<key>Style</key>
<dict>
@@ -377,8 +497,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{155.00000254313238, 482.12574895537085}</string>
- <string>{238.52297468463752, 508.35839132916635}</string>
+ <string>{155.00000254313238, 512.6257489553708}</string>
+ <string>{238.52297468463752, 538.85839132916635}</string>
</array>
<key>Style</key>
<dict>
@@ -413,7 +533,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.74999618530273, 275.99999999999994}, {105.75002924601222, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 306.49999999999994}, {105.75002924601222, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FontInfo</key>
@@ -475,7 +595,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.74999618530273, 421.15071036499205}, {105.66668701171875, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 451.65071036499205}, {105.66668701171875, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FontInfo</key>
@@ -537,7 +657,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.74999618530273, 312.65604172230951}, {105.66668701171875, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 343.15604172230951}, {105.66668701171875, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FontInfo</key>
@@ -599,7 +719,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.74999618530273, 402.55704269887212}, {105.66668701171875, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 433.05704269887212}, {105.66668701171875, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FontInfo</key>
@@ -659,7 +779,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.74999618530273, 383.90099016834085}, {105.66668701171875, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 414.40099016834085}, {105.66668701171875, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FontInfo</key>
@@ -719,7 +839,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.74999618530273, 350.36561209044055}, {105.66668701171875, 33.089282989501953}}</string>
+ <string>{{238.74999618530273, 380.86561209044055}, {105.66668701171875, 33.089282989501953}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FontInfo</key>
@@ -779,7 +899,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.74999618530273, 331.26348241170439}, {105.66668701171875, 18.656048080136394}}</string>
+ <string>{{238.74999618530273, 361.76348241170439}, {105.66668701171875, 18.656048080136394}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FontInfo</key>
@@ -865,8 +985,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{155.00000254313238, 470.25295298442387}</string>
- <string>{238.33861159880226, 482.4262543949045}</string>
+ <string>{155.00000254313238, 500.75295298442387}</string>
+ <string>{238.33861159880226, 512.9262543949045}</string>
</array>
<key>Style</key>
<dict>
@@ -901,7 +1021,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.83336130777977, 471.22620192028251}, {105.66666412353516, 22.544641494750977}}</string>
+ <string>{{238.83336130777977, 501.72620192028251}, {105.66666412353516, 22.544641494750977}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -980,8 +1100,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{154.99998733539806, 128.68025330008533}</string>
- <string>{239.83340199788393, 128.59152244387357}</string>
+ <string>{154.99998724282403, 165.00131810958592}</string>
+ <string>{239.83340199788393, 164.59152244387357}</string>
</array>
<key>Style</key>
<dict>
@@ -1016,7 +1136,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{239.83340199788395, 117.31920169649808}, {105.66666412353516, 22.544641494750977}}</string>
+ <string>{{239.83340199788395, 153.31920169649808}, {105.66666412353516, 22.544641494750977}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -1074,7 +1194,7 @@
<array>
<dict>
<key>Bounds</key>
- <string>{{102.1666056315114, 148.28868579864499}, {105.66669464111328, 33.08929443359375}}</string>
+ <string>{{102.1666056315114, 181.78868579864499}, {105.66669464111328, 33.08929443359375}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -1125,7 +1245,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{102.1666056315114, 181.37798023223874}, {105.66669464111328, 17.244049072265625}}</string>
+ <string>{{102.1666056315114, 214.87798023223874}, {105.66669464111328, 17.244049072265625}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -1193,7 +1313,7 @@
<array>
<dict>
<key>Bounds</key>
- <string>{{102.16666158040482, 272}, {105.66666412353516, 33.08929443359375}}</string>
+ <string>{{102.16666158040482, 302.5}, {105.66666412353516, 33.08929443359375}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -1244,7 +1364,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{102.16666158040482, 305.08929443359375}, {105.66666412353516, 17.244049072265625}}</string>
+ <string>{{102.16666158040482, 335.58929443359375}, {105.66666412353516, 17.244049072265625}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -1321,8 +1441,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{238.74999618530282, 439.80675844512831}</string>
- <string>{207.66666666666765, 385.656005859375}</string>
+ <string>{238.74999618530282, 470.30675844512831}</string>
+ <string>{207.66666666666765, 416.156005859375}</string>
</array>
<key>Style</key>
<dict>
@@ -1371,8 +1491,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{239.25039065750093, 276.57837549845181}</string>
- <string>{207.66666666666777, 353.07514659563753}</string>
+ <string>{239.25039065750093, 307.07837549845181}</string>
+ <string>{207.66666666666777, 383.57514659563753}</string>
</array>
<key>Style</key>
<dict>
@@ -1436,8 +1556,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{155.00000254313238, 386.66442959065108}</string>
- <string>{155.00000254313238, 422.21209462483216}</string>
+ <string>{155.00000254313238, 417.16442959065108}</string>
+ <string>{155.00000254313238, 452.71209462483216}</string>
</array>
<key>Style</key>
<dict>
@@ -1472,7 +1592,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{102.16667048136482, 353.07514659563753}, {105.66666412353516, 33.089282989501953}}</string>
+ <string>{{102.16667048136482, 383.57514659563753}, {105.66666412353516, 33.089282989501953}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -1555,8 +1675,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{154.9999936421724, 258.44082431579938}</string>
- <string>{238.8333613077798, 258.45536063967575}</string>
+ <string>{154.9999936421724, 288.94082431579938}</string>
+ <string>{238.8333613077798, 288.95536063967575}</string>
</array>
<key>Style</key>
<dict>
@@ -1969,7 +2089,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{375.5, 400.5}, {105.66666412353516, 22.544642175946908}}</string>
+ <string>{{375.5, 431}, {105.66666412353516, 22.544642175946908}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -2053,8 +2173,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{155.00000170434049, 119.22767858295661}</string>
- <string>{154.99995295206804, 148.28868579864499}</string>
+ <string>{155.00000157307935, 156.72767858475962}</string>
+ <string>{154.99995295206804, 181.78868579864499}</string>
</array>
<key>Style</key>
<dict>
@@ -2110,7 +2230,9 @@
<key>Head</key>
<dict>
<key>ID</key>
- <integer>169378</integer>
+ <integer>169516</integer>
+ <key>Info</key>
+ <integer>2</integer>
</dict>
<key>ID</key>
<integer>169385</integer>
@@ -2119,7 +2241,7 @@
<key>Points</key>
<array>
<string>{155.00000254313238, 67.727678571434836}</string>
- <string>{155.00000254313238, 96.18303707668386}</string>
+ <string>{155.00000381469727, 87.276790618896484}</string>
</array>
<key>Style</key>
<dict>
@@ -2156,7 +2278,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{102.16667048136482, 509.6179466247504}, {105.66666412353516, 22.544641494750977}}</string>
+ <string>{{102.16667048136482, 540.1179466247504}, {105.66666412353516, 22.544641494750977}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -2209,7 +2331,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{239, 497.23589324949899}, {105.66666412353516, 22.544641494750977}}</string>
+ <string>{{239, 527.73589324949899}, {105.66666412353516, 22.544641494750977}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -2262,7 +2384,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{239, 445.23589324949717}, {105.66666412353516, 22.544641494750977}}</string>
+ <string>{{239, 475.73589324949717}, {105.66666412353516, 22.544641494750977}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -2315,7 +2437,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{102.16667048136482, 422.21209462483216}, {105.66666412353516, 22.544641494750977}}</string>
+ <string>{{102.16667048136482, 452.71209462483216}, {105.66666412353516, 22.544641494750977}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -2368,7 +2490,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{238.83336130777977, 247.18303989230026}, {105.66666412353516, 22.544641494750977}}</string>
+ <string>{{238.83336130777977, 277.68303989230026}, {105.66666412353516, 22.544641494750977}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -2421,7 +2543,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{102.16667048136482, 222.18303707668389}, {105.66666412353516, 22.544641494750977}}</string>
+ <string>{{102.16667048136482, 252.68303707668389}, {105.66666412353516, 22.544641494750977}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -2474,7 +2596,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{102.16667048136482, 96.18303707668386}, {105.66666412353516, 22.544641494750977}}</string>
+ <string>{{102.16667048136482, 133.68303707668386}, {105.66666412353516, 22.544641494750977}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -2609,8 +2731,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{154.99995295206804, 198.62202930450437}</string>
- <string>{155.00000254313238, 222.18303707668389}</string>
+ <string>{154.99995295206804, 232.12202930450437}</string>
+ <string>{155.00000254313238, 252.68303707668389}</string>
</array>
<key>Style</key>
<dict>
@@ -2678,8 +2800,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{154.9999936421724, 245.22767856643924}</string>
- <string>{154.9999936421724, 272}</string>
+ <string>{154.9999936421724, 275.72767856643924}</string>
+ <string>{154.9999936421724, 302.5}</string>
</array>
<key>Style</key>
<dict>
@@ -2743,8 +2865,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{154.9999936421724, 322.33334350585938}</string>
- <string>{155.00000254313238, 353.07514659563753}</string>
+ <string>{154.9999936421724, 352.83334350585938}</string>
+ <string>{155.00000254313238, 383.57514659563753}</string>
</array>
<key>Style</key>
<dict>
@@ -2812,8 +2934,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{155.00000254313238, 444.75673611958314}</string>
- <string>{155.00000254313238, 509.6179466247504}</string>
+ <string>{155.00000254313238, 475.25673611958314}</string>
+ <string>{155.00000254313238, 540.1179466247504}</string>
</array>
<key>Style</key>
<dict>
@@ -9868,7 +9990,7 @@
<key>MasterSheets</key>
<array/>
<key>ModificationDate</key>
- <string>2016-04-13 08:32:47 +0000</string>
+ <string>2017-04-10 09:33:14 +0000</string>
<key>Modifier</key>
<string>Steve Piercy</string>
<key>NotesVisible</key>
@@ -9949,7 +10071,7 @@
</dict>
</array>
<key>Frame</key>
- <string>{{35, 93}, {2284, 1325}}</string>
+ <string>{{35, 93}, {1632, 1325}}</string>
<key>ListView</key>
<true/>
<key>OutlineWidth</key>
@@ -9963,15 +10085,15 @@
<key>SidebarWidth</key>
<integer>163</integer>
<key>VisibleRegion</key>
- <string>{{110.125, 77.875}, {239.125, 146.375}}</string>
+ <string>{{-27, 33.5}, {630.5, 593}}</string>
<key>Zoom</key>
- <real>8</real>
+ <real>2</real>
<key>ZoomValues</key>
<array>
<array>
<string>Request Processing</string>
+ <real>2</real>
<real>8</real>
- <real>4</real>
</array>
</array>
</dict>
diff --git a/docs/_static/pyramid_request_processing.png b/docs/_static/pyramid_request_processing.png
index 2f44f4824..d62b172b0 100644
--- a/docs/_static/pyramid_request_processing.png
+++ b/docs/_static/pyramid_request_processing.png
Binary files differ
diff --git a/docs/_static/pyramid_request_processing.svg b/docs/_static/pyramid_request_processing.svg
index 03f6d56fa..38fa7348d 100644
--- a/docs/_static/pyramid_request_processing.svg
+++ b/docs/_static/pyramid_request_processing.svg
@@ -1,3 +1,3 @@
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="91 11 424 533" width="424pt" height="533pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2016-04-13 08:32Z</dc:date><!-- Produced by OmniGraffle Professional 5.4.4 --></metadata><defs><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="#191919"><g><path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Request Processing</title><rect fill="white" width="576" height="733"/><g><title>no exceptions</title><path d="M 155 444.75674 C 155 450.64061 155 486.2592 155 502.71617" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 322.33334 C 154.99999 327.72413 155 337.74646 155 346.1775" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 245.22768 C 154.99999 250.5417 154.99999 257.93189 154.99999 265.10145" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99995 198.62203 C 154.99995 203.74682 154.99998 209.1909 154.99999 215.28222" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 50.455358)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="4.7596016" y="10" textLength="88.92578">middleware ingress </tspan></text><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 101.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.983723" y="10" textLength="61.69922">tween ingress</tspan></text><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 227.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="28.660969" y="10" textLength="38.344727">traversal</tspan></text><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 252.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.424641" y="10" textLength="62.817383">ContextFound</tspan></text><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 427.48442)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.094563" y="10" textLength="59.47754">tween egress</tspan></text><rect x="239" y="445.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="445.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 450.50821)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.3113594" y="10" textLength="85.043945">response callbacks</tspan></text><rect x="239" y="497.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="497.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 502.5082)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.6463203" y="10" textLength="5">fi</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="13.64632" y="10" textLength="73.374023">nished callbacks</tspan></text><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 514.89027)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.8704414" y="10" textLength="83.92578">middleware egress</tspan></text><path d="M 155 67.72768 C 155 73.048893 155 81.55558 155 89.2853" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 155 119.22768 C 155 124.62026 154.99997 133.48763 154.99996 141.38632" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="375.5" y="400.5" width="105.666664" height="22.544642" fill="#dfbeff"/><rect x="375.5" y="400.5" width="105.666664" height="22.544642" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(380.5 405.77232)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.702961" y="10" textLength="62.260742">BeforeRender</tspan></text><text transform="translate(233.5 20)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".31445312" y="11" textLength="115.371094">Request Processing</tspan></text><path d="M 375.99995 42.910746 L 498.66662 42.910746 C 501.42805 42.910746 503.66662 45.149323 503.66662 47.910746 L 503.66662 222 C 503.66662 224.76142 501.42805 227 498.66662 227 L 375.99995 227 C 373.23853 227 370.99995 224.76142 370.99995 222 L 370.99995 47.910746 C 370.99995 45.149323 373.23853 42.910746 375.99995 42.910746 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(375.99995 42.910746)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="0" y="10" textLength="35.55664">Legend</tspan></text><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 69.180834)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="35.601887" y="10" textLength="24.46289">event</tspan></text><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" fill="#fed153"/><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 191.85458)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="29.769367" y="10" textLength="36.12793">callback</tspan></text><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" fill="#ffff6c"/><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 163.8223)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="21.158527" y="10" textLength="53.34961">view deriver</tspan></text><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" fill="#a4cfff"/><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 96.48543)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.148762" y="10" textLength="76.14746">external process </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="2.8162422" y="22" textLength="90.03418">(middleware, tween)</tspan></text><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 135.79003)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.537922" y="10" textLength="70.59082">internal process</tspan></text><line x1="154.99999" y1="258.44082" x2="238.83336" y2="258.45536" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 363.61979)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.205402" y="10" textLength="57.25586">view pipeline</tspan></text><path d="M 155 386.66443 C 155 392.17252 155 405.5052 155 415.30935" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="239.25039" y1="276.57838" x2="207.66667" y2="353.07515" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><line x1="238.75" y1="439.80676" x2="207.66667" y2="385.656" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" fill="#d2ffd0"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 307.71132)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="24.764484" y="10" textLength="46.137695">predicates</tspan></text><rect x="102.16666" y="272" width="105.666664" height="33.089294" fill="#d2ffd0"/><rect x="102.16666" y="272" width="105.666664" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 282.54465)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="21.707844" y="10" textLength="52.250977">view lookup</tspan></text><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" fill="#d2ffd0"/><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 184)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.978855" y="10" textLength="71.708984">route predicates</tspan></text><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" fill="#d2ffd0"/><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 158.83333)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.001804" y="10" textLength="20.004883">URL</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.640476" y="10" textLength="40.024414"> dispatch</tspan></text><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.8334 122.59152)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.207844" y="10" textLength="57.250977">NewRequest</tspan></text><line x1="154.99999" y1="128.68025" x2="239.8334" y2="128.59152" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 476.49852)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="15.316242" y="10" textLength="65.03418">NewResponse</tspan></text><line x1="155" y1="470.25295" x2="238.33861" y2="482.42625" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75" y="331.26348" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="331.26348" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 334.5915)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="1.9812927" y="10" textLength="91.7041">view mapper ingress</tspan></text><rect x="238.75" y="350.36561" width="105.66669" height="33.089283" fill="#ffff6c"/><rect x="238.75" y="350.36561" width="105.66669" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 360.91025)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.830902" y="10" textLength="20.004883">view</tspan></text><rect x="238.75" y="383.901" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="383.901" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 387.22901)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="3.0921326" y="10" textLength="89.48242">view mapper egress</tspan></text><rect x="238.75" y="402.55704" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="402.55704" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 405.88507)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.917328" y="10" textLength="77.83203">response adapter</tspan></text><rect x="238.75" y="312.65604" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="312.65604" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 315.98407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="6.7029724" y="10" textLength="82.26074">decorators ingress</tspan></text><rect x="238.75" y="421.1507" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="421.1507" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 424.47873)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="7.8138123" y="10" textLength="80.039062">decorators egress</tspan></text><rect x="238.75" y="276" width="105.75003" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="276" width="105.75003" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 279.32802)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.244644" y="10" textLength="57.260742">authorization</tspan></text><line x1="155" y1="482.12575" x2="238.52297" y2="508.3584" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="155" y1="459.27668" x2="238.50027" y2="456.52468" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="344.41668" y1="411.88507" x2="375.5" y2="411.77232" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="239.83336" y="197.875" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.83336" y="197.875" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.83336 203.14732)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.44759" y="10" textLength="35.57129">BeforeT</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="47.652668" y="10" textLength="35.566406">raversal</tspan></text><line x1="154.99998" y1="209.11366" x2="239.83336" y2="209.14732" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75" y="294.65604" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="294.65604" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 297.98407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="17.27182" y="10" textLength="61.123047">CSRF checks</tspan></text></g></g></svg>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="91 11 424 563" width="424pt" height="563pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2017-04-10 09:33Z</dc:date><!-- Produced by OmniGraffle Professional 5.4.4 --></metadata><defs><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="#191919"><g><path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Request Processing</title><rect fill="white" width="576" height="733"/><g><title>no exceptions</title><path d="M 155 475.25674 C 155 481.14061 155 516.7592 155 533.21617" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 352.83334 C 154.99999 358.22413 155 368.24646 155 376.6775" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 275.72768 C 154.99999 281.0417 154.99999 288.43189 154.99999 295.60145" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99995 232.12203 C 154.99995 237.07358 154.99998 240.81523 154.99999 245.78103" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 50.455358)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="4.7596016" y="10" textLength="88.92578">middleware ingress </tspan></text><rect x="102.16667" y="133.68304" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="133.68304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 138.95536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.983723" y="10" textLength="61.69922">tween ingress</tspan></text><rect x="102.16667" y="252.68304" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="102.16667" y="252.68304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 257.95536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="28.660969" y="10" textLength="38.344727">traversal</tspan></text><rect x="238.83336" y="277.68304" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="277.68304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 282.95536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.424641" y="10" textLength="62.817383">ContextFound</tspan></text><rect x="102.16667" y="452.7121" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="452.7121" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 457.98442)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.094563" y="10" textLength="59.47754">tween egress</tspan></text><rect x="239" y="475.7359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="475.7359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 481.0082)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.3113594" y="10" textLength="85.043945">response callbacks</tspan></text><rect x="239" y="527.7359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="527.7359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 533.0082)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.6463203" y="10" textLength="5">fi</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="13.64632" y="10" textLength="73.374023">nished callbacks</tspan></text><rect x="102.16667" y="540.11795" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="540.11795" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 545.39027)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.8704414" y="10" textLength="83.92578">middleware egress</tspan></text><path d="M 155 67.72768 C 155 72.60557 155 75.81567 155 80.374542" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 155 156.72768 C 155 161.9728 154.999975 168.29494 154.99996 174.89142" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="375.5" y="431" width="105.666664" height="22.544642" fill="#dfbeff"/><rect x="375.5" y="431" width="105.666664" height="22.544642" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(380.5 436.27232)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.702961" y="10" textLength="62.260742">BeforeRender</tspan></text><text transform="translate(233.5 20)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".31445312" y="11" textLength="115.371094">Request Processing</tspan></text><path d="M 375.99995 42.910746 L 498.66662 42.910746 C 501.42805 42.910746 503.66662 45.149323 503.66662 47.910746 L 503.66662 222 C 503.66662 224.76142 501.42805 227 498.66662 227 L 375.99995 227 C 373.23853 227 370.99995 224.76142 370.99995 222 L 370.99995 47.910746 C 370.99995 45.149323 373.23853 42.910746 375.99995 42.910746 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(375.99995 42.910746)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="0" y="10" textLength="35.55664">Legend</tspan></text><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 69.180834)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="35.601887" y="10" textLength="24.46289">event</tspan></text><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" fill="#fed153"/><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 191.85458)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="29.769367" y="10" textLength="36.12793">callback</tspan></text><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" fill="#ffff6c"/><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 163.8223)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="21.158527" y="10" textLength="53.34961">view deriver</tspan></text><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" fill="#a4cfff"/><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 96.48543)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.148762" y="10" textLength="76.14746">external process </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="2.8162422" y="22" textLength="90.03418">(middleware, tween)</tspan></text><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 135.79003)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.537922" y="10" textLength="70.59082">internal process</tspan></text><line x1="154.99999" y1="288.94082" x2="238.83336" y2="288.95536" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="102.16667" y="383.57515" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="102.16667" y="383.57515" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 394.11979)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.205402" y="10" textLength="57.25586">view pipeline</tspan></text><path d="M 155 417.16443 C 155 422.67252 155 436.0052 155 445.80935" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="239.25039" y1="307.07838" x2="207.66667" y2="383.57515" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><line x1="238.75" y1="470.30676" x2="207.66667" y2="416.156" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><rect x="102.16666" y="335.5893" width="105.666664" height="17.244049" fill="#d2ffd0"/><rect x="102.16666" y="335.5893" width="105.666664" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 338.21132)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="24.764484" y="10" textLength="46.137695">predicates</tspan></text><rect x="102.16666" y="302.5" width="105.666664" height="33.089294" fill="#d2ffd0"/><rect x="102.16666" y="302.5" width="105.666664" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 313.04465)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="21.707844" y="10" textLength="52.250977">view lookup</tspan></text><rect x="102.166606" y="214.87798" width="105.666695" height="17.244049" fill="#d2ffd0"/><rect x="102.166606" y="214.87798" width="105.666695" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 217.5)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.978855" y="10" textLength="71.708984">route predicates</tspan></text><rect x="102.166606" y="181.78869" width="105.666695" height="33.089294" fill="#d2ffd0"/><rect x="102.166606" y="181.78869" width="105.666695" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 192.33333)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.001804" y="10" textLength="20.004883">URL</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.640476" y="10" textLength="40.024414"> dispatch</tspan></text><rect x="239.8334" y="153.3192" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.8334" y="153.3192" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.8334 158.59152)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.207844" y="10" textLength="57.250977">NewRequest</tspan></text><line x1="154.99999" y1="165.00132" x2="239.8334" y2="164.59152" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.83336" y="501.7262" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="501.7262" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 506.99852)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="15.316242" y="10" textLength="65.03418">NewResponse</tspan></text><line x1="155" y1="500.75295" x2="238.33861" y2="512.92625" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75" y="361.76348" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="361.76348" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 365.0915)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="1.9812927" y="10" textLength="91.7041">view mapper ingress</tspan></text><rect x="238.75" y="380.86561" width="105.66669" height="33.089283" fill="#ffff6c"/><rect x="238.75" y="380.86561" width="105.66669" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 391.41025)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.830902" y="10" textLength="20.004883">view</tspan></text><rect x="238.75" y="414.401" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="414.401" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 417.72901)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="3.0921326" y="10" textLength="89.48242">view mapper egress</tspan></text><rect x="238.75" y="433.05704" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="433.05704" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 436.38507)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.917328" y="10" textLength="77.83203">response adapter</tspan></text><rect x="238.75" y="343.15604" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="343.15604" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 346.48407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="6.7029724" y="10" textLength="82.26074">decorators ingress</tspan></text><rect x="238.75" y="451.6507" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="451.6507" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 454.97873)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="7.8138123" y="10" textLength="80.039062">decorators egress</tspan></text><rect x="238.75" y="306.5" width="105.75003" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="306.5" width="105.75003" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 309.82802)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.244644" y="10" textLength="57.260742">authorization</tspan></text><line x1="155" y1="512.62575" x2="238.52297" y2="538.8584" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="155" y1="489.77668" x2="238.50027" y2="487.02468" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="344.41668" y1="442.38507" x2="375.5" y2="442.27232" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="239.83336" y="229.375" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.83336" y="229.375" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.83336 234.64732)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.44759" y="10" textLength="35.57129">BeforeT</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="47.652668" y="10" textLength="35.566406">raversal</tspan></text><line x1="154.99998" y1="241.21657" x2="239.83336" y2="240.64732" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75" y="325.15604" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="325.15604" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 328.48407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="17.27182" y="10" textLength="61.123047">CSRF checks</tspan></text><rect x="102.16667" y="87.27679" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="87.27679" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 92.54911)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.25716" y="10" textLength="71.152344">execution policy</tspan></text><path d="M 155 109.82143 C 155 114.961226 155 120.58499 155 126.78393" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/></g></g></svg>
diff --git a/docs/api/config.rst b/docs/api/config.rst
index 62f138b76..a785b64ad 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -37,6 +37,7 @@
.. automethod:: set_authentication_policy
.. automethod:: set_authorization_policy
.. automethod:: set_default_csrf_options
+ .. automethod:: set_csrf_storage_policy
.. automethod:: set_default_permission
.. automethod:: add_permission
@@ -70,6 +71,7 @@
.. automethod:: add_subscriber_predicate
.. automethod:: add_view_predicate
.. automethod:: add_view_deriver
+ .. automethod:: set_execution_policy
.. automethod:: set_request_factory
.. automethod:: set_root_factory
.. automethod:: set_session_factory
diff --git a/docs/api/csrf.rst b/docs/api/csrf.rst
new file mode 100644
index 000000000..38501546e
--- /dev/null
+++ b/docs/api/csrf.rst
@@ -0,0 +1,23 @@
+.. _csrf_module:
+
+:mod:`pyramid.csrf`
+-------------------
+
+.. automodule:: pyramid.csrf
+
+ .. autoclass:: LegacySessionCSRFStoragePolicy
+ :members:
+
+ .. autoclass:: SessionCSRFStoragePolicy
+ :members:
+
+ .. autoclass:: CookieCSRFStoragePolicy
+ :members:
+
+ .. autofunction:: get_csrf_token
+
+ .. autofunction:: new_csrf_token
+
+ .. autofunction:: check_csrf_origin
+
+ .. autofunction:: check_csrf_token
diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst
index 521d65d2b..e542a6be0 100644
--- a/docs/api/interfaces.rst
+++ b/docs/api/interfaces.rst
@@ -44,6 +44,9 @@ Other Interfaces
.. autointerface:: IRoutePregenerator
:members:
+ .. autointerface:: ICSRFStoragePolicy
+ :members:
+
.. autointerface:: ISession
:members:
@@ -65,6 +68,9 @@ Other Interfaces
.. autointerface:: IResponseFactory
:members:
+ .. autointerface:: IRouter
+ :members:
+
.. autointerface:: IViewMapperFactory
:members:
diff --git a/docs/api/paster.rst b/docs/api/paster.rst
index 27bc81a1f..f0784d0f8 100644
--- a/docs/api/paster.rst
+++ b/docs/api/paster.rst
@@ -7,8 +7,8 @@
.. autofunction:: bootstrap
- .. autofunction:: get_app(config_uri, name=None, options=None)
+ .. autofunction:: get_app
- .. autofunction:: get_appsettings(config_uri, name=None, options=None)
+ .. autofunction:: get_appsettings
- .. autofunction:: setup_logging(config_uri, global_conf=None)
+ .. autofunction:: setup_logging
diff --git a/docs/api/security.rst b/docs/api/security.rst
index 88086dbbf..116459226 100644
--- a/docs/api/security.rst
+++ b/docs/api/security.rst
@@ -80,15 +80,23 @@ Return Values
'george', 'read')`` that means deny access. A sequence of ACEs
makes up an ACL. It is a string, and its actual value is "Deny".
+.. autoclass:: Denied
+ :members: msg
+
+ .. automethod:: __new__
+
+.. autoclass:: Allowed
+ :members: msg
+
+ .. automethod:: __new__
+
.. autoclass:: ACLDenied
- :members:
+ :members: msg
-.. autoclass:: ACLAllowed
- :members:
+ .. automethod:: __new__
-.. autoclass:: Denied
- :members:
+.. autoclass:: ACLAllowed
+ :members: msg
-.. autoclass:: Allowed
- :members:
+ .. automethod:: __new__
diff --git a/docs/api/session.rst b/docs/api/session.rst
index 56c4f52d7..53bae7c52 100644
--- a/docs/api/session.rst
+++ b/docs/api/session.rst
@@ -9,10 +9,6 @@
.. autofunction:: signed_deserialize
- .. autofunction:: check_csrf_origin
-
- .. autofunction:: check_csrf_token
-
.. autofunction:: SignedCookieSessionFactory
.. autofunction:: UnencryptedCookieSessionFactoryConfig
diff --git a/docs/api/url.rst b/docs/api/url.rst
index 131d85806..8aaabc352 100644
--- a/docs/api/url.rst
+++ b/docs/api/url.rst
@@ -5,7 +5,7 @@
.. automodule:: pyramid.url
- .. autofunction:: pyramid.url.resource_url(context, request, *elements, query=None, anchor=None)
+ .. autofunction:: resource_url
.. autofunction:: route_url
diff --git a/docs/changes.rst b/docs/changes.rst
index fdeaf1e99..61d639a16 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -3,11 +3,11 @@
:app:`Pyramid` Change History
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. include:: ../CHANGES.txt
+.. include:: ../CHANGES.rst
-.. include:: ../HISTORY.txt
+.. include:: ../HISTORY.rst
:mod:`repoze.bfg` Change History (previous name for Pyramid)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. include:: ../BFG_HISTORY.txt
+.. include:: ../BFG_HISTORY.rst
diff --git a/docs/conf.py b/docs/conf.py
index df58064e5..cf92e05e8 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -62,25 +62,26 @@ extensions = [
# Looks for objects in external projects
intersphinx_mapping = {
- 'colander': ('http://docs.pylonsproject.org/projects/colander/en/latest', None),
- 'cookbook': ('http://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/', None),
+ 'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest', None),
+ 'cookbook': ('https://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/', None),
'cookiecutter': ('https://cookiecutter.readthedocs.io/en/latest/', None),
- 'deform': ('http://docs.pylonsproject.org/projects/deform/en/latest', None),
- 'jinja2': ('http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None),
- 'pylonswebframework': ('http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None),
+ 'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest', None),
+ 'jinja2': ('https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None),
+ 'plaster': ('https://docs.pylonsproject.org/projects/plaster/en/latest/', None),
+ 'pylonswebframework': ('https://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None),
'python': ('https://docs.python.org/3', None),
- 'pytest': ('http://pytest.org/latest/', None),
+ 'pytest': ('https://docs.pytest.org/en/latest/', None),
'sphinx': ('http://www.sphinx-doc.org/en/latest', None),
'sqla': ('http://docs.sqlalchemy.org/en/latest', None),
- 'tm': ('http://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None),
- 'toolbar': ('http://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest', None),
- 'tstring': ('http://docs.pylonsproject.org/projects/translationstring/en/latest', None),
- 'tutorials': ('http://docs.pylonsproject.org/projects/pyramid-tutorials/en/latest/', None),
- 'venusian': ('http://docs.pylonsproject.org/projects/venusian/en/latest', None),
- 'webob': ('http://docs.webob.org/en/latest', None),
+ 'tm': ('https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None),
+ 'toolbar': ('https://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest', None),
+ 'tstring': ('https://docs.pylonsproject.org/projects/translationstring/en/latest', None),
+ 'tutorials': ('https://docs.pylonsproject.org/projects/pyramid-tutorials/en/latest/', None),
+ 'venusian': ('https://docs.pylonsproject.org/projects/venusian/en/latest', None),
+ 'webob': ('https://docs.pylonsproject.org/projects/webob/en/latest/', None),
'webtest': ('http://webtest.pythonpaste.org/en/latest', None),
- 'who': ('http://repozewho.readthedocs.org/en/latest', None),
- 'zcml': ('http://docs.pylonsproject.org/projects/pyramid-zcml/en/latest', None),
+ 'who': ('http://repozewho.readthedocs.io/en/latest', None),
+ 'zcml': ('https://docs.pylonsproject.org/projects/pyramid-zcml/en/latest', None),
'zcomponent': ('http://zopecomponent.readthedocs.io/en/latest/', None),
'zinterface': ('http://zopeinterface.readthedocs.io/en/latest/', None),
}
@@ -135,7 +136,7 @@ if book:
# -----------------------
# enable pylons_sphinx_latesturl when this branch is no longer "latest"
# pylons_sphinx_latesturl_base = (
-# 'http://docs.pylonsproject.org/projects/pyramid/en/latest/')
+# 'https://docs.pylonsproject.org/projects/pyramid/en/latest/')
# pylons_sphinx_latesturl_pagename_overrides = {
# # map old pagename -> new pagename
# 'whatsnew-1.0': 'index',
@@ -147,6 +148,7 @@ if book:
# 'whatsnew-1.6': 'index',
# 'whatsnew-1.7': 'index',
# 'whatsnew-1.8': 'index',
+# 'whatsnew-1.9': 'index',
# 'tutorials/gae/index': 'index',
# 'api/chameleon_text': 'api',
# 'api/chameleon_zpt': 'api',
@@ -171,9 +173,10 @@ html_title = 'The Pyramid Web Framework v%s' % release
# using the given strftime format.
html_last_updated_fmt = '%b %d, %Y'
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-html_use_smartypants = False # people use cutnpaste in some places
+# Do not use smart quotes.
+smartquotes = False
+# Remove next line when RTD goes to Sphinx==1.6.6
+html_use_smartypants = False
# Output file base name for HTML help builder.
htmlhelp_basename = 'pyramid'
diff --git a/docs/copyright.rst b/docs/copyright.rst
index 30ae40603..be47aef33 100644
--- a/docs/copyright.rst
+++ b/docs/copyright.rst
@@ -18,7 +18,7 @@ First print publishing: February, 2011
All rights reserved. This material may be copied or distributed only
subject to the terms and conditions set forth in the `Creative Commons
Attribution-Noncommercial-Share Alike 3.0 United States License
-<http://creativecommons.org/licenses/by-nc-sa/3.0/us/>`_. You must
+<https://creativecommons.org/licenses/by-nc-sa/3.0/us/>`_. You must
give the original author credit. You may not use this work for
commercial purposes. If you alter, transform, or build upon this
work, you may distribute the resulting work only under the same or
@@ -96,7 +96,7 @@ HTML Version and Source Code
----------------------------
An HTML version of this book is freely available via
-http://docs.pylonsproject.org/projects/pyramid/en/latest/
+https://docs.pylonsproject.org/projects/pyramid/en/latest/
The source code for the examples used in this book are available
within the :app:`Pyramid` software distribution, always available
diff --git a/docs/designdefense.rst b/docs/designdefense.rst
index 0a72ff27d..c0a1f8336 100644
--- a/docs/designdefense.rst
+++ b/docs/designdefense.rst
@@ -1433,7 +1433,7 @@ object which *is not logically global*:
# credentials were invalid
The `Pylons 1.X
-<http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/>`_
+<https://docs.pylonsproject.org/projects/pylons-webframework/en/latest/>`_
web framework uses a similar strategy. It calls these things "Stacked Object
Proxies", so, for purposes of this discussion, I'll do so as well.
@@ -1529,20 +1529,20 @@ inlined comments take into account what we've discussed in the
.. code-block:: python
:linenos:
- from pyramid.response import Response # explicit response, no thread local
- from wsgiref.simple_server import make_server # explicitly WSGI
+ from wsgiref.simple_server import make_server # explicitly WSGI
+ from pyramid.config import Configurator # to configure app registry
+ from pyramid.response import Response # explicit response, no threadlocal
- def hello_world(request): # accepts a request; no request thread local reqd
+ def hello_world(request): # accept a request; no request threadlocal reqd
# explicit response object means no response threadlocal
return Response('Hello world!')
if __name__ == '__main__':
- from pyramid.config import Configurator
- config = Configurator() # no global application object
- config.add_view(hello_world) # explicit non-decorator registration
- app = config.make_wsgi_app() # explicitly WSGI
+ with Configurator() as config: # no global application object
+ config.add_view(hello_world) # explicit non-decorator registration
+ app = config.make_wsgi_app() # explicitly WSGI
server = make_server('0.0.0.0', 8080, app)
- server.serve_forever() # explicitly WSGI
+ server.serve_forever() # explicitly WSGI
Pyramid doesn't offer pluggable apps
@@ -1634,7 +1634,7 @@ Let's take this criticism point-by-point.
Too Complex
+++++++++++
-If you can understand this hello world program, you can use Pyramid:
+If you can understand this "hello world" program, you can use Pyramid:
.. code-block:: python
:linenos:
@@ -1647,9 +1647,9 @@ If you can understand this hello world program, you can use Pyramid:
return Response('Hello world!')
if __name__ == '__main__':
- config = Configurator()
- config.add_view(hello_world)
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_view(hello_world)
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 0f299c169..7f1147fa1 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -321,7 +321,7 @@ Glossary
:term:`principal` (or principals) associated with a request.
WSGI
- `Web Server Gateway Interface <http://wsgi.readthedocs.org/en/latest/>`_.
+ `Web Server Gateway Interface <https://wsgi.readthedocs.io/en/latest/>`_.
This is a Python standard for connecting web applications to web servers,
similar to the concept of Java Servlets. :app:`Pyramid` requires that
your application be served as a WSGI application.
@@ -330,8 +330,8 @@ Glossary
*Middleware* is a :term:`WSGI` concept. It is a WSGI component
that acts both as a server and an application. Interesting uses
for middleware exist, such as caching, content-transport
- encoding, and other functions. See `WSGI.org
- <http://wsgi.readthedocs.org/en/latest/>`_ or `PyPI
+ encoding, and other functions. See `WSGI documentation
+ <https://wsgi.readthedocs.io/en/latest/>`_ or `PyPI
<https://pypi.python.org/pypi>`_ to find middleware for your application.
pipeline
@@ -339,7 +339,7 @@ Glossary
server, a WSGI application, with a set of :term:`middleware` in-between.
Zope
- `The Z Object Publishing Framework <http://zope.org>`_, a
+ `The Z Object Publishing Framework <http://www.zope.org/en/latest/>`_, a
full-featured Python web framework.
Grok
@@ -349,7 +349,7 @@ Glossary
`A full-featured Python web framework <https://www.djangoproject.com/>`_.
Pylons
- `A lightweight Python web framework <http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/>`_
+ `A lightweight Python web framework <https://docs.pylonsproject.org/projects/pylons-webframework/en/latest/>`_
and a predecessor of Pyramid.
ZODB
@@ -357,17 +357,25 @@ Glossary
Python object store.
WebOb
- `WebOb <http://webob.org>`_ is a WSGI request/response
+ `WebOb <https://webob.org>`_ is a WSGI request/response
library created by Ian Bicking.
PasteDeploy
- `PasteDeploy <http://pythonpaste.org/deploy/>`_ is a library used by
+ `PasteDeploy <https://pastedeploy.readthedocs.io/en/latest/>`_ is a library used by
:app:`Pyramid` which makes it possible to configure
:term:`WSGI` components together declaratively within an ``.ini``
file. It was developed by Ian Bicking.
+ plaster
+ `plaster <https://docs.pylonsproject.org/projects/plaster/en/latest/>`_ is
+ a library used by :app:`Pyramid` which acts as an abstraction between
+ command-line scripts and the file format used to load the :term:`WSGI`
+ components and application settings. By default :app:`Pyramid` ships
+ with the ``plaster_pastedeploy`` library installed which provides
+ integrated support for loading a :term:`PasteDeploy` INI file.
+
Chameleon
- `chameleon <https://chameleon.readthedocs.org/en/latest/>`_ is an
+ `chameleon <https://chameleon.readthedocs.io/en/latest/>`_ is an
attribute language template compiler which supports the :term:`ZPT`
templating specification. It is written and maintained by Malthe Borch. It
has several extensions, such as the ability to use bracketed (Mako-style)
@@ -393,7 +401,7 @@ Glossary
A `text templating language <http://jinja.pocoo.org/>`_ by Armin Ronacher.
Routes
- A `system by Ben Bangert <http://routes.readthedocs.org/en/latest/>`_
+ A `system by Ben Bangert <https://routes.readthedocs.io/en/latest/>`_
which parses URLs and compares them against a number of user defined
mappings. The URL pattern matching syntax in :app:`Pyramid` is inspired by
the Routes syntax (which was inspired by Ruby On Rails pattern syntax).
@@ -492,7 +500,7 @@ Glossary
information.
repoze.who
- `Authentication middleware <http://repozewho.readthedocs.org/en/latest/>`_
+ `Authentication middleware <https://repozewho.readthedocs.io/en/latest/>`_
for :term:`WSGI` applications. It can be used by :app:`Pyramid` to
provide authentication information.
@@ -568,7 +576,7 @@ Glossary
:ref:`adding_and_overriding_renderers` for more information.
mod_wsgi
- `mod_wsgi <https://modwsgi.readthedocs.io>`_ is an Apache
+ `mod_wsgi <https://modwsgi.readthedocs.io/en/develop/>`_ is an Apache
module developed by Graham Dumpleton. It allows :term:`WSGI` applications
(such as applications developed using :app:`Pyramid`) to be served using
the Apache web server.
@@ -743,7 +751,7 @@ Glossary
:ref:`Venusian` is a library which
allows framework authors to defer decorator actions. Instead of
taking actions when a function (or class) decorator is executed
- at import time, the action usually taken by the decorator is
+ at :term:`import time`, the action usually taken by the decorator is
deferred until a separate "scan" phase. :app:`Pyramid` relies
on Venusian to provide a basis for its :term:`scan` feature.
@@ -891,6 +899,11 @@ Glossary
:meth:`pyramid.config.Configurator.set_session_factory` for more
information.
+ CSRF storage policy
+ A utility that implements :class:`pyramid.interfaces.ICSRFStoragePolicy`
+ which is responsible for allocating CSRF tokens to a user and verifying
+ that a provided token is acceptable.
+
Mako
`Mako <http://www.makotemplates.org/>`_ is a template language
which refines the familiar ideas of componentized layout and inheritance
@@ -941,16 +954,16 @@ Glossary
pyramid_handlers
An add-on package which allows :app:`Pyramid` users to create classes
that are analogues of Pylons 1 "controllers". See
- http://docs.pylonsproject.org/projects/pyramid_handlers/en/latest/.
+ https://docs.pylonsproject.org/projects/pyramid_handlers/en/latest/.
pyramid_jinja2
:term:`Jinja2` templating system bindings for Pyramid, documented at
- http://docs.pylonsproject.org/projects/pyramid_jinja2/en/latest/. This
+ https://docs.pylonsproject.org/projects/pyramid_jinja2/en/latest/. This
package also includes a scaffold named ``pyramid_jinja2_starter``, which
creates an application package based on the Jinja2 templating system.
Akhet
- `Akhet <http://docs.pylonsproject.org/projects/akhet/en/latest/>`_ is a
+ `Akhet <https://docs.pylonsproject.org/projects/akhet/en/latest/>`_ is a
Pyramid library and demo application with a Pylons-like feel.
It's most known for its former application scaffold, which helped
users transition from Pylons and those preferring a more Pylons-like API.
@@ -993,7 +1006,7 @@ Glossary
database information. :mod:`pyramid_debugtoolbar` is configured into
the ``development.ini`` of all applications which use a Pyramid
:term:`cookiecutter`. For more information, see
- http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest/.
+ https://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest/.
scaffold
A project template that generates some of the major parts of a Pyramid
@@ -1010,7 +1023,7 @@ Glossary
used in production applications, because the logger can be configured to
log to a file, to UNIX syslog, to the Windows Event Log, or even to
email. See its `documentation
- <http://docs.pylonsproject.org/projects/pyramid_exclog/en/latest/>`_.
+ <https://docs.pylonsproject.org/projects/pyramid_exclog/en/latest/>`_.
console script
A script written to the ``bin`` (on UNIX, or ``Scripts`` on Windows)
@@ -1065,7 +1078,7 @@ Glossary
A :term:`WSGI` server that runs on UNIX and Windows under Python 2.7+
and Python 3.3+. Projects generated via Pyramid cookiecutters use
Waitress as a WGSI server. See
- http://docs.pylonsproject.org/projects/waitress/en/latest/ for detailed
+ https://docs.pylonsproject.org/projects/waitress/en/latest/ for detailed
information.
Green Unicorn
@@ -1117,7 +1130,7 @@ Glossary
The :term:`Python Packaging Authority` formerly recommended using the
``pyvenv`` command for `creating virtual environments on Python 3.4 and
3.5
- <https://packaging.python.org/en/latest/installing/#creating-virtual-environments>`_,
+ <https://packaging.python.org/tutorials/installing-packages/#creating-virtual-environments>`_,
but it was deprecated in 3.6 in favor of ``python3 -m venv`` on UNIX or
``python -m venv`` on Windows, which is backward compatible on Python
3.3 and greater.
@@ -1154,3 +1167,42 @@ Glossary
coverage
A measurement of code coverage, usually expressed as a percentage of which lines of code have been executed over which lines are executable, typically run during test execution.
+
+ execution policy
+ A policy which wraps the :term:`router` by creating the request object
+ and sending it through the request pipeline.
+ See :class:`pyramid.config.Configurator.set_execution_policy`.
+
+ singleton
+ A singleton is a class which will only ever have one instance.
+ As there is only one, it is shared by all other code.
+ This makes it an example of :term:`global state`.
+
+ Using a singleton is `considered a poor design choice. <https://softwareengineering.stackexchange.com/questions/148108/why-is-global-state-so-evil>`_
+ As :term:`mutable` global state, it can be changed by any other code,
+ and so the values it represents cannot be reasoned about or tested properly.
+
+ global state
+ A set of values that are available to the entirety of a program.
+
+ mutable
+ In Python, a value is mutable if it can be changed *in place*.
+ The Python ``list`` and ``dict`` types are mutable.
+ When a value is added to or removed from an instance of either, the original object remains.
+ The opposite of mutable is :term:`immutable`.
+
+ immutable
+ In Python, a value is immutable if it cannot be changed.
+ The Python ``str``, ``int``, and ``tuple`` data types are all ``immutable``.
+
+ import time
+ In Python, the moment when a module is referred to in an ``import`` statement.
+ At this moment, all statements in that module at the module scope (at the left margin) are executed.
+ It is a bad design decision to put statements in a Python module that have :term:`side effect`\ s at import time.
+
+ side effect
+ A statement or function has a side effect when it changes a value outside its own scope.
+ Put another way, if one can observe the change made by a function from outside that function, it has a side effect.
+
+ context manager
+ A context manager is an object that defines the runtime context to be established when executing a :ref:`with <python:with>` statement in Python. The context manager handles the entry into, and the exit from, the desired runtime context for the execution of the block of code. Context managers are normally invoked using the ``with`` statement, but can also be used by directly invoking their methods. Pyramid adds context managers for :class:`pyramid.config.Configurator`, :meth:`pyramid.interfaces.IRouter.request_context`, :func:`pyramid.paster.bootstrap`, :func:`pyramid.scripting.prepare`, and :func:`pyramid.testing.testConfig`. See also the Python documentation for :ref:`With Statement Context Managers <python:context-managers>` and :pep:`343`.
diff --git a/docs/index.rst b/docs/index.rst
index ed5b458ea..3cd764b2f 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -5,7 +5,7 @@ The Pyramid Web Framework
=========================
:app:`Pyramid` is a small, fast, down-to-earth Python web framework. It is
-developed as part of the `Pylons Project <http://pylonsproject.org/>`_.
+developed as part of the `Pylons Project <https://pylonsproject.org>`_.
It is licensed under a `BSD-like license <http://repoze.org/license.html>`_.
Here is one of the simplest :app:`Pyramid` applications you can make:
@@ -35,9 +35,6 @@ speed right away.
* :doc:`quick_tour` gives an overview of the major features in Pyramid,
covering a little about a lot.
-* :doc:`quick_tutorial/index` is similar to the Quick Tour, but in a tutorial
- format, with somewhat deeper treatment of each topic and with working code.
-
* Like learning by example? Visit the official :ref:`html_tutorials` as well as
the community-contributed :ref:`Pyramid Tutorials
<tutorials:pyramid-tutorials>` and :ref:`Pyramid Community Cookbook
@@ -53,13 +50,12 @@ speed right away.
Tutorials
=========
-Official tutorials explaining how to use :app:`Pyramid` to build various types
-of applications, and how to deploy :app:`Pyramid` applications to various
-platforms.
+Official tutorials provide a quick overview of :app:`Pyramid`'s features in more depth than the Quick Tour and with working code, explain how to use :app:`Pyramid` to build various types of applications, and how to deploy :app:`Pyramid` applications to various platforms.
.. toctree::
:maxdepth: 1
+ quick_tutorial/index
tutorials/wiki2/index
tutorials/wiki/index
tutorials/modwsgi/index
@@ -95,9 +91,7 @@ the trunk via ``git``, use either command:
# Otherwise, HTTPS will work, using your GitHub login:
git clone https://github.com/Pylons/pyramid.git
-To find out how to become a contributor to :app:`Pyramid`, please see the
-`contributor's section of the documentation
-<http://docs.pylonsproject.org/en/latest/#contributing>`_.
+To find out how to become a contributor to :app:`Pyramid`, please see `How to Contribute Source Code and Documentation <https://pylonsproject.org/community-how-to-contribute.html>`_.
.. _html_narrative_documentation:
@@ -185,6 +179,7 @@ Change History
.. toctree::
:maxdepth: 1
+ whatsnew-1.9
whatsnew-1.8
whatsnew-1.7
whatsnew-1.6
diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst
new file mode 100644
index 000000000..82e20963d
--- /dev/null
+++ b/docs/narr/advanced-features.rst
@@ -0,0 +1,330 @@
+Advanced :app:`Pyramid` Design Features
+=======================================
+
+:app:`Pyramid` has been built from the ground up to avoid the problems that other frameworks can suffer.
+
+You Don't Need Singletons
+-------------------------
+
+Have you ever struggled with parameterizing Django's ``settings.py`` file for multiple installations of the same Django application? Have you ever needed to monkey-patch a framework fixture to get it to behave properly for your use case? Have you ever tried to deploy your application using an asynchronous server and failed?
+
+All these problems are symptoms of :term:`mutable` :term:`global state`, also known as :term:`import time` :term:`side effect`\ s and arise from the use of :term:`singleton` data structures.
+
+:app:`Pyramid` is written so that you don't run into these types of problems. It is even possible to run multiple copies of the *same* :app:`Pyramid` application configured differently within a single Python process. This makes running :app:`Pyramid` in shared hosting environments a snap.
+
+Simplify your View Code with Predicates
+---------------------------------------
+
+How many times have you found yourself beginning the logic of your view code with something like this:
+
+.. code-block:: python
+ :linenos:
+
+ if request.user.is_authenticated:
+ # do one thing
+ else:
+ # do something else
+
+Unlike many other systems, :app:`Pyramid` allows you to associate more than one view with a single route. For example, you can create a route with the pattern ``/items`` and when the route is matched, you can send the request to one view if the request method is GET, another view if the request method is POST, and so on.
+
+:app:`Pyramid` uses a system of :term:`view predicate`\ s to allow this. Matching the request method is one basic thing you can do with a :term:`view predicate`. You can also associate views with other request parameters, such as elements in the query string, the Accept header, whether the request is an AJAX (XHR) request or not, and lots of other things.
+
+For our example above, you can do this instead:
+
+.. code-block:: python
+ :linenos:
+
+ @view_config(route_name="items", effective_principals=pyramid.security.Authenticated)
+ def auth_view(request):
+ # do one thing
+
+ @view_config(route_name="items")
+ def anon_view(request):
+ # do something else
+
+This approach allows you to develop view code that is simpler, more easily understandable, and more directly testable.
+
+.. seealso::
+
+ See also :ref:`view_configuration_parameters`.
+
+Stop Worrying About Transactions
+--------------------------------
+
+:app:`Pyramid`\ 's :term:`cookiecutter`\ s render projects that include a *transaction management* system. When you use this system, you can stop worrying about when to commit your changes, :app:`Pyramid` handles it for you. The system will commit at the end of a request or abort if there was an exception.
+
+Why is that a good thing? Imagine a situation where you manually commit a change to your persistence layer. It's very likely that other framework code will run *after* your changes are done. If an error happens in that other code, you can easily wind up with inconsistent data if you're not extremely careful.
+
+Using transaction management saves you from needing to think about this. Either a request completes successfully and all changes are committed, or it does not and all changes are aborted.
+
+:app:`Pyramid`\ 's transaction management is extendable, so you can synchronize commits between multiple databases or databases of different kinds. It also allows you to do things like conditionally send email if a transaction is committed, but otherwise keep quiet.
+
+.. seealso::
+
+ See also :ref:`bfg_sql_wiki_tutorial` (note the lack of commit statements anywhere in application code).
+
+Stop Worrying About Configuration
+---------------------------------
+
+When a system is small, it's reasonably easy to keep it all in your head. But as systems grow large, configuration grows more complex. Your app may grow to have hundreds or even thousands of configuration statements.
+
+:app:`Pyramid`\ 's configuration system keeps track of each of your statements. If you accidentally add two that are identical, or :app:`Pyramid` can't make sense out of what it would mean to have both statements active at the same time, it will complain loudly at startup time.
+
+:app:`Pyramid`\ 's configuration system is not dumb though. If you use the :meth:`~pyramid.config.Configurator.include` system, it can automatically resolve conflicts on its own. More local statements are preferred over less local ones. So you can intelligently factor large systems into smaller ones.
+
+.. seealso::
+
+ See also :ref:`conflict_detection`.
+
+Compose Powerful Apps From Simple Parts
+----------------------------------------
+
+Speaking of the :app:`Pyramid` structured :meth:`~pyramid.config.Configurator.include` mechanism, it allows you to compose complex applications from multiple, simple Python packages. All the configuration statements that can be performed in your main :app:`Pyramid` application can also be used in included packages. You can add views, routes, and subscribers, and even set authentication and authorization policies.
+
+If you need, you can extend or override the configuration of an existing application by including its configuration in your own and then modifying it.
+
+
+For example, if you want to reuse an existing application that already has a bunch of routes, you can just use the ``include`` statement with a ``route_prefix``. All the routes of that application will be availabe, prefixed as you requested:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.include('pyramid_jinja2')
+ config.include('pyramid_exclog')
+ config.include('some.other.package', route_prefix='/somethingelse')
+
+.. seealso::
+
+ See also :ref:`including_configuration` and :ref:`building_an_extensible_app`.
+
+Authenticate Users Your Way
+---------------------------
+
+:app:`Pyramid` ships with prebuilt, well-tested authentication and authorization schemes out of the box. Using a scheme is a matter of configuration. So if you need to change approaches later, you need only update your configuration.
+
+In addition, the system that handles authentication and authorization is flexible and pluggable. If you want to use another security add-on, or define your own, you can. And again, you need only update your application configuration to make the change.
+
+.. seealso::
+
+ See also :ref:`enabling_authorization_policy`.
+
+Build Trees of Resources
+------------------------
+
+:app:`Pyramid` supports :term:`traversal`, a way of mapping URLs to a concrete :term:`resource tree`. If your application naturally consists of an arbitrary heirarchy of different types of content (like a CMS or a Document Management System), traversal is for you. If you have a requirement for a highly granular security model ("Jane can edit documents in *this* folder, but not *that* one"), traversal can be a powerful approach.
+
+.. seealso::
+
+ See also :ref:`hello_traversal_chapter` and :ref:`much_ado_about_traversal_chapter`.
+
+Take Action on Each Request with Tweens
+---------------------------------------
+
+:app:`Pyramid` has a system for applying an arbitrary action to each request or response called a :term:`tween`. The system is similar in concept to WSGI :term:`middleware`, but can be more useful since :term:`tween`\ s run in the :app:`Pyramid` context, and have access to templates, request objects, and other niceties.
+
+The :app:`Pyramid` debug toolbar is a :term:`tween`, as is the ``pyramid_tm`` transaction manager.
+
+.. seealso::
+
+ See also :ref:`registering_tweens`.
+
+Return What You Want From Your Views
+------------------------------------
+
+We have shown elsewhere (in the :doc:`introduction`) how using a :term:`renderer` allows you to return simple Python dictionaries from your view code. But some frameworks allow you to return strings or tuples from view callables. When frameworks allow for this, code looks slightly prettier because there are fewer imports and less code. For example, compare this:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.response import Response
+
+ def aview(request):
+ return Response("Hello world!")
+
+To this:
+
+.. code-block:: python
+ :linenos:
+
+ def aview(request):
+ return "Hello world!"
+
+Nicer to look at, right?
+
+Out of the box, :app:`Pyramid` will raise an exception if you try to run the second example above. After all, a view should return a response, and "explicit is better than implicit".
+
+But if you're a developer who likes the aesthetics of simplicity, :app:`Pyramid` provides a way to support this sort of thing, the :term:`response adapter`\ :
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+ from pyramid.response import Response
+
+ def string_response_adapter(s):
+ response = Response(s)
+ response.content_type = 'text/html'
+ return response
+
+A new response adapter is registered in configuration:
+
+.. code-block:: python
+ :linenos:
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_response_adapter(string_response_adapter, str)
+
+With that, you may return strings from any of your view callables, e.g.:
+
+.. code-block:: python
+ :linenos:
+
+ def helloview(request):
+ return "Hello world!"
+
+ def goodbyeview(request):
+ return "Goodbye world!"
+
+You can even use a :term:`response adapter` to allow for custom content types and return codes:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+
+ def tuple_response_adapter(val):
+ status_int, content_type, body = val
+ response = Response(body)
+ response.content_type = content_type
+ response.status_int = status_int
+ return response
+
+ def string_response_adapter(body):
+ response = Response(body)
+ response.content_type = 'text/html'
+ response.status_int = 200
+ return response
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_response_adapter(string_response_adapter, str)
+ config.add_response_adapter(tuple_response_adapter, tuple)
+
+With this, both of these views will work as expected:
+
+.. code-block:: python
+ :linenos:
+
+ def aview(request):
+ return "Hello world!"
+
+ def anotherview(request):
+ return (403, 'text/plain', "Forbidden")
+
+.. seealso::
+
+ See also :ref:`using_iresponse`.
+
+Use Global Response Objects
+---------------------------
+
+Views have to return responses. But constructing them in view code is a chore. And perhaps registering a :term:`response adapter` as shown above is just too much work. :app:`Pyramid` provides a global response object as well. You can use it directly, if you prefer:
+
+.. code-block:: python
+ :linenos:
+
+ def aview(request):
+ response = request.response
+ response.body = 'Hello world!'
+ response.content_type = 'text/plain'
+ return response
+
+.. seealso::
+
+ See also :ref:`request_response_attr`.
+
+Extend Configuration
+--------------------
+
+Perhaps the :app:`Pyramid` configurator's syntax feels a bit verbose to you. Or possibly you would like to add a feature to configuration without asking the core developers to change :app:`Pyramid` itself?
+
+You can extend :app:`Pyramid`\ 's :term:`configurator` with your own directives. For example, let's say you find yourself calling :meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can get rid of the boring with existing shortcuts, but let's say that this is a case where there is no such shortcut:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+
+ config = Configurator()
+ config.add_route('xhr_route', '/xhr/{id}')
+ config.add_view('my.package.GET_view', route_name='xhr_route',
+ xhr=True, permission='view', request_method='GET')
+ config.add_view('my.package.POST_view', route_name='xhr_route',
+ xhr=True, permission='view', request_method='POST')
+ config.add_view('my.package.HEAD_view', route_name='xhr_route',
+ xhr=True, permission='view', request_method='HEAD')
+
+Pretty tedious right? You can add a directive to the :app:`Pyramid` :term:`configurator` to automate some of the tedium away:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import Configurator
+
+ def add_protected_xhr_views(config, module):
+ module = config.maybe_dotted(module)
+ for method in ('GET', 'POST', 'HEAD'):
+ view = getattr(module, 'xhr_%s_view' % method, None)
+ if view is not None:
+ config.add_view(view, route_name='xhr_route', xhr=True,
+ permission='view', request_method=method)
+
+ config = Configurator()
+ config.add_directive('add_protected_xhr_views', add_protected_xhr_views)
+
+Once that's done, you can call the directive you've just added as a method of the :term:`configurator` object:
+
+.. code-block:: python
+ :linenos:
+
+ config.add_route('xhr_route', '/xhr/{id}')
+ config.add_protected_xhr_views('my.package')
+
+Much better!
+
+You can share your configuration code with others, too. Add your code to a Python package. Put the call to :meth:`~pyramid.config.Configurator.add_directive` in a function. When other programmers install your package, they'll be able to use your configuration by passing your function to a call to :meth:`~pyramid.config.Configurator.include`.
+
+.. seealso::
+
+ See also :ref:`add_directive`.
+
+Introspect Your Application
+---------------------------
+
+If you're building a large, pluggable system, it's useful to be able to get a list of what has been plugged in *at application runtime*. For example, you might want to show users a set of tabs at the top of the screen based on a list of the views they registered.
+
+:app:`Pyramid` provides an :term:`introspector` for just this purpose.
+
+Here's an example of using :app:`Pyramid`\ 's :term:`introspector` from within a view:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.view import view_config
+ from pyramid.response import Response
+
+ @view_config(route_name='bar')
+ def show_current_route_pattern(request):
+ introspector = request.registry.introspector
+ route_name = request.matched_route.name
+ route_intr = introspector.get('routes', route_name)
+ return Response(str(route_intr['pattern']))
+
+.. seealso::
+
+ See also :ref:`using_introspection`. \ No newline at end of file
diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst
index ee54e3acd..bbf01240e 100644
--- a/docs/narr/configuration.rst
+++ b/docs/narr/configuration.rst
@@ -47,9 +47,9 @@ configured imperatively:
return Response('Hello world!')
if __name__ == '__main__':
- config = Configurator()
- config.add_view(hello_world)
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_view(hello_world)
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()
@@ -116,9 +116,9 @@ and its subpackages. For example:
return Response('Hello')
if __name__ == '__main__':
- config = Configurator()
- config.scan()
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.scan()
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()
diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst
index ad05976c0..db55f2f9c 100644
--- a/docs/narr/firstapp.rst
+++ b/docs/narr/firstapp.rst
@@ -60,7 +60,7 @@ Imports
The above ``helloworld.py`` script uses the following set of import statements:
.. literalinclude:: helloworld.py
- :linenos:
+ :lineno-match:
:lines: 1-3
The script imports the :class:`~pyramid.config.Configurator` class from the
@@ -83,7 +83,7 @@ The above script, beneath its set of imports, defines a function named
``hello_world``.
.. literalinclude:: helloworld.py
- :linenos:
+ :lineno-match:
:pyobject: hello_world
The function accepts a single argument (``request``) and it returns an instance
@@ -125,7 +125,7 @@ imports and function definitions, placed within the confines of an ``if``
statement:
.. literalinclude:: helloworld.py
- :linenos:
+ :lineno-match:
:lines: 9-15
Let's break this down piece by piece.
@@ -134,7 +134,7 @@ Configurator Construction
~~~~~~~~~~~~~~~~~~~~~~~~~
.. literalinclude:: helloworld.py
- :linenos:
+ :lineno-match:
:lines: 9-10
The ``if __name__ == '__main__':`` line in the code sample above represents a
@@ -153,8 +153,8 @@ code within the ``if`` statement to execute if this module is imported from
another; the code within the ``if`` block should only be run during a direct
script execution.
-The ``config = Configurator()`` line above creates an instance of the
-:class:`~pyramid.config.Configurator` class. The resulting ``config`` object
+The ``with Configurator() as config:`` line above creates an instance of the
+:class:`~pyramid.config.Configurator` class using a :term:`context manager`. The resulting ``config`` object
represents an API which the script uses to configure this particular
:app:`Pyramid` application. Methods called on the Configurator will cause
registrations to be made in an :term:`application registry` associated with the
@@ -166,7 +166,7 @@ Adding Configuration
~~~~~~~~~~~~~~~~~~~~
.. literalinclude:: helloworld.py
- :linenos:
+ :lineno-match:
:lines: 11-12
The first line above calls the :meth:`pyramid.config.Configurator.add_route`
@@ -185,7 +185,7 @@ WSGI Application Creation
~~~~~~~~~~~~~~~~~~~~~~~~~
.. literalinclude:: helloworld.py
- :linenos:
+ :lineno-match:
:lines: 13
After configuring views and ending configuration, the script creates a WSGI
@@ -197,7 +197,7 @@ method returns a :term:`WSGI` application object that can be used by any WSGI
server to present an application to a requestor. :term:`WSGI` is a protocol
that allows servers to talk to Python applications. We don't discuss
:term:`WSGI` in any depth within this book, but you can learn more about it by
-reading its `documentation <http://wsgi.readthedocs.org/en/latest/>`_.
+reading its `documentation <https://wsgi.readthedocs.io/en/latest/>`_.
The :app:`Pyramid` application object, in particular, is an instance of a class
representing a :app:`Pyramid` :term:`router`. It has a reference to the
@@ -212,7 +212,7 @@ WSGI Application Serving
~~~~~~~~~~~~~~~~~~~~~~~~
.. literalinclude:: helloworld.py
- :linenos:
+ :lineno-match:
:lines: 14-15
Finally, we actually serve the application to requestors by starting up a WSGI
diff --git a/docs/narr/helloworld.py b/docs/narr/helloworld.py
index c01329af9..29ed8e6f2 100644
--- a/docs/narr/helloworld.py
+++ b/docs/narr/helloworld.py
@@ -7,10 +7,9 @@ def hello_world(request):
return Response('Hello %(name)s!' % request.matchdict)
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/hello/{name}')
- config.add_view(hello_world, route_name='hello')
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_route('hello', '/hello/{name}')
+ config.add_view(hello_world, route_name='hello')
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()
-
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 63279027a..f9bb72986 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -1059,8 +1059,8 @@ enabling you to set up the utility in advance:
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()
-For full details, please read the `Venusian documentation
-<http://docs.repoze.org/venusian>`_.
+For full details, please read the :ref:`Venusian documentation <venusian:venusian>`.
+
.. _registering_tweens:
@@ -1569,7 +1569,7 @@ event type.
def __call__(self, event):
return event.request.path.startswith(self.val)
-Once you've created a subscriber predicate, it may registered via
+Once you've created a subscriber predicate, it may be registered via
:meth:`pyramid.config.Configurator.add_subscriber_predicate`. For example:
.. code-block:: python
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
index 3549b53a5..e64584322 100644
--- a/docs/narr/i18n.rst
+++ b/docs/narr/i18n.rst
@@ -647,7 +647,7 @@ before being rendered:
The features represented by attributes of the ``i18n`` namespace of Chameleon
will also consult the :app:`Pyramid` translations. See
-http://chameleon.readthedocs.org/en/latest/reference.html#translation-i18n.
+https://chameleon.readthedocs.io/en/latest/reference.html#translation-i18n.
.. note::
@@ -681,9 +681,9 @@ Jinja2 Pyramid i18n Support
The add-on `pyramid_jinja2 <https://github.com/Pylons/pyramid_jinja2>`_
provides a scaffold with an example of how to use internationalization with
Jinja2 in Pyramid. See the documentation sections `Internalization (i18n)
-<http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/#internalization-i18n>`_
+<https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/#internalization-i18n>`_
and `Paster Template I18N
-<http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/#paster-template-i18n>`_.
+<https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/#paster-template-i18n>`_.
.. index::
diff --git a/docs/narr/install.rst b/docs/narr/install.rst
index 2a25ad84d..a9ec68d61 100644
--- a/docs/narr/install.rst
+++ b/docs/narr/install.rst
@@ -51,10 +51,10 @@ Python comes pre-installed on Mac OS X, but due to Apple's release cycle, it is
often out of date. Unless you have a need for a specific earlier version, it is
recommended to install the latest 3.x version of Python.
-You can install the latest verion of Python for Mac OS X from the binaries on
+You can install the latest version of Python for Mac OS X from the binaries on
`python.org <https://www.python.org/downloads/mac-osx/>`_.
-Alternatively, you can use the `homebrew <http://brew.sh/>`_ package manager.
+Alternatively, you can use the `homebrew <https://brew.sh/>`_ package manager.
.. code-block:: text
@@ -157,7 +157,7 @@ application, rather than being installed system wide.
.. seealso:: See the Python Packaging Authority's (PyPA) documention
`Requirements for Installing Packages
- <https://packaging.python.org/en/latest/installing/#requirements-for-installing-packages>`_
+ <https://packaging.python.org/tutorials/installing-packages/#requirements-for-installing-packages>`_
for full details.
@@ -206,9 +206,7 @@ After installing Python as described previously in :ref:`for-mac-os-x-users` or
``$VENV/bin/pip`` clearly specifies that ``pip`` is run from within the
virtual environment and not at the system level.
- ``activate`` drops turds into the user's shell environment, leaving them
- vulnerable to executing commands in the wrong context. ``deactivate`` might
- not correctly restore previous shell environment variables.
+ ``activate`` makes changes to the user's shell environment which can often be convenient. However, in the context of long-form documentation, environment configuration can easily be forgotten. By keeping each snippet explicit we can reduce copy / paste errors by users in which commands are executed against the wrong Python environment. Also, ``deactivate`` might not correctly restore previous shell environment variables. Avoiding ``activate`` keeps the environment more reproducible.
Although using ``source bin/activate``, then ``pip``, requires fewer key
strokes to issue commands once invoked, there are other things to consider.
diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst
index 3aa603bcf..df3567726 100644
--- a/docs/narr/introduction.rst
+++ b/docs/narr/introduction.rst
@@ -9,112 +9,87 @@
:app:`Pyramid` Introduction
===========================
-:app:`Pyramid` is a general, open source, Python web application development
-*framework*. Its primary goal is to make it easier for a Python developer to
-create web applications.
-
-.. sidebar:: Frameworks vs. Libraries
-
- A *framework* differs from a *library* in one very important way: library
- code is always *called* by code that you write, while a framework always
- *calls* code that you write. Using a set of libraries to create an
- application is usually easier than using a framework initially, because you
- can choose to cede control to library code you have not authored very
- selectively. But when you use a framework, you are required to cede a
- greater portion of control to code you have not authored: code that resides
- in the framework itself. You needn't use a framework at all to create a web
- application using Python. A rich set of libraries already exists for the
- platform. In practice, however, using a framework to create an application
- is often more practical than rolling your own via a set of libraries if the
- framework provides a set of facilities that fits your application
- requirements.
-
-Pyramid attempts to follow these design and engineering principles:
+:app:`Pyramid` is a Python web application *framework*. It is designed to make creating web applications easier. It is open source.
+
+.. sidebar:: What Is a Framework?
+
+ A *framework* provides capabilities that developers can enhance or extend. A web application framework provides many of the common needs of building web applications allowing developers to concentrate only on the parts that are specific to their application.
+
+ Every framework makes choices about how a particular problem should be solved. When developers choose to use a framework, they cede control over the portions of their application that are provided by the framework. It is possible to write a complete web application without any framework, by using Python libraries. In practice, however, it is often more practical to use a framework, so long as your chosen framework fits the requirements of your application.
+
+:app:`Pyramid` follows these design and engineering principles:
Simplicity
- :app:`Pyramid` takes a *"pay only for what you eat"* approach. You can get
- results even if you have only a partial understanding of :app:`Pyramid`. It
- doesn't force you to use any particular technology to produce an application,
- and we try to keep the core set of concepts that you need to understand to a
- minimum.
+ :app:`Pyramid` is designed to be easy to use. You can get started even if you don't understand it all. And when you're ready to do more, :app:`Pyramid` will be there for you.
Minimalism
- :app:`Pyramid` tries to solve only the fundamental problems of creating a web
- application: the mapping of URLs to code, templating, security, and serving
- static assets. We consider these to be the core activities that are common to
- nearly all web applications.
+ Out of the box, :app:`Pyramid` provides only the core tools needed for nearly all web applications: mapping URLs to code, security, and serving static assets (files like JavaScript and CSS). Additional tools provide templating, database integration and more. But with :app:`Pyramid` you can *"pay only for what you eat"*.
Documentation
- Pyramid's minimalism means that it is easier for us to maintain complete and
- up-to-date documentation. It is our goal that no aspect of Pyramid is
- undocumented.
+ :app:`Pyramid` is committed to comprehensive and up-to-date documentation.
Speed
- :app:`Pyramid` is designed to provide noticeably fast execution for common
- tasks such as templating and simple response generation.
+ :app:`Pyramid` is designed to be noticeably fast.
Reliability
- :app:`Pyramid` is developed conservatively and tested exhaustively. Where
- Pyramid source code is concerned, our motto is: "If it ain't tested, it's
- broke".
+ :app:`Pyramid` is developed conservatively and tested exhaustively. Our motto is: "If it ain't tested, it's broke".
Openness
- As with Python, the Pyramid software is distributed under a `permissive open
- source license <http://repoze.org/license.html>`_.
+ As with Python, the :app:`Pyramid` software is distributed under a `permissive open source license <http://repoze.org/license.html>`_.
+
+.. _why_pyramid:
+
+Why Pyramid?
+------------
+
+In a world filled with web frameworks, why should you choose :app:`Pyramid`\ ?
+
+Modern
+~~~~~~
+
+:app:`Pyramid` is fully compatible with Python 3. If you develop a :app:`Pyramid` application today, you can rest assured that you'll be able to use the most modern features of your favorite language. And in the years to come, you'll continue to bed working on a framework that is up-to-date and forward-looking.
+
+Tested
+~~~~~~
+
+Untested code is broken by design. The :app:`Pyramid` community has a strong testing culture and our framework reflects that. Every release of :app:`Pyramid` has 100% statement coverage (as measured by `coverage <http://coverage.readthedocs.io/en/latest/>`_) and 95% decision/condition coverage. (as measured by `instrumental <http://instrumental.readthedocs.io/en/latest/intro.html>`_) It is automatically tested using `Travis <https://travis-ci.org/Pylons/pyramid>`_ and `Jenkins <http://jenkins.pylonsproject.org/job/pyramid/>`_ on supported versions of Python after each commit to its GitHub repository. `Official Pyramid add-ons <https://trypyramid.com/resources-extending-pyramid.html>`_ are held to a similar testing standard.
+
+We still find bugs in :app:`Pyramid`, but we've noticed we find a lot fewer of them while working on projects with a solid testing regime.
+
+Documented
+~~~~~~~~~~
+
+The :app:`Pyramid` documentation is comprehensive. We strive to keep our narrative documentation both complete and friendly to newcomers. We also maintain the :ref:`Pyramid Community Cookbook <cookbook:pyramid-cookbook>` of recipes demonstrating common scenarios you might face. Contributions in the form of improvements to our documentation are always appreciated. And we always welcome improvements to our `official tutorials <html_tutorials>`_ as well as new contributions to our `community maintained tutorials <tutorials:pyramid-tutorials>`_.
+
+Supported
+~~~~~~~~~
+
+You can get help quickly with :app:`Pyramid`. It's our goal that no :app:`Pyramid` question go unanswered. Whether you ask a question on IRC, on the Pylons-discuss mailing list, or on StackOverflow, you're likely to get a reasonably prompt response.
+
+:app:`Pyramid` is also a welcoming, friendly space for newcomers. We don't tolerate "support trolls" or those who enjoy berating fellow users in our support channels. We try to keep it well-lit and new-user-friendly.
+
+.. seealso::
+
+ See also our `#pyramid IRC channel <https://webchat.freenode.net/?channels=pyramid>`_, our `pylons-discuss mailing list <https://groups.google.com/forum/#!forum/pylons-discuss>`_, and :ref:`support-and-development`.
.. _what_makes_pyramid_unique:
What makes Pyramid unique
-------------------------
-Understandably, people don't usually want to hear about squishy engineering
-principles; they want to hear about concrete stuff that solves their problems.
-With that in mind, what would make someone want to use Pyramid instead of one
-of the many other web frameworks available today? What makes Pyramid unique?
-
-This is a hard question to answer because there are lots of excellent choices,
-and it's actually quite hard to make a wrong choice, particularly in the Python
-web framework market. But one reasonable answer is this: you can write very
-small applications in Pyramid without needing to know a lot. "What?" you say.
-"That can't possibly be a unique feature. Lots of other web frameworks let you
-do that!" Well, you're right. But unlike many other systems, you can also
-write very large applications in Pyramid if you learn a little more about it.
-Pyramid will allow you to become productive quickly, and will grow with you. It
-won't hold you back when your application is small, and it won't get in your
-way when your application becomes large. "Well that's fine," you say. "Lots of
-other frameworks let me write large apps, too." Absolutely. But other Python
-web frameworks don't seamlessly let you do both. They seem to fall into two
-non-overlapping categories: frameworks for "small apps" and frameworks for "big
-apps". The "small app" frameworks typically sacrifice "big app" features, and
-vice versa.
-
-We don't think it's a universally reasonable suggestion to write "small apps"
-in a "small framework" and "big apps" in a "big framework". You can't really
-know to what size every application will eventually grow. We don't really want
-to have to rewrite a previously small application in another framework when it
-gets "too big". We believe the current binary distinction between frameworks
-for small and large applications is just false. A well-designed framework
-should be able to be good at both. Pyramid strives to be that kind of
-framework.
-
-To this end, Pyramid provides a set of features that combined are unique
-amongst Python web frameworks. Lots of other frameworks contain some
-combination of these features. Pyramid of course actually stole many of them
-from those other frameworks. But Pyramid is the only one that has all of them
-in one place, documented appropriately, and useful *à la carte* without
-necessarily paying for the entire banquet. These are detailed below.
-
-Single-file applications
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can write a Pyramid application that lives entirely in one Python file, not
-unlike existing Python microframeworks. This is beneficial for one-off
-prototyping, bug reproduction, and very small applications. These applications
-are easy to understand because all the information about the application lives
-in a single place, and you can deploy them without needing to understand much
-about Python distributions and packaging. Pyramid isn't really marketed as a
-microframework, but it allows you to do almost everything that frameworks that
-are marketed as "micro" offer in very similar ways.
+There are many tools available for web development. What would make someone want to use :app:`Pyramid` instead? What makes :app:`Pyramid` unique?
+
+With :app:`Pyramid` you can write very small applications without needing to know a lot. And by learning a bit more, you can write very large applications too. :app:`Pyramid` will allow you to become productive quickly, and will grow with you. It won't hold you back when your application is small, and it won't get in your way when your application becomes large. Other application frameworks seem to fall into two non-overlapping categories: those that support "small apps" and those designed for "big apps".
+
+We don't believe you should have to make this choice. You can't really know how large your application will become. You certainly shouldn't have to rewrite a small application in another framework when it gets "too big". A well-designed framework should be able to be good at both. :app:`Pyramid` is that kind of framework.
+
+:app:`Pyramid` provides a set of features that are unique among Python web frameworks. Others may provide some, but only :app:`Pyramid` provides them all, in one place, fully documented, and *à la carte* without needing to pay for the whole banquet.
+
+
+Build single-file applications
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can write a :app:`Pyramid` application that lives entirely in one Python file. Such an application is easy to understand since everything is in one place. It is easy to deploy because you don't need to know much about Python packaging. :app:`Pyramid` allows you to do almost everything that so-called *microframeworks* can in very similar ways.
.. literalinclude:: helloworld.py
@@ -122,13 +97,10 @@ are marketed as "micro" offer in very similar ways.
See also :ref:`firstapp_chapter`.
-Decorator-based configuration
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Configure applications with decorators
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If you like the idea of framework configuration statements living next to the
-code it configures, so you don't have to constantly switch between files to
-refer to framework configuration when adding new code, you can use Pyramid
-decorators to localize the configuration. For example:
+:app:`Pyramid` allows you to keep your configuration right next to your code. That way you don't have to switch files to see your configuration. For example:
.. code-block:: python
@@ -139,103 +111,74 @@ decorators to localize the configuration. For example:
def fred_view(request):
return Response('fred')
-However, unlike some other systems, using decorators for Pyramid configuration
-does not make your application difficult to extend, test, or reuse. The
-:class:`~pyramid.view.view_config` decorator, for example, does not actually
-*change* the input or output of the function it decorates, so testing it is a
-"WYSIWYG" operation. You don't need to understand the framework to test your
-own code. You just behave as if the decorator is not there. You can also
-instruct Pyramid to ignore some decorators, or use completely imperative
-configuration instead of decorators to add views. Pyramid decorators are inert
-instead of eager. You detect and activate them with a :term:`scan`.
+However, using :app:`Pyramid` configuration decorators does not change your code. It remains easy to extend, test, or reuse. You can test your code as if the decorators were not there. You can instruct the framework to ignore some decorators. You can even use an imperative style to write your configuration, skipping decorators entirely.
-Example: :ref:`mapping_views_using_a_decorator_section`.
+.. seealso::
+
+ See also :ref:`mapping_views_using_a_decorator_section`.
+
+Generate application URLs
+~~~~~~~~~~~~~~~~~~~~~~~~~
-URL generation
-~~~~~~~~~~~~~~
+Dynamic web applications produce URLs that can change depending on what you are viewing. :app:`Pyramid` provides flexible, consistent, easy to use tools for generating URLs. When you use these tools to write your application, you can change your configuration without fear of breaking links in your web pages.
-Pyramid is capable of generating URLs for resources, routes, and static assets.
-Its URL generation APIs are easy to use and flexible. If you use Pyramid's
-various APIs for generating URLs, you can change your configuration around
-arbitrarily without fear of breaking a link on one of your web pages.
+.. seealso::
-Example: :ref:`generating_route_urls`.
+ See also :ref:`generating_route_urls`.
-Static file serving
+Serve static assets
~~~~~~~~~~~~~~~~~~~
-Pyramid is perfectly willing to serve static files itself. It won't make you
-use some external web server to do that. You can even serve more than one set
-of static files in a single Pyramid web application (e.g., ``/static`` and
-``/static2``). You can optionally place your files on an external web server
-and ask Pyramid to help you generate URLs to those files. This let's you use
-Pyramid's internal file serving while doing development, and a faster static
-file server in production, without changing any code.
+Web applications often require JavaScript, CSS, images and other so-called *static assets*. :app:`Pyramid` provides flexible tools for serving these kinds of files. You can serve them directly from :app:`Pyramid`, or host them on an external server or CDN (content delivery network). Either way, :app:`Pyramid` can help you to generate URLs so you can change where your files come from without changing any code.
-Example: :ref:`static_assets_section`.
+.. seealso::
-Fully interactive development
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ See also :ref:`static_assets_section`.
-When developing a Pyramid application, several interactive features are
-available. Pyramid can automatically utilize changed templates when rendering
-pages and automatically restart the application to incorporate changed Python
-code. Plain old ``print()`` calls used for debugging can display to a console.
+Develop interactively
+~~~~~~~~~~~~~~~~~~~~~
-Pyramid's debug toolbar comes activated when you use a Pyramid :term:`cookiecutter` to
-render a project. This toolbar overlays your application in the browser, and
-allows you access to framework data, such as the routes configured, the last
-renderings performed, the current set of packages installed, SQLAlchemy queries
-run, logging data, and various other facts. When an exception occurs, you can
-use its interactive debugger to poke around right in your browser to try to
-determine the cause of the exception. It's handy.
+:app:`Pyramid` can automatically detect changes you make to template files and code, so your changes are immediately available in your browser. You can debug using plain old ``print()`` calls, which will display to your console.
-Example: :ref:`debug_toolbar`.
+:app:`Pyramid` has a debug toolbar that allows you to see information about how your application is working right in your browser. See configuration, installed packages, SQL queries, logging statements and more.
-Debugging settings
-~~~~~~~~~~~~~~~~~~
+When your application has an error, an interactive debugger allows you to poke around from your browser to find out what happened.
+
+To use the :app:`Pyramid` debug toolbar, build your project with a :app:`Pyramid` :term:`cookiecutter`.
+
+.. seealso::
+
+ See also :ref:`debug_toolbar`.
+
+Debug with power
+~~~~~~~~~~~~~~~~
+
+When things go wrong, :app:`Pyramid` gives you powerful ways to fix the problem.
+
+You can configure :app:`Pyramid` to print helpful information to the console. The ``debug_notfound`` setting shows information about URLs that aren't matched. The ``debug_authorization`` setting provides helpful messages about why you aren't allowed to do what you just tried.
+
+:app:`Pyramid` also has command line tools to help you verify your configuration. You can use ``proutes`` and ``pviews`` to inspect how URLs are connected to your application code.
+
+.. seealso::
-Pyramid has debugging settings that allow you to print Pyramid runtime
-information to the console when things aren't behaving as you're expecting. For
-example, you can turn on ``debug_notfound``, which prints an informative
-message to the console every time a URL does not match any view. You can turn
-on ``debug_authorization``, which lets you know why a view execution was
-allowed or denied by printing a message to the console. These features are
-useful for those WTF moments.
-
-There are also a number of commands that you can invoke within a Pyramid
-environment that allow you to introspect the configuration of your system.
-``proutes`` shows all configured routes for an application in the order they'll
-be evaluated for matching. ``pviews`` shows all configured views for any given
-URL. These are also WTF-crushers in some circumstances.
-
-Examples: :ref:`debug_authorization_section` and :ref:`command_line_chapter`.
-
-Add-ons
-~~~~~~~
-
-Pyramid has an extensive set of add-ons held to the same quality standards as
-the Pyramid core itself. Add-ons are packages which provide functionality that
-the Pyramid core doesn't. Add-on packages already exist which let you easily
-send email, let you use the Jinja2 templating system, let you use XML-RPC or
-JSON-RPC, let you integrate with jQuery Mobile, etc.
-
-Examples:
-https://trypyramid.com/resources-extending-pyramid.html
-
-Class-based and function-based views
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Pyramid has a structured, unified concept of a :term:`view callable`. View
-callables can be functions, methods of classes, or even instances. When you
-add a new view callable, you can choose to make it a function or a method of a
-class. In either case Pyramid treats it largely the same way. You can change
-your mind later and move code between methods of classes and functions. A
-collection of similar view callables can be attached to a single class as
-methods, if that floats your boat, and they can share initialization code as
-necessary. All kinds of views are easy to understand and use, and operate
-similarly. There is no phony distinction between them. They can be used for
-the same purposes.
+ See also :ref:`debug_authorization_section`, :ref:`command_line_chapter`,
+ and :doc:`../pscripts/index`
+
+Extend your application
+~~~~~~~~~~~~~~~~~~~~~~~
+
+:app:`Pyramid` add-ons extend the core of the framework with useful abilities. There are add-ons available for your favorite template language, SQL and NoSQL databases, authentication services and more.
+
+Supported :app:`Pyramid` add-ons are held to the same demanding standards as the framework itself. You will find them to be fully tested and well documented.
+
+.. seealso::
+
+ See also https://trypyramid.com/resources-extending-pyramid.html
+
+Write your views, *your* way
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A fundamental task for any framework is to map URLs to code. In :app:`Pyramid`, that code is called a :term:`view callable`. View callables can be functions, class methods or even callable class instances. You are free to choose the approach that best fits your use case. Regardless of your choice, :app:`Pyramid` treats them the same. You can change your mind at any time without any penalty. There are no artificial distinctions between the various approaches.
Here's a view callable defined as a function:
@@ -275,54 +218,30 @@ Here's a few views defined as methods of a class instead:
.. _intro_asset_specs:
-Asset specifications
-~~~~~~~~~~~~~~~~~~~~
+Find *your* static assets
+~~~~~~~~~~~~~~~~~~~~~~~~~
-Asset specifications are strings that contain both a Python package name and a
-file or directory name, e.g., ``MyPackage:static/index.html``. Use of these
-specifications is omnipresent in Pyramid. An asset specification can refer to
-a template, a translation directory, or any other package-bound static
-resource. This makes a system built on Pyramid extensible because you don't
-have to rely on globals ("*the* static directory") or lookup schemes ("*the*
-ordered set of template directories") to address your files. You can move
-files around as necessary, and include other packages that may not share your
-system's templates or static files without encountering conflicts.
-
-Because asset specifications are used heavily in Pyramid, we've also provided a
-way to allow users to override assets. Say you love a system that someone else
-has created with Pyramid but you just need to change "that one template" to
-make it all better. No need to fork the application. Just override the asset
-specification for that template with your own inside a wrapper, and you're good
-to go.
+In many web frameworks, the static assets required by an application are kept in a globally shared location, "the *static* directory". Others use a lookup scheme, like an ordered set of template directories. Both of these approaches have problems when it comes to customization.
+
+:app:`Pyramid` takes a different approach. Static assets are located using *asset specifications*, strings that contain reference both to a Python package name and a file or directory name, e.g. ``MyPackage:static/index.html``. These specifications are used for templates, JavaScript and CSS, translation files, and any other package-bound static resource. By using asset specifications, :app:`Pyramid` makes it easy to extend your application with other packages without worrying about conflicts.
+
+What happens if another :app:`Pyramid` package you are using provides an asset you need to customize? Maybe that page template needs better HTML, or you want to update some CSS. With asset specifications you can override the assets from other packages using simple wrappers.
Examples: :ref:`asset_specifications` and :ref:`overriding_assets_section`.
-Extensible templating
-~~~~~~~~~~~~~~~~~~~~~
+Use *your* templates
+~~~~~~~~~~~~~~~~~~~~
-Pyramid has a structured API that allows for pluggability of "renderers".
-Templating systems such as Mako, Genshi, Chameleon, and Jinja2 can be treated
-as renderers. Renderer bindings for all of these templating systems already
-exist for use in Pyramid. But if you'd rather use another, it's not a big
-deal. Just copy the code from an existing renderer package, and plug in your
-favorite templating system. You'll then be able to use that templating system
-from within Pyramid just as you'd use one of the "built-in" templating systems.
+In :app:`Pyramid`, the job of creating a ``Response`` belongs to a :term:`renderer`. Any templating system—Mako, Chameleon, Jinja2—can be a renderer. In fact, packages exist for all of these systems. But if you'd rather use another, a structured API exists allowing you to create a renderer using your favorite templating system. You can use the templating system *you* understand, not one required by the framework.
-Pyramid does not make you use a single templating system exclusively. You can
-use multiple templating systems, even in the same project.
+What's more, :app:`Pyramid` does not make you use a single templating system exclusively. You can use multiple templating systems, even in the same project.
Example: :ref:`templates_used_directly`.
-Rendered views can return dictionaries
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Write testable views
+~~~~~~~~~~~~~~~~~~~~
-If you use a :term:`renderer`, you don't have to return a special kind of
-"webby" ``Response`` object from a view. Instead you can return a dictionary,
-and Pyramid will take care of converting that dictionary to a Response using a
-template on your behalf. This makes the view easier to test, because you don't
-have to parse HTML in your tests. Instead just make an assertion that the view
-returns "the right stuff" in the dictionary. You can write "real" unit tests
-instead of functionally testing all of your views.
+When you use a :term:`renderer` with your view callable, you are freed from needing to return a "webby" ``Response`` object. Instead your views can return a simple Python dictionary. :app:`Pyramid` will take care of rendering the information in that dictionary to a ``Response`` on your behalf. As a result, your views are more easily tested, since you don't need to parse HTML to evaluate the results. :app:`Pyramid` makes it a snap to write unit tests for your views, instead of requiring you to use functional tests.
.. index::
pair: renderer; explicitly calling
@@ -330,8 +249,7 @@ instead of functionally testing all of your views.
.. _example_render_to_response_call:
-For example, instead of returning a ``Response`` object from a
-``render_to_response`` call:
+For example, a typical web framework might return a ``Response`` object from a ``render_to_response`` call:
.. code-block:: python
:linenos:
@@ -342,7 +260,7 @@ For example, instead of returning a ``Response`` object from a
return render_to_response('myapp:templates/mytemplate.pt', {'a':1},
request=request)
-You can return a Python dictionary:
+While you *can* do this in :app:`Pyramid`, you can also return a Python dictionary:
.. code-block:: python
:linenos:
@@ -353,547 +271,92 @@ You can return a Python dictionary:
def myview(request):
return {'a':1}
-When this view callable is called by Pyramid, the ``{'a':1}`` dictionary will
-be rendered to a response on your behalf. The string passed as ``renderer=``
-above is an :term:`asset specification`. It is in the form
-``packagename:directoryname/filename.ext``. In this case, it refers to the
-``mytemplate.pt`` file in the ``templates`` directory within the ``myapp``
-Python package. Asset specifications are omnipresent in Pyramid. See
-:ref:`intro_asset_specs` for more information.
-
-Example: :ref:`renderers_chapter`.
-
-Event system
-~~~~~~~~~~~~
-
-Pyramid emits *events* during its request processing lifecycle. You can
-subscribe any number of listeners to these events. For example, to be notified
-of a new request, you can subscribe to the ``NewRequest`` event. To be
-notified that a template is about to be rendered, you can subscribe to the
-``BeforeRender`` event, and so forth. Using an event publishing system as a
-framework notification feature instead of hardcoded hook points tends to make
-systems based on that framework less brittle.
-
-You can also use Pyramid's event system to send your *own* events. For
-example, if you'd like to create a system that is itself a framework, and may
-want to notify subscribers that a document has just been indexed, you can
-create your own event type (``DocumentIndexed`` perhaps) and send the event via
-Pyramid. Users of this framework can then subscribe to your event like they'd
-subscribe to the events that are normally sent by Pyramid itself.
-
-Example: :ref:`events_chapter` and :ref:`event_types`.
-
-Built-in internationalization
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+By configuring your view to use a renderer, you tell :app:`Pyramid` to use the ``{'a':1}`` dictionary and the specified template to render a response on your behalf.
-Pyramid ships with internationalization-related features in its core:
-localization, pluralization, and creating message catalogs from source files
-and templates. Pyramid allows for a plurality of message catalogs via the use
-of translation domains. You can create a system that has its own translations
-without conflict with other translations in other domains.
+The string passed as ``renderer=`` above is an :term:`asset specification`. Asset specifications are widely used in :app:`Pyramid`. They allow for more reliable customization. See :ref:`intro_asset_specs` for more information.
-Example: :ref:`i18n_chapter`.
-
-HTTP caching
-~~~~~~~~~~~~
-
-Pyramid provides an easy way to associate views with HTTP caching policies. You
-can just tell Pyramid to configure your view with an ``http_cache`` statement,
-and it will take care of the rest::
-
- @view_config(http_cache=3600) # 60 minutes
- def myview(request): ....
-
-Pyramid will add appropriate ``Cache-Control`` and ``Expires`` headers to
-responses generated when this view is invoked.
-
-See the :meth:`~pyramid.config.Configurator.add_view` method's ``http_cache``
-documentation for more information.
-
-Sessions
-~~~~~~~~
-
-Pyramid has built-in HTTP sessioning. This allows you to associate data with
-otherwise anonymous users between requests. Lots of systems do this. But
-Pyramid also allows you to plug in your own sessioning system by creating some
-code that adheres to a documented interface. Currently there is a binding
-package for the third-party Redis sessioning system that does exactly this. But
-if you have a specialized need (perhaps you want to store your session data in
-MongoDB), you can. You can even switch between implementations without
-changing your application code.
-
-Example: :ref:`sessions_chapter`.
-
-Speed
-~~~~~
-
-The Pyramid core is, as far as we can tell, at least marginally faster than any
-other existing Python web framework. It has been engineered from the ground up
-for speed. It only does as much work as absolutely necessary when you ask it
-to get a job done. Extraneous function calls and suboptimal algorithms in its
-core codepaths are avoided. It is feasible to get, for example, between 3500
-and 4000 requests per second from a simple Pyramid view on commodity dual-core
-laptop hardware and an appropriate WSGI server (:term:`mod_wsgi` or gunicorn). In any
-case, performance statistics are largely useless without requirements and
-goals, but if you need speed, Pyramid will almost certainly never be your
-application's bottleneck; at least no more than Python will be a bottleneck.
-
-Example: http://blog.curiasolutions.com/pages/the-great-web-framework-shootout.html
-
-Exception views
-~~~~~~~~~~~~~~~
-
-Exceptions happen. Rather than deal with exceptions that might present
-themselves to a user in production in an ad-hoc way, Pyramid allows you to
-register an :term:`exception view`. Exception views are like regular Pyramid
-views, but they're only invoked when an exception "bubbles up" to Pyramid
-itself. For example, you might register an exception view for the
-:exc:`Exception` exception, which will catch *all* exceptions, and present a
-pretty "well, this is embarrassing" page. Or you might choose to register an
-exception view for only specific kinds of application-specific exceptions, such
-as an exception that happens when a file is not found, or an exception that
-happens when an action cannot be performed because the user doesn't have
-permission to do something. In the former case, you can show a pretty "Not
-Found" page; in the latter case you might show a login form.
-
-Example: :ref:`exception_views`.
-
-No singletons
-~~~~~~~~~~~~~
-
-Pyramid is written in such a way that it requires your application to have
-exactly zero "singleton" data structures. Or put another way, Pyramid doesn't
-require you to construct any "mutable globals". Or put even another different
-way, an import of a Pyramid application needn't have any "import-time side
-effects". This is esoteric-sounding, but if you've ever tried to cope with
-parameterizing a Django ``settings.py`` file for multiple installations of the
-same application, or if you've ever needed to monkey-patch some framework
-fixture so that it behaves properly for your use case, or if you've ever wanted
-to deploy your system using an asynchronous server, you'll end up appreciating
-this feature. It just won't be a problem. You can even run multiple copies of
-a similar but not identically configured Pyramid application within the same
-Python process. This is good for shared hosting environments, where RAM is at
-a premium.
-
-View predicates and many views per route
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Unlike many other systems, Pyramid allows you to associate more than one view
-per route. For example, you can create a route with the pattern ``/items`` and
-when the route is matched, you can shuffle off the request to one view if the
-request method is GET, another view if the request method is POST, etc. A
-system known as "view predicates" allows for this. Request method matching is
-the most basic thing you can do with a view predicate. You can also associate
-views with other request parameters, such as the elements in the query string,
-the Accept header, whether the request is an XHR request or not, and lots of
-other things. This feature allows you to keep your individual views clean.
-They won't need much conditional logic, so they'll be easier to test.
-
-Example: :ref:`view_configuration_parameters`.
-
-Transaction management
-~~~~~~~~~~~~~~~~~~~~~~
+Example: :ref:`renderers_chapter`.
-A couple of Pyramid's :term:`cookiecutter`\ s include a *transaction
-management* system, stolen from Zope. When you use this transaction management
-system, you cease being responsible for committing your data anymore. Instead
-Pyramid takes care of committing: it commits at the end of a request or aborts
-if there's an exception. Why is that a good thing? Having a centralized place
-for transaction management is a great thing. If, instead of managing your
-transactions in a centralized place, you sprinkle ``session.commit`` calls in
-your application logic itself, you can wind up in a bad place. Wherever you
-manually commit data to your database, it's likely that some of your other code
-is going to run *after* your commit. If that code goes on to do other important
-things after that commit, and an error happens in the later code, you can
-easily wind up with inconsistent data if you're not extremely careful. Some
-data will have been written to the database that probably should not have.
-Having a centralized commit point saves you from needing to think about this;
-it's great for lazy people who also care about data integrity. Either the
-request completes successfully, and all changes are committed, or it does not,
-and all changes are aborted.
-
-Pyramid's transaction management system allows you to synchronize commits
-between multiple databases. It also allows you to do things like conditionally
-send email if a transaction commits, but otherwise keep quiet.
-
-Example: :ref:`bfg_sql_wiki_tutorial` (note the lack of commit statements
-anywhere in application code).
-
-Configuration conflict detection
+Use events to coordinate actions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-When a system is small, it's reasonably easy to keep it all in your head. But
-when systems grow large, you may have hundreds or thousands of configuration
-statements which add a view, add a route, and so forth.
-
-Pyramid's configuration system keeps track of your configuration statements. If
-you accidentally add two that are identical, or Pyramid can't make sense out of
-what it would mean to have both statements active at the same time, it will
-complain loudly at startup time. It's not dumb though. It will automatically
-resolve conflicting configuration statements on its own if you use the
-configuration :meth:`~pyramid.config.Configurator.include` system. "More local"
-statements are preferred over "less local" ones. This allows you to
-intelligently factor large systems into smaller ones.
-
-Example: :ref:`conflict_detection`.
-
-Configuration extensibility
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Unlike other systems, Pyramid provides a structured "include" mechanism (see
-:meth:`~pyramid.config.Configurator.include`) that allows you to combine
-applications from multiple Python packages. All the configuration statements
-that can be performed in your "main" Pyramid application can also be performed
-by included packages, including the addition of views, routes, subscribers, and
-even authentication and authorization policies. You can even extend or override
-an existing application by including another application's configuration in
-your own, overriding or adding new views and routes to it. This has the
-potential to allow you to create a big application out of many other smaller
-ones. For example, if you want to reuse an existing application that already
-has a bunch of routes, you can just use the ``include`` statement with a
-``route_prefix``. The new application will live within your application at an
-URL prefix. It's not a big deal, and requires little up-front engineering
-effort.
-
-For example:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.config import Configurator
-
- if __name__ == '__main__':
- config = Configurator()
- config.include('pyramid_jinja2')
- config.include('pyramid_exclog')
- config.include('some.other.guys.package', route_prefix='/someotherguy')
-
-.. seealso::
-
- See also :ref:`including_configuration` and
- :ref:`building_an_extensible_app`.
-
-Flexible authentication and authorization
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Pyramid includes a flexible, pluggable authentication and authorization system.
-No matter where your user data is stored, or what scheme you'd like to use to
-permit your users to access your data, you can use a predefined Pyramid
-plugpoint to plug in your custom authentication and authorization code. If you
-want to change these schemes later, you can just change it in one place rather
-than everywhere in your code. It also ships with prebuilt well-tested
-authentication and authorization schemes out of the box. But what if you don't
-want to use Pyramid's built-in system? You don't have to. You can just write
-your own bespoke security code as you would in any other system.
-
-Example: :ref:`enabling_authorization_policy`.
-
-Traversal
-~~~~~~~~~
-
-:term:`Traversal` is a concept stolen from :term:`Zope`. It allows you to
-create a tree of resources, each of which can be addressed by one or more URLs.
-Each of those resources can have one or more *views* associated with it. If
-your data isn't naturally treelike, or you're unwilling to create a treelike
-representation of your data, you aren't going to find traversal very useful.
-However, traversal is absolutely fantastic for sites that need to be
-arbitrarily extensible. It's a lot easier to add a node to a tree than it is to
-shoehorn a route into an ordered list of other routes, or to create another
-entire instance of an application to service a department and glue code to
-allow disparate apps to share data. It's a great fit for sites that naturally
-lend themselves to changing departmental hierarchies, such as content
-management systems and document management systems. Traversal also lends
-itself well to systems that require very granular security ("Bob can edit
-*this* document" as opposed to "Bob can edit documents").
-
-Examples: :ref:`hello_traversal_chapter` and
-:ref:`much_ado_about_traversal_chapter`.
-
-Tweens
-~~~~~~
-
-Pyramid has a sort of internal WSGI-middleware-ish pipeline that can be hooked
-by arbitrary add-ons named "tweens". The debug toolbar is a "tween", and the
-``pyramid_tm`` transaction manager is also. Tweens are more useful than WSGI
-:term:`middleware` in some circumstances because they run in the context of
-Pyramid itself, meaning you have access to templates and other renderers, a
-"real" request object, and other niceties.
-
-Example: :ref:`registering_tweens`.
-
-View response adapters
-~~~~~~~~~~~~~~~~~~~~~~
-
-A lot is made of the aesthetics of what *kinds* of objects you're allowed to
-return from view callables in various frameworks. In a previous section in
-this document, we showed you that, if you use a :term:`renderer`, you can
-usually return a dictionary from a view callable instead of a full-on
-:term:`Response` object. But some frameworks allow you to return strings or
-tuples from view callables. When frameworks allow for this, code looks
-slightly prettier, because fewer imports need to be done, and there is less
-code. For example, compare this:
-
-.. code-block:: python
- :linenos:
-
- def aview(request):
- return "Hello world!"
+When writing web applications, it is often important to have your code run at a specific point in the lifecycle of a request. In :app:`Pyramid`, you can accomplish this using *subscribers* and *events*.
-To this:
+For example, you might have a job that needs to be done each time your application handles a new request. :app:`Pyramid` emits a ``NewRequest`` event at this point in the request handling lifecycle. You can register your code as a subscriber to this event using a clear, declarative style:
.. code-block:: python
- :linenos:
-
- from pyramid.response import Response
- def aview(request):
- return Response("Hello world!")
+ from pyramid.events import NewRequest
+ from pyramid.events import subscriber
-The former is "prettier", right?
+ @subscriber(NewRequest)
+ def my_job(event):
+ do_something(event.request)
-Out of the box, if you define the former view callable (the one that simply
-returns a string) in Pyramid, when it is executed, Pyramid will raise an
-exception. This is because "explicit is better than implicit", in most cases,
-and by default Pyramid wants you to return a :term:`Response` object from a
-view callable. This is because there's usually a heck of a lot more to a
-response object than just its body. But if you're the kind of person who
-values such aesthetics, we have an easy way to allow for this sort of thing:
+:app:`Pyramid`\ 's event system can be extended as well. If you need, you can create events of your own and send them using :app:`Pyramid`\ 's event system. Then anyone working with your application can subscribe to your events and coordinate their code with yours.
-.. code-block:: python
- :linenos:
-
- from pyramid.config import Configurator
- from pyramid.response import Response
-
- def string_response_adapter(s):
- response = Response(s)
- response.content_type = 'text/html'
- return response
-
- if __name__ == '__main__':
- config = Configurator()
- config.add_response_adapter(string_response_adapter, basestring)
-
-Do that once in your Pyramid application at startup. Now you can return
-strings from any of your view callables, e.g.:
-
-.. code-block:: python
- :linenos:
-
- def helloview(request):
- return "Hello world!"
-
- def goodbyeview(request):
- return "Goodbye world!"
-
-Oh noes! What if you want to indicate a custom content type? And a custom
-status code? No fear:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.config import Configurator
-
- def tuple_response_adapter(val):
- status_int, content_type, body = val
- response = Response(body)
- response.content_type = content_type
- response.status_int = status_int
- return response
-
- def string_response_adapter(body):
- response = Response(body)
- response.content_type = 'text/html'
- response.status_int = 200
- return response
-
- if __name__ == '__main__':
- config = Configurator()
- config.add_response_adapter(string_response_adapter, basestring)
- config.add_response_adapter(tuple_response_adapter, tuple)
-
-Once this is done, both of these view callables will work:
-
-.. code-block:: python
- :linenos:
-
- def aview(request):
- return "Hello world!"
-
- def anotherview(request):
- return (403, 'text/plain', "Forbidden")
-
-Pyramid defaults to explicit behavior, because it's the most generally useful,
-but provides hooks that allow you to adapt the framework to localized aesthetic
-desires.
-
-.. seealso::
-
- See also :ref:`using_iresponse`.
-
-"Global" response object
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-"Constructing these response objects in my view callables is such a chore! And
-I'm way too lazy to register a response adapter, as per the prior section," you
-say. Fine. Be that way:
-
-.. code-block:: python
- :linenos:
-
- def aview(request):
- response = request.response
- response.body = 'Hello world!'
- response.content_type = 'text/plain'
- return response
-
-.. seealso::
-
- See also :ref:`request_response_attr`.
-
-Automating repetitive configuration
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Example: :ref:`events_chapter` and :ref:`event_types`.
-Does Pyramid's configurator allow you to do something, but you're a little
-adventurous and just want it a little less verbose? Or you'd like to offer up
-some handy configuration feature to other Pyramid users without requiring that
-we change Pyramid? You can extend Pyramid's :term:`Configurator` with your own
-directives. For example, let's say you find yourself calling
-:meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can
-take the boring away by using existing shortcuts, but let's say that this is a
-case where there is no such shortcut:
+Build international applications
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. code-block:: python
- :linenos:
+:app:`Pyramid` ships with internationalization-related features in its core: localization, pluralization, and creating message catalogs from source files and templates. :app:`Pyramid` allows for a plurality of message catalogs via the use of translation domains. You can create a system that has its own translations without conflict with other translations in other domains.
- from pyramid.config import Configurator
+Example: :ref:`i18n_chapter`.
- config = Configurator()
- config.add_route('xhr_route', '/xhr/{id}')
- config.add_view('my.package.GET_view', route_name='xhr_route',
- xhr=True, permission='view', request_method='GET')
- config.add_view('my.package.POST_view', route_name='xhr_route',
- xhr=True, permission='view', request_method='POST')
- config.add_view('my.package.HEAD_view', route_name='xhr_route',
- xhr=True, permission='view', request_method='HEAD')
+Build efficient applications
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Pretty tedious right? You can add a directive to the Pyramid configurator to
-automate some of the tedium away:
+:app:`Pyramid` provides an easy way to *cache* the results of slow or expensive views. You can indicate in view configuration that you want a view to be cached:
.. code-block:: python
- :linenos:
- from pyramid.config import Configurator
+ @view_config(http_cache=3600) # 60 minutes
+ def myview(request):
+ # ...
- def add_protected_xhr_views(config, module):
- module = config.maybe_dotted(module)
- for method in ('GET', 'POST', 'HEAD'):
- view = getattr(module, 'xhr_%s_view' % method, None)
- if view is not None:
- config.add_view(view, route_name='xhr_route', xhr=True,
- permission='view', request_method=method)
+:app:`Pyramid` will automatically add the appropriate ``Cache-Control`` and ``Expires`` headers to the response it creates.
- config = Configurator()
- config.add_directive('add_protected_xhr_views', add_protected_xhr_views)
+See the :meth:`~pyramid.config.Configurator.add_view` method's ``http_cache`` documentation for more information.
-Once that's done, you can call the directive you've just added as a method of
-the Configurator object:
+Build fast applications
+~~~~~~~~~~~~~~~~~~~~~~~
-.. code-block:: python
- :linenos:
+The :app:`Pyramid` core is fast. It has been engineered from the ground up for speed. It only does as much work as absolutely necessary when you ask it to get a job done. If you need speed from your application, :app:`Pyramid` is the right choice for you.
- config.add_route('xhr_route', '/xhr/{id}')
- config.add_protected_xhr_views('my.package')
+Example: https://blog.curiasolutions.com/pages/the-great-web-framework-shootout.html
-Your previously repetitive configuration lines have now morphed into one line.
+Store session data
+~~~~~~~~~~~~~~~~~~
-You can share your configuration code with others this way, too, by packaging
-it up and calling :meth:`~pyramid.config.Configurator.add_directive` from
-within a function called when another user uses the
-:meth:`~pyramid.config.Configurator.include` method against your code.
+:app:`Pyramid` has built-in support for HTTP sessions, so you can associate data with specific users between requests. Lots of other frameworks also support sessions. But :app:`Pyramid` allows you to plug in your own custom sessioning system. So long as your system conforms to a documented interface, you can drop it in in place of the provided system.
-.. seealso::
+Currently there is a binding package for the third-party Redis sessioning system that does exactly this. But if you have a specialized need (perhaps you want to store your session data in MongoDB), you can. You can even switch between implementations without changing your application code.
- See also :ref:`add_directive`.
+Example: :ref:`sessions_chapter`.
-Programmatic introspection
+Handle problems with grace
~~~~~~~~~~~~~~~~~~~~~~~~~~
-If you're building a large system that other users may plug code into, it's
-useful to be able to get an enumeration of what code they plugged in *at
-application runtime*. For example, you might want to show them a set of tabs
-at the top of the screen based on an enumeration of views they registered.
-
-This is possible using Pyramid's :term:`introspector`.
-
-Here's an example of using Pyramid's introspector from within a view callable:
+Mistakes happen. Problems crop up. No one writes bug-free code. :app:`Pyramid`provides a way to handle the exceptions your code encounters. An :term:`exception view` is a special kind of view which is automatically called when a particular exception type arises without being handled by your application.
-.. code-block:: python
- :linenos:
+For example, you might register an exception view for the :exc:`Exception` exception type, which will catch *all* exceptions, and present a pretty "well, this is embarrassing" page. Or you might choose to register an exception view for only certain application-specific exceptions. You can make one for when a file is not found, or when the user doesn't have permission to do something. In the former case, you can show a pretty "Not Found" page; in the latter case you might show a login form.
- from pyramid.view import view_config
- from pyramid.response import Response
+Example: :ref:`exception_views`.
- @view_config(route_name='bar')
- def show_current_route_pattern(request):
- introspector = request.registry.introspector
- route_name = request.matched_route.name
- route_intr = introspector.get('routes', route_name)
- return Response(str(route_intr['pattern']))
+And much, much more...
+~~~~~~~~~~~~~~~~~~~~~~
-.. seealso::
+:app:`Pyramid` has been built with a number of other sophisticated design features that make it adaptable. Read more about them below.
- See also :ref:`using_introspection`.
+.. toctree::
+ :maxdepth: 2
-Python 3 compatibility
-~~~~~~~~~~~~~~~~~~~~~~
+ advanced-features
-Pyramid and most of its add-ons are Python 3 compatible. If you develop a
-Pyramid application today, you won't need to worry that five years from now
-you'll be backwatered because there are language features you'd like to use but
-your framework doesn't support newer Python versions.
-
-Testing
-~~~~~~~
-
-Every release of Pyramid has 100% statement coverage via unit and integration
-tests, as measured by the ``coverage`` tool available on PyPI. It also has
-greater than 95% decision/condition coverage as measured by the
-``instrumental`` tool available on PyPI. It is automatically tested by Travis,
-and Jenkins on Python 2.7, Python 3.4, Python 3.5, Python 3.6, and PyPy
-after each commit to its GitHub repository. Official Pyramid add-ons are held
-to a similar testing standard. We still find bugs in Pyramid and its official
-add-ons, but we've noticed we find a lot more of them while working on other
-projects that don't have a good testing regime.
-
-Travis: https://travis-ci.org/Pylons/pyramid
-Jenkins: http://jenkins.pylonsproject.org/job/pyramid/
-
-Support
-~~~~~~~
-
-It's our goal that no Pyramid question go unanswered. Whether you ask a
-question on IRC, on the Pylons-discuss mailing list, or on StackOverflow,
-you're likely to get a reasonably prompt response. We don't tolerate "support
-trolls" or other people who seem to get their rocks off by berating fellow
-users in our various official support channels. We try to keep it well-lit and
-new-user-friendly.
-
-Example: Visit irc\://freenode.net#pyramid (the ``#pyramid`` channel on
-irc.freenode.net in an IRC client) or the pylons-discuss maillist at
-https://groups.google.com/forum/#!forum/pylons-discuss.
-Documentation
-~~~~~~~~~~~~~
-It's a constant struggle, but we try to maintain a balance between completeness
-and new-user-friendliness in the official narrative Pyramid documentation
-(concrete suggestions for improvement are always appreciated, by the way). We
-also maintain a "cookbook" of recipes, which are usually demonstrations of
-common integration scenarios too specific to add to the official narrative
-docs. In any case, the Pyramid documentation is comprehensive.
-Example: The :ref:`Pyramid Community Cookbook <cookbook:pyramid-cookbook>`.
.. index::
single: Pylons Project
@@ -901,10 +364,7 @@ Example: The :ref:`Pyramid Community Cookbook <cookbook:pyramid-cookbook>`.
What Is The Pylons Project?
---------------------------
-:app:`Pyramid` is a member of the collection of software published under the
-Pylons Project. Pylons software is written by a loose-knit community of
-contributors. The `Pylons Project website <http://www.pylonsproject.org>`_
-includes details about how :app:`Pyramid` relates to the Pylons Project.
+:app:`Pyramid` is a member of the collection of software published under the Pylons Project. Pylons software is written by a loose-knit community of contributors. The `Pylons Project website <https://pylonsproject.org>`_ includes details about how :app:`Pyramid` relates to the Pylons Project.
.. index::
single: pyramid and other frameworks
@@ -914,72 +374,16 @@ includes details about how :app:`Pyramid` relates to the Pylons Project.
single: MVC
:app:`Pyramid` and Other Web Frameworks
-------------------------------------------
-
-The first release of Pyramid's predecessor (named :mod:`repoze.bfg`) was made
-in July of 2008. At the end of 2010, we changed the name of :mod:`repoze.bfg`
-to :app:`Pyramid`. It was merged into the Pylons project as :app:`Pyramid` in
-November of that year.
-
-:app:`Pyramid` was inspired by :term:`Zope`, :term:`Pylons` (version 1.0), and
-:term:`Django`. As a result, :app:`Pyramid` borrows several concepts and
-features from each, combining them into a unique web framework.
-
-Many features of :app:`Pyramid` trace their origins back to :term:`Zope`. Like
-Zope applications, :app:`Pyramid` applications can be easily extended. If you
-obey certain constraints, the application you produce can be reused, modified,
-re-integrated, or extended by third-party developers without forking the
-original application. The concepts of :term:`traversal` and declarative
-security in :app:`Pyramid` were pioneered first in Zope.
-
-The :app:`Pyramid` concept of :term:`URL dispatch` is inspired by the
-:term:`Routes` system used by :term:`Pylons` version 1.0. Like Pylons version
-1.0, :app:`Pyramid` is mostly policy-free. It makes no assertions about which
-database you should use. Pyramid no longer has built-in templating facilities
-as of version 1.5a2, but instead officially supports bindings for templating
-languages, including Chameleon, Jinja2, and Mako. In essence, it only supplies
-a mechanism to map URLs to :term:`view` code, along with a set of conventions
-for calling those views. You are free to use third-party components that fit
-your needs in your applications.
-
-The concept of :term:`view` is used by :app:`Pyramid` mostly as it would be by
-Django. :app:`Pyramid` has a documentation culture more like Django's than
-like Zope's.
-
-Like :term:`Pylons` version 1.0, but unlike :term:`Zope`, a :app:`Pyramid`
-application developer may use completely imperative code to perform common
-framework configuration tasks such as adding a view or a route. In Zope,
-:term:`ZCML` is typically required for similar purposes. In :term:`Grok`, a
-Zope-based web framework, :term:`decorator` objects and class-level
-declarations are used for this purpose. Out of the box, Pyramid supports
-imperative and decorator-based configuration. :term:`ZCML` may be used via an
-add-on package named ``pyramid_zcml``.
-
-Also unlike :term:`Zope` and other "full-stack" frameworks such as
-:term:`Django`, :app:`Pyramid` makes no assumptions about which persistence
-mechanisms you should use to build an application. Zope applications are
-typically reliant on :term:`ZODB`. :app:`Pyramid` allows you to build
-:term:`ZODB` applications, but it has no reliance on the ZODB software.
-Likewise, :term:`Django` tends to assume that you want to store your
-application's data in a relational database. :app:`Pyramid` makes no such
-assumption, allowing you to use a relational database, and neither encouraging
-nor discouraging the decision.
-
-Other Python web frameworks advertise themselves as members of a class of web
-frameworks named `model-view-controller
-<https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller>`_
-frameworks. Insofar as this term has been claimed to represent a class of web
-frameworks, :app:`Pyramid` also generally fits into this class.
-
-.. sidebar:: You Say :app:`Pyramid` is MVC, but Where's the Controller?
-
- The :app:`Pyramid` authors believe that the MVC pattern just doesn't really
- fit the web very well. In a :app:`Pyramid` application, there is a resource
- tree which represents the site structure, and views which tend to present
- the data stored in the resource tree and a user-defined "domain model".
- However, no facility provided *by the framework* actually necessarily maps
- to the concept of a "controller" or "model". So if you had to give it some
- acronym, I guess you'd say :app:`Pyramid` is actually an "RV" framework
- rather than an "MVC" framework. "MVC", however, is close enough as a
- general classification moniker for purposes of comparison with other web
- frameworks.
+---------------------------------------
+
+The first release of :app:`Pyramid`\ 's predecessor (named :mod:`repoze.bfg`) was made in July of 2008. At the end of 2010, we changed the name of :mod:`repoze.bfg` to :app:`Pyramid`. It was merged into the Pylons project as :app:`Pyramid` in November of that year.
+
+:app:`Pyramid` was inspired by :term:`Zope`, :term:`Pylons` (version 1.0), and :term:`Django`. As a result, :app:`Pyramid` borrows several concepts and features from each, combining them into a unique web framework.
+
+Similar to :term:`Zope`, :app:`Pyramid` applications may easily be extended. If you work within the constraints of the framework, you can produce applications that can be reused, modified, or extended without needing to modify the original application code. :app:`Pyramid` also inherits the concepts of :term:`traversal` and declarative security from Zope.
+
+Similar to :term:`Pylons` version 1.0, :app:`Pyramid` is largely free of policy. It makes no assertions about which database or template system you should use. You are free to use whatever third-party components fit the needs of your specific application. :app:`Pyramid` also inherits its approach to :term:`URL dispatch` from Pylons.
+
+Similar to :term:`Django`, :app:`Pyramid` values extensive documentation. In addition, the concept of a :term:`view` is used by :app:`Pyramid` much as it would be by Django.
+
+Other Python web frameworks advertise themselves as members of a class of web frameworks named `model-view-controller <https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller>`_ frameworks. The authors of :app:`Pyramid` do not believe that the MVC pattern fits the web particularly well. However, if this abstraction works for you, :app:`Pyramid` also generally fits into this class.
diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst
index 87682158b..a7ee0f1f8 100644
--- a/docs/narr/logging.rst
+++ b/docs/narr/logging.rst
@@ -16,7 +16,7 @@ to send log messages to loggers that you've configured.
cookiecutter which does not create these files, the configuration information in
this chapter may not be applicable.
-.. index:
+.. index::
pair: settings; logging
pair: .ini; logging
pair: logging; configuration
@@ -67,12 +67,12 @@ In this logging configuration:
2007-08-17 15:04:08,704 INFO [packagename] Loading resource, id: 86
-- a logger named ``myapp`` is configured that logs messages sent at a level
+- a logger named ``myproject`` is configured that logs messages sent at a level
above or equal to ``DEBUG`` to stderr in the same format as the root logger.
The ``root`` logger will be used by all applications in the Pyramid process
that ask for a logger (via ``logging.getLogger``) that has a name which begins
-with anything except your project's package name (e.g., ``myapp``). The logger
+with anything except your project's package name (e.g., ``myproject``). The logger
with the same name as your package name is reserved for your own usage in your
:app:`Pyramid` application. Its existence means that you can log to a known
logging location from any :app:`Pyramid` application generated via a cookiecutter.
@@ -96,10 +96,10 @@ Sending Logging Messages
------------------------
Python's special ``__name__`` variable refers to the current module's fully
-qualified name. From any module in a package named ``myapp``, the ``__name__``
-builtin variable will always be something like ``myapp``, or
-``myapp.subpackage`` or ``myapp.package.subpackage`` if your project is named
-``myapp``. Sending a message to this logger will send it to the ``myapp``
+qualified name. From any module in a package named ``myproject``, the ``__name__``
+builtin variable will always be something like ``myproject``, or
+``myproject.subpackage`` or ``myproject.package.subpackage`` if your project is named
+``myproject``. Sending a message to this logger will send it to the ``myproject``
logger.
To log messages to the package-specific logger configured in your ``.ini``
@@ -123,7 +123,7 @@ This will result in the following printed to the console, on ``stderr``:
.. code-block:: text
- 16:20:20,440 DEBUG [myapp.views] Returning: Hello World!
+ 16:20:20,440 DEBUG [myproject.views] Returning: Hello World!
(content-type: text/plain)
Filtering log messages
@@ -150,7 +150,7 @@ then add it to the list of loggers:
.. code-block:: ini
[loggers]
- keys = root, myapp, sqlalchemy.pool
+ keys = root, myproject, sqlalchemy.pool
No handlers need to be configured for this logger as by default non-root
loggers will propagate their log records up to their parent logger's handlers.
@@ -165,16 +165,16 @@ level is set to ``INFO``, whereas the application's log level is set to
# Begin logging configuration
[loggers]
- keys = root, myapp
+ keys = root, myproject
- [logger_myapp]
+ [logger_myproject]
level = DEBUG
handlers =
- qualname = myapp
+ qualname = myproject
-All of the child loggers of the ``myapp`` logger will inherit the ``DEBUG``
-level unless they're explicitly set differently. Meaning the ``myapp.views``,
-``myapp.models``, and all your app's modules' loggers by default have an
+All of the child loggers of the ``myproject`` logger will inherit the ``DEBUG``
+level unless they're explicitly set differently. Meaning the ``myproject.views``,
+``myproject.models``, and all your app's modules' loggers by default have an
effective level of ``DEBUG`` too.
For more advanced filtering, the logging module provides a
@@ -191,7 +191,7 @@ To capture log output to a separate file, use :class:`logging.FileHandler` (or
[handler_filelog]
class = FileHandler
- args = ('%(here)s/myapp.log','a')
+ args = ('%(here)s/myproject.log','a')
level = INFO
formatter = generic
@@ -200,7 +200,7 @@ Before it's recognized, it needs to be added to the list of handlers:
.. code-block:: ini
[handlers]
- keys = console, myapp, filelog
+ keys = console, myproject, filelog
and finally utilized by a logger.
@@ -211,7 +211,7 @@ and finally utilized by a logger.
handlers = console, filelog
These final three lines of configuration direct all of the root logger's output
-to the ``myapp.log`` as well as the console.
+to the ``myproject.log`` as well as the console.
Logging Exceptions
------------------
@@ -219,7 +219,7 @@ Logging Exceptions
To log or email exceptions generated by your :app:`Pyramid` application, use
the :term:`pyramid_exclog` package. Details about its configuration are in its
`documentation
-<http://docs.pylonsproject.org/projects/pyramid_exclog/en/latest/>`_.
+<https://docs.pylonsproject.org/projects/pyramid_exclog/en/latest/>`_.
.. index::
single: TransLogger
@@ -294,7 +294,7 @@ output to the console when we request a page:
.. code-block:: text
- 00:50:53,694 INFO [myapp.views] Returning: Hello World!
+ 00:50:53,694 INFO [myproject.views] Returning: Hello World!
(content-type: text/plain)
00:50:53,695 INFO [wsgi] 192.168.1.111 - - [11/Aug/2011:20:09:33 -0700] "GET /hello
HTTP/1.1" 404 - "-"
@@ -310,7 +310,7 @@ that the ``wsgi`` logger is configured and uses this handler accordingly:
# Begin logging configuration
[loggers]
- keys = root, myapp, wsgi
+ keys = root, myproject, wsgi
[handlers]
keys = console, accesslog
diff --git a/docs/narr/myproject/README.txt b/docs/narr/myproject/README.txt
index 41ef0ff91..2ffc0acba 100644
--- a/docs/narr/myproject/README.txt
+++ b/docs/narr/myproject/README.txt
@@ -1,5 +1,5 @@
MyProject
-===============================
+=========
Getting Started
---------------
diff --git a/docs/narr/myproject/development.ini b/docs/narr/myproject/development.ini
index 5d110805a..7e5496881 100644
--- a/docs/narr/myproject/development.ini
+++ b/docs/narr/myproject/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -24,11 +24,11 @@ pyramid.includes =
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/narr/myproject/myproject/templates/layout.jinja2 b/docs/narr/myproject/myproject/templates/layout.jinja2
index bfac9e64e..1baca52bd 100644
--- a/docs/narr/myproject/myproject/templates/layout.jinja2
+++ b/docs/narr/myproject/myproject/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>Cookiecutter Starter project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('myproject:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -42,7 +42,7 @@
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -58,7 +58,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/narr/myproject/myproject/templates/mytemplate.jinja2 b/docs/narr/myproject/myproject/templates/mytemplate.jinja2
index ce042215d..f2e7283f8 100644
--- a/docs/narr/myproject/myproject/templates/mytemplate.jinja2
+++ b/docs/narr/myproject/myproject/templates/mytemplate.jinja2
@@ -3,6 +3,6 @@
{% block content %}
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">MyProject</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
{% endblock content %}
diff --git a/docs/narr/myproject/production.ini b/docs/narr/myproject/production.ini
index 13be488e7..7060ef854 100644
--- a/docs/narr/myproject/production.ini
+++ b/docs/narr/myproject/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -22,7 +22,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/narr/myproject/setup.py b/docs/narr/myproject/setup.py
index 00e377349..153a659ba 100644
--- a/docs/narr/myproject/setup.py
+++ b/docs/narr/myproject/setup.py
@@ -9,6 +9,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
+ 'plaster_pastedeploy',
'pyramid',
'pyramid_jinja2',
'pyramid_debugtoolbar',
diff --git a/docs/narr/paste.rst b/docs/narr/paste.rst
index 2d4e76e24..c02036f69 100644
--- a/docs/narr/paste.rst
+++ b/docs/narr/paste.rst
@@ -21,17 +21,12 @@ debugging an application.
This chapter is not a replacement for documentation about PasteDeploy; it only
contextualizes the use of PasteDeploy within Pyramid. For detailed
-documentation, see http://pythonpaste.org/deploy/.
+documentation, see https://pastedeploy.readthedocs.io/en/latest/.
PasteDeploy
-----------
-:term:`PasteDeploy` is the system that Pyramid uses to allow :term:`deployment
-settings` to be specified using an ``.ini`` configuration file format. It also
-allows the ``pserve`` command to work. Its configuration format provides a
-convenient place to define application :term:`deployment settings` and WSGI
-server settings, and its server runner allows you to stop and start a Pyramid
-application easily.
+:term:`plaster` is the system that Pyramid uses to load settings from configuration files. The most common format for these files is an ``.ini`` format structured in a way defined by :term:`PasteDeploy`. The format supports mechanisms to define WSGI app :term:`deployment settings`, WSGI server settings and logging. This allows the ``pserve`` command to work, allowing you to stop and start a Pyramid application easily.
.. _pastedeploy_entry_points:
@@ -96,3 +91,8 @@ applications, servers, and :term:`middleware` defined within the configuration
file. The values in a ``[DEFAULT]`` section will be passed to your
application's ``main`` function as ``global_config`` (see the reference to the
``main`` function in :ref:`init_py`).
+
+Alternative Configuration File Formats
+--------------------------------------
+
+It is possible to use different file formats with :app:`Pyramid` if you do not like :term:`PasteDeploy`. Under the hood all command-line scripts such as ``pserve`` and ``pshell`` pass the ``config_uri`` (e.g. ``development.ini`` or ``production.ini``) to the :term:`plaster` library which performs a lookup for an appropriate parser. For ``.ini`` files it uses PasteDeploy but you can register your own configuration formats that plaster will find instead.
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index f32fad370..c54e28137 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -43,7 +43,7 @@ Pyramid cookiecutters released under the Pylons Project differ from each other o
- the mechanism they use to map URLs to code (:term:`URL dispatch` or :term:`traversal`)
-- templating libraries (:term:`Jinja2` or :term:`Chameleon`)
+- templating libraries (:term:`Jinja2`, :term:`Chameleon`, or :term:`Mako`)
* `pyramid-cookiecutter-starter <https://github.com/Pylons/pyramid-cookiecutter-starter>`_
* `pyramid-cookiecutter-alchemy <https://github.com/Pylons/pyramid-cookiecutter-alchemy>`_
@@ -52,7 +52,7 @@ Pyramid cookiecutters released under the Pylons Project differ from each other o
These cookiecutters include:
``pyramid-cookiecutter-starter``
- :term:`URL dispatch` for routing and :term:`Jinja2` for templating
+ :term:`URL dispatch` for routing and either :term:`Jinja2`, :term:`Chameleon`, or :term:`Mako` for templating
``pyramid-cookiecutter-alchemy``
SQLite for persistent storage, :term:`SQLAlchemy` for an ORM, :term:`URL dispatch` for routing, and :term:`Jinja2` for templating.
@@ -85,14 +85,21 @@ On all platforms, generate a project using cookiecutter.
.. code-block:: bash
- $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter
+ $ cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master
If prompted for the first item, accept the default ``yes`` by hitting return.
-#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it
- okay to delete and re-clone it? [yes]:``
-#. ``project_name [Pyramid Scaffold]: myproject``
-#. ``repo_name [scaffold]: myproject``
+.. code-block:: text
+
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: myproject
+ repo_name [myproject]: myproject
+ Select template_language:
+ 1 - jinja2
+ 2 - chameleon
+ 3 - mako
+ Choose from 1, 2, 3 [1]: 1
We then run through the following commands.
@@ -325,7 +332,7 @@ Access is restricted such that only a browser running on the same machine as
Pyramid will be able to access your Pyramid application. However, if you want
to open access to other machines on the same network, then edit the
``development.ini`` file, and replace the ``listen`` value in the
-``[server:main]`` section, changing it from ``127.0.0.1:6543 [::1]:6543`` to ``*:6543``
+``[server:main]`` section, changing it from ``localhost:6543`` to ``*:6543``
(this is equivalent to ``0.0.0.0:6543 [::]:6543``). For example:
.. code-block:: ini
@@ -349,8 +356,8 @@ IPv6. ``[::]`` means the same as ``0.0.0.0`` but for IPv6 protocol.
You can change the port on which the server runs on by changing the same
portion of the ``development.ini`` file. For example, you can change the
-``listen = 127.0.0.1:6543 [::1]:6543`` line in the ``development.ini`` file's ``[server:main]``
-section to ``listen = 127:0.0.1:8080 [::1]:8080`` to run the server on port 8080 instead of port 6543.
+``listen = localhost:6543`` line in the ``development.ini`` file's ``[server:main]``
+section to ``listen = localhost:8080`` to run the server on port 8080 instead of port 6543.
You can shut down a server started this way by pressing ``Ctrl-C`` (or
``Ctrl-Break`` on Windows).
@@ -733,7 +740,7 @@ testing, as well as distributing your application.
``setup.py`` is the de facto standard which Python developers use to
distribute their reusable code. You can read more about ``setup.py`` files
and their usage in the `Python Packaging User Guide
- <https://packaging.python.org/en/latest/>`_ and `Setuptools documentation
+ <https://packaging.python.org/>`_ and `Setuptools documentation
<http://pythonhosted.org/setuptools/>`_.
Our generated ``setup.py`` looks like this:
@@ -1105,7 +1112,7 @@ Automatically Reloading Your Code
During development, it can be really useful to automatically have the
webserver restart when you make changes. ``pserve`` has a ``--reload`` switch
to enable this. It uses the
-`hupper <http://docs.pylonsproject.org/projects/hupper/en/latest/>`_ package
+`hupper <https://docs.pylonsproject.org/projects/hupper/en/latest/>`_ package
to enable this behavior. When your code crashes, ``hupper`` will wait for
another change or the ``SIGHUP`` signal before restarting again.
@@ -1134,7 +1141,7 @@ serve it many times. When you change it, you want ``pserve`` to restart:
[pserve]
watch_files =
- myapp/static/favicon.ico
+ myproject/static/favicon.ico
Paths may be absolute or relative to the configuration file. They may also
be an :term:`asset specification`. These paths are passed to ``hupper``, which
diff --git a/docs/narr/scaffolding.rst b/docs/narr/scaffolding.rst
index 27239d34e..82ae0f9ac 100644
--- a/docs/narr/scaffolding.rst
+++ b/docs/narr/scaffolding.rst
@@ -27,7 +27,7 @@ found by the ``pcreate`` command.
To create a scaffold template, create a Python :term:`distribution` to house
the scaffold which includes a ``setup.py`` that relies on the ``setuptools``
package. See `Packaging and Distributing Projects
-<https://packaging.python.org/en/latest/distributing/>`_ for more information
+<https://packaging.python.org/tutorials/distributing-packages/>`_ for more information
about how to do this. For example, we'll pretend the distribution you create
is named ``CoolExtension``, and it has a package directory within it named
``coolextension``.
diff --git a/docs/narr/security.rst b/docs/narr/security.rst
index 77e7fd707..0265152fa 100644
--- a/docs/narr/security.rst
+++ b/docs/narr/security.rst
@@ -146,7 +146,7 @@ For example, the following view declaration protects the view named
# config is an instance of pyramid.config.Configurator
config.add_view('mypackage.views.blog_entry_add_view',
- name='add_entry.html',
+ name='add_entry.html',
context='mypackage.resources.Blog',
permission='add')
@@ -725,7 +725,7 @@ object that implements the following interface:
""" Return ``True`` if any of the ``principals`` is allowed the
``permission`` in the current ``context``, else return ``False``
"""
-
+
def principals_allowed_by_permission(self, context, permission):
""" Return a set of principal identifiers allowed by the
``permission`` in ``context``. This behavior is optional; if you
@@ -765,3 +765,215 @@ which would allow the attacker to control the content of the payload. Re-using
a secret across two different subsystems might drop the security of signing to
zero. Keys should not be re-used across different contexts where an attacker
has the possibility of providing a chosen plaintext.
+
+.. index::
+ single: preventing cross-site request forgery attacks
+ single: cross-site request forgery attacks, prevention
+
+Preventing Cross-Site Request Forgery Attacks
+---------------------------------------------
+
+`Cross-site request forgery
+<https://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a
+phenomenon whereby a user who is logged in to your website might inadvertantly
+load a URL because it is linked from, or embedded in, an attacker's website.
+If the URL is one that may modify or delete data, the consequences can be dire.
+
+You can avoid most of these attacks by issuing a unique token to the browser
+and then requiring that it be present in all potentially unsafe requests.
+:app:`Pyramid` provides facilities to create and check CSRF tokens.
+
+By default :app:`Pyramid` comes with a session-based CSRF implementation
+:class:`pyramid.csrf.SessionCSRFStoragePolicy`. To use it, you must first enable
+a :term:`session factory` as described in
+:ref:`using_the_default_session_factory` or
+:ref:`using_alternate_session_factories`. Alternatively, you can use
+a cookie-based implementation :class:`pyramid.csrf.CookieCSRFStoragePolicy` which gives
+some additional flexibility as it does not require a session for each user.
+You can also define your own implementation of
+:class:`pyramid.interfaces.ICSRFStoragePolicy` and register it with the
+:meth:`pyramid.config.Configurator.set_csrf_storage_policy` directive.
+
+For example:
+
+.. code-block:: python
+
+ from pyramid.config import Configurator
+
+ config = Configurator()
+ config.set_csrf_storage_policy(MyCustomCSRFPolicy())
+
+.. index::
+ single: csrf.get_csrf_token
+
+Using the ``csrf.get_csrf_token`` Method
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To get the current CSRF token, use the
+:data:`pyramid.csrf.get_csrf_token` method.
+
+.. code-block:: python
+
+ from pyramid.csrf import get_csrf_token
+ token = get_csrf_token(request)
+
+The ``get_csrf_token()`` method accepts a single argument: the request. It
+returns a CSRF *token* string. If ``get_csrf_token()`` or ``new_csrf_token()``
+was invoked previously for this user, then the existing token will be returned.
+If no CSRF token previously existed for this user, then a new token will be set
+into the session and returned. The newly created token will be opaque and
+randomized.
+
+.. _get_csrf_token_in_templates:
+
+Using the ``get_csrf_token`` global in templates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Templates have a ``get_csrf_token()`` method inserted into their globals, which
+allows you to get the current token without modifying the view code. This
+method takes no arguments and returns a CSRF token string. You can use the
+returned token as the value of a hidden field in a form that posts to a method
+that requires elevated privileges, or supply it as a request header in AJAX
+requests.
+
+For example, include the CSRF token as a hidden field:
+
+.. code-block:: html
+
+ <form method="post" action="/myview">
+ <input type="hidden" name="csrf_token" value="${get_csrf_token()}">
+ <input type="submit" value="Delete Everything">
+ </form>
+
+Or include it as a header in a jQuery AJAX request:
+
+.. code-block:: javascript
+
+ var csrfToken = "${get_csrf_token()}";
+ $.ajax({
+ type: "POST",
+ url: "/myview",
+ headers: { 'X-CSRF-Token': csrfToken }
+ }).done(function() {
+ alert("Deleted");
+ });
+
+The handler for the URL that receives the request should then require that the
+correct CSRF token is supplied.
+
+.. index::
+ single: csrf.new_csrf_token
+
+Using the ``csrf.new_csrf_token`` Method
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To explicitly create a new CSRF token, use the ``csrf.new_csrf_token()``
+method. This differs only from ``csrf.get_csrf_token()`` inasmuch as it
+clears any existing CSRF token, creates a new CSRF token, sets the token into
+the user, and returns the token.
+
+.. code-block:: python
+
+ from pyramid.csrf import new_csrf_token
+ token = new_csrf_token(request)
+
+.. note::
+
+ It is not possible to force a new CSRF token from a template. If you
+ want to regenerate your CSRF token then do it in the view code and return
+ the new token as part of the context.
+
+Checking CSRF Tokens Manually
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In request handling code, you can check the presence and validity of a CSRF
+token with :func:`pyramid.csrf.check_csrf_token`. If the token is valid, it
+will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally,
+you can specify ``raises=False`` to have the check return ``False`` instead of
+raising an exception.
+
+By default, it checks for a POST parameter named ``csrf_token`` or a header
+named ``X-CSRF-Token``.
+
+.. code-block:: python
+
+ from pyramid.csrf import check_csrf_token
+
+ def myview(request):
+ # Require CSRF Token
+ check_csrf_token(request)
+
+ # ...
+
+.. _auto_csrf_checking:
+
+Checking CSRF Tokens Automatically
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 1.7
+
+:app:`Pyramid` supports automatically checking CSRF tokens on requests with an
+unsafe method as defined by RFC2616. Any other request may be checked manually.
+This feature can be turned on globally for an application using the
+:meth:`pyramid.config.Configurator.set_default_csrf_options` directive.
+For example:
+
+.. code-block:: python
+
+ from pyramid.config import Configurator
+
+ config = Configurator()
+ config.set_default_csrf_options(require_csrf=True)
+
+CSRF checking may be explicitly enabled or disabled on a per-view basis using
+the ``require_csrf`` view option. A value of ``True`` or ``False`` will
+override the default set by ``set_default_csrf_options``. For example:
+
+.. code-block:: python
+
+ @view_config(route_name='hello', require_csrf=False)
+ def myview(request):
+ # ...
+
+When CSRF checking is active, the token and header used to find the
+supplied CSRF token will be ``csrf_token`` and ``X-CSRF-Token``, respectively,
+unless otherwise overridden by ``set_default_csrf_options``. The token is
+checked against the value in ``request.POST`` which is the submitted form body.
+If this value is not present, then the header will be checked.
+
+In addition to token based CSRF checks, if the request is using HTTPS then the
+automatic CSRF checking will also check the referrer of the request to ensure
+that it matches one of the trusted origins. By default the only trusted origin
+is the current host, however additional origins may be configured by setting
+``pyramid.csrf_trusted_origins`` to a list of domain names (and ports if they
+are non-standard). If a host in the list of domains starts with a ``.`` then
+that will allow all subdomains as well as the domain without the ``.``.
+
+If CSRF checks fail then a :class:`pyramid.exceptions.BadCSRFToken` or
+:class:`pyramid.exceptions.BadCSRFOrigin` exception will be raised. This
+exception may be caught and handled by an :term:`exception view` but, by
+default, will result in a ``400 Bad Request`` response being sent to the
+client.
+
+Checking CSRF Tokens with a View Predicate
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. deprecated:: 1.7
+ Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead
+ to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised.
+
+A convenient way to require a valid CSRF token for a particular view is to
+include ``check_csrf=True`` as a view predicate. See
+:meth:`pyramid.config.Configurator.add_view`.
+
+.. code-block:: python
+
+ @view_config(request_method='POST', check_csrf=True, ...)
+ def myview(request):
+ ...
+
+.. note::
+ A mismatch of a CSRF token is treated like any other predicate miss, and the
+ predicate system, when it doesn't find a view, raises ``HTTPNotFound``
+ instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different
+ from calling :func:`pyramid.csrf.check_csrf_token`.
diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst
index 5b24201a9..b34d7a4f1 100644
--- a/docs/narr/sessions.rst
+++ b/docs/narr/sessions.rst
@@ -12,8 +12,7 @@ application.
This chapter describes how to configure sessions, what session implementations
:app:`Pyramid` provides out of the box, how to store and retrieve data from
-sessions, and two session-specific features: flash messages, and cross-site
-request forgery attack prevention.
+sessions, and a session-specific feature: flash messages.
.. index::
single: session factory (default)
@@ -175,10 +174,10 @@ pyramid_beaker_ Beaker_ Session factory for Pyramid
.. _PyNaCl: https://pynacl.readthedocs.io/en/latest/secret/
.. _pyramid_redis_sessions: https://pypi.python.org/pypi/pyramid_redis_sessions
-.. _Redis: http://redis.io/
+.. _Redis: https://redis.io/
.. _pyramid_beaker: https://pypi.python.org/pypi/pyramid_beaker
-.. _Beaker: http://beaker.readthedocs.org/en/latest/
+.. _Beaker: https://beaker.readthedocs.io/en/latest/
.. index::
single: session factory (custom)
@@ -316,183 +315,3 @@ flash storage.
['info message']
>>> request.session.peek_flash()
[]
-
-.. index::
- single: preventing cross-site request forgery attacks
- single: cross-site request forgery attacks, prevention
-
-Preventing Cross-Site Request Forgery Attacks
----------------------------------------------
-
-`Cross-site request forgery
-<https://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a
-phenomenon whereby a user who is logged in to your website might inadvertantly
-load a URL because it is linked from, or embedded in, an attacker's website.
-If the URL is one that may modify or delete data, the consequences can be dire.
-
-You can avoid most of these attacks by issuing a unique token to the browser
-and then requiring that it be present in all potentially unsafe requests.
-:app:`Pyramid` sessions provide facilities to create and check CSRF tokens.
-
-To use CSRF tokens, you must first enable a :term:`session factory` as
-described in :ref:`using_the_default_session_factory` or
-:ref:`using_alternate_session_factories`.
-
-.. index::
- single: session.get_csrf_token
-
-Using the ``session.get_csrf_token`` Method
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To get the current CSRF token from the session, use the
-``session.get_csrf_token()`` method.
-
-.. code-block:: python
-
- token = request.session.get_csrf_token()
-
-The ``session.get_csrf_token()`` method accepts no arguments. It returns a
-CSRF *token* string. If ``session.get_csrf_token()`` or
-``session.new_csrf_token()`` was invoked previously for this session, then the
-existing token will be returned. If no CSRF token previously existed for this
-session, then a new token will be set into the session and returned. The newly
-created token will be opaque and randomized.
-
-You can use the returned token as the value of a hidden field in a form that
-posts to a method that requires elevated privileges, or supply it as a request
-header in AJAX requests.
-
-For example, include the CSRF token as a hidden field:
-
-.. code-block:: html
-
- <form method="post" action="/myview">
- <input type="hidden" name="csrf_token" value="${request.session.get_csrf_token()}">
- <input type="submit" value="Delete Everything">
- </form>
-
-Or include it as a header in a jQuery AJAX request:
-
-.. code-block:: javascript
-
- var csrfToken = ${request.session.get_csrf_token()};
- $.ajax({
- type: "POST",
- url: "/myview",
- headers: { 'X-CSRF-Token': csrfToken }
- }).done(function() {
- alert("Deleted");
- });
-
-The handler for the URL that receives the request should then require that the
-correct CSRF token is supplied.
-
-.. index::
- single: session.new_csrf_token
-
-Using the ``session.new_csrf_token`` Method
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To explicitly create a new CSRF token, use the ``session.new_csrf_token()``
-method. This differs only from ``session.get_csrf_token()`` inasmuch as it
-clears any existing CSRF token, creates a new CSRF token, sets the token into
-the session, and returns the token.
-
-.. code-block:: python
-
- token = request.session.new_csrf_token()
-
-Checking CSRF Tokens Manually
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In request handling code, you can check the presence and validity of a CSRF
-token with :func:`pyramid.session.check_csrf_token`. If the token is valid, it
-will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally,
-you can specify ``raises=False`` to have the check return ``False`` instead of
-raising an exception.
-
-By default, it checks for a POST parameter named ``csrf_token`` or a header
-named ``X-CSRF-Token``.
-
-.. code-block:: python
-
- from pyramid.session import check_csrf_token
-
- def myview(request):
- # Require CSRF Token
- check_csrf_token(request)
-
- # ...
-
-.. _auto_csrf_checking:
-
-Checking CSRF Tokens Automatically
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 1.7
-
-:app:`Pyramid` supports automatically checking CSRF tokens on requests with an
-unsafe method as defined by RFC2616. Any other request may be checked manually.
-This feature can be turned on globally for an application using the
-:meth:`pyramid.config.Configurator.set_default_csrf_options` directive.
-For example:
-
-.. code-block:: python
-
- from pyramid.config import Configurator
-
- config = Configurator()
- config.set_default_csrf_options(require_csrf=True)
-
-CSRF checking may be explicitly enabled or disabled on a per-view basis using
-the ``require_csrf`` view option. A value of ``True`` or ``False`` will
-override the default set by ``set_default_csrf_options``. For example:
-
-.. code-block:: python
-
- @view_config(route_name='hello', require_csrf=False)
- def myview(request):
- # ...
-
-When CSRF checking is active, the token and header used to find the
-supplied CSRF token will be ``csrf_token`` and ``X-CSRF-Token``, respectively,
-unless otherwise overridden by ``set_default_csrf_options``. The token is
-checked against the value in ``request.POST`` which is the submitted form body.
-If this value is not present, then the header will be checked.
-
-In addition to token based CSRF checks, if the request is using HTTPS then the
-automatic CSRF checking will also check the referrer of the request to ensure
-that it matches one of the trusted origins. By default the only trusted origin
-is the current host, however additional origins may be configured by setting
-``pyramid.csrf_trusted_origins`` to a list of domain names (and ports if they
-are non standard). If a host in the list of domains starts with a ``.`` then
-that will allow all subdomains as well as the domain without the ``.``.
-
-If CSRF checks fail then a :class:`pyramid.exceptions.BadCSRFToken` or
-:class:`pyramid.exceptions.BadCSRFOrigin` exception will be raised. This
-exception may be caught and handled by an :term:`exception view` but, by
-default, will result in a ``400 Bad Request`` response being sent to the
-client.
-
-Checking CSRF Tokens with a View Predicate
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. deprecated:: 1.7
- Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead
- to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised.
-
-A convenient way to require a valid CSRF token for a particular view is to
-include ``check_csrf=True`` as a view predicate. See
-:meth:`pyramid.config.Configurator.add_view`.
-
-.. code-block:: python
-
- @view_config(request_method='POST', check_csrf=True, ...)
- def myview(request):
- ...
-
-.. note::
- A mismatch of a CSRF token is treated like any other predicate miss, and the
- predicate system, when it doesn't find a view, raises ``HTTPNotFound``
- instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different
- from calling :func:`pyramid.session.check_csrf_token`.
diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst
index cf4612602..5e7c7c871 100644
--- a/docs/narr/startup.rst
+++ b/docs/narr/startup.rst
@@ -10,12 +10,12 @@ you'll see something much like this show up on the console:
$ $VENV/bin/pserve development.ini
Starting server in PID 16305.
- Serving on http://127.0.0.1:6543
- Serving on http://[::1]:6543
+ Serving on http://localhost:6543
+ Serving on http://localhost:6543
This chapter explains what happens between the time you press the "Return" key
-on your keyboard after typing ``pserve development.ini`` and the time the line
-``serving on http://127.0.0.1:6543`` is output to your console.
+on your keyboard after typing ``pserve development.ini`` and the time the lines
+``Serving on http://localhost:6543`` are output to your console.
.. index::
single: startup process
@@ -38,7 +38,14 @@ Here's a high-level time-ordered overview of what happens when you press
begin to run and serve an application using the information contained
within the ``development.ini`` file.
-#. The framework finds a section named either ``[app:main]``,
+#. ``pserve`` passes the ``development.ini`` path to :term:`plaster` which
+ finds an available configuration loader that recognizes the ``ini`` format.
+
+#. :term:`plaster` finds the ``plaster_pastedeploy`` library which binds
+ the :term:`PasteDeploy` library and returns a parser that can understand
+ the format.
+
+#. The :term:`PasteDeploy` finds a section named either ``[app:main]``,
``[pipeline:main]``, or ``[composite:main]`` in the ``.ini`` file. This
section represents the configuration of a :term:`WSGI` application that will
be served. If you're using a simple application (e.g., ``[app:main]``), the
@@ -132,8 +139,8 @@ Here's a high-level time-ordered overview of what happens when you press
#. ``pserve`` starts the WSGI *server* defined within the ``[server:main]``
section. In our case, this is the Waitress server (``use =
egg:waitress#main``), and it will listen on all interfaces on port 6543
- for both IPv4 and IPv6 (``listen = 127.0.0.1:6543 [::1]:6543``). The server
- code itself is what prints ``serving on http://127.0.0.1:6543``. The server
+ for both IPv4 and IPv6 (``listen = localhost:6543``). The server
+ code itself is what prints ``Serving on http://localhost:6543``. The server
serves the application, and the application is running, waiting to receive requests.
.. seealso::
diff --git a/docs/narr/subrequest.rst b/docs/narr/subrequest.rst
index 7c847de50..b4deb42f2 100644
--- a/docs/narr/subrequest.rst
+++ b/docs/narr/subrequest.rst
@@ -62,7 +62,7 @@ object:
.. code-block:: python
:linenos:
- :emphasize-lines: 11
+ :emphasize-lines: 11,18
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst
index 6b3b5fcce..156cb863f 100644
--- a/docs/narr/templates.rst
+++ b/docs/narr/templates.rst
@@ -228,6 +228,10 @@ These values are provided to the template:
provided if the template is rendered as the result of a ``renderer=``
argument to the view configuration being used.
+``get_csrf_token()``
+ A convenience function to access the current CSRF token. See
+ :ref:`get_csrf_token_in_templates` for more information.
+
``renderer_name``
The renderer name used to perform the rendering, e.g.,
``mypackage:templates/foo.pt``.
@@ -444,14 +448,14 @@ templating languages including the following:
| Mako_ | pyramid_mako_ | .mak, .mako |
+---------------------------+----------------------------+--------------------+
-.. _Chameleon: http://chameleon.readthedocs.org/en/latest/
+.. _Chameleon: https://chameleon.readthedocs.io/en/latest/
.. _pyramid_chameleon:
- http://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/
+ https://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/
.. _Jinja2: http://jinja.pocoo.org/docs/dev/
.. _pyramid_jinja2:
- http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/
+ https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/
.. _Mako: http://www.makotemplates.org/
.. _pyramid_mako:
- http://docs.pylonsproject.org/projects/pyramid-mako/en/latest/
+ https://docs.pylonsproject.org/projects/pyramid-mako/en/latest/
diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst
index 406383bbd..594badcb6 100644
--- a/docs/narr/testing.rst
+++ b/docs/narr/testing.rst
@@ -158,7 +158,7 @@ Test setup using a context manager
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An alternative style of setting up a test configuration is to use the ``with``
-statement and :func:`pyramid.testing.testConfig` to create a context manager.
+statement and :func:`pyramid.testing.testConfig` to create a :term:`context manager`.
The context manager will call :func:`pyramid.testing.setUp` before the code
under test and :func:`pyramid.testing.tearDown` afterwards.
@@ -376,18 +376,16 @@ following the ``requires`` block in the file ``myproject/setup.py``.
.. literalinclude:: myproject/setup.py
:language: python
- :linenos:
- :lines: 11-22
- :lineno-start: 11
- :emphasize-lines: 8-
+ :lines: 11-23
+ :lineno-match:
+ :emphasize-lines: 9-
Remember to change the dependency.
.. literalinclude:: myproject/setup.py
:language: python
- :linenos:
- :lines: 40-44
- :lineno-start: 40
+ :lines: 42-46
+ :lineno-match:
:emphasize-lines: 2-4
As always, whenever you change your dependencies, make sure to run the correct
diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst
index 578fdeb51..406351562 100644
--- a/docs/narr/webob.rst
+++ b/docs/narr/webob.rst
@@ -27,7 +27,7 @@ functionality to the standard WebOb request, which is documented in the
:ref:`request_module` API documentation.
WebOb provides objects for HTTP requests and responses. Specifically it does
-this by wrapping the `WSGI <http://wsgi.readthedocs.org/en/latest/>`_ request
+this by wrapping the `WSGI <https://wsgi.readthedocs.io/en/latest/>`_ request
environment and response status, header list, and app_iter (body) values.
WebOb request and response objects provide many conveniences for parsing WSGI
@@ -88,7 +88,7 @@ below.
``req.urlvars`` and ``req.urlargs``
``req.urlvars`` are the keyword parameters associated with the request URL.
``req.urlargs`` are the positional parameters. These are set by products
- like `Routes <http://routes.readthedocs.org/en/latest/>`_ and `Selector
+ like `Routes <https://routes.readthedocs.io/en/latest/>`_ and `Selector
<https://github.com/lukearno/selector>`_.
Also for standard HTTP request headers, there are usually attributes such as
diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst
index 053846276..c6e696ae3 100644
--- a/docs/quick_tour.rst
+++ b/docs/quick_tour.rst
@@ -46,7 +46,7 @@ For Windows:
# set an environment variable to where you want your virtual environment
c:\\> set VENV=c:\\env
# create the virtual environment
- c:\\> %VENV%\\Scripts\\python -m venv %VENV%
+ c:\\> python -m venv %VENV%
# install pyramid
c:\\> %VENV%\\Scripts\\pip install pyramid
# or for a specific released version
@@ -90,7 +90,7 @@ explanation:
#. *Line 10*. ``if __name__ == '__main__':`` is Python's way of saying "Start
here when running from the command line".
-#. *Lines 11-13*. Use Pyramid's :term:`configurator` to connect :term:`view`
+#. *Lines 11-13*. Use Pyramid's :term:`configurator` in a :term:`context manager` to connect :term:`view`
code to a particular URL :term:`route`.
#. *Lines 6-7*. Implement the view code that generates the :term:`response`.
@@ -510,14 +510,21 @@ Let's use the cookiecutter ``pyramid-cookiecutter-starter`` to create a starter
.. code-block:: bash
- $ $VENV/bin/cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter
+ $ $VENV/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master
If prompted for the first item, accept the default ``yes`` by hitting return.
-#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it
- okay to delete and re-clone it? [yes]:``
-#. ``project_name [Pyramid Scaffold]: hello_world``
-#. ``repo_name [scaffold]: hello_world``
+.. code-block:: text
+
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: hello_world
+ repo_name [hello_world]: hello_world
+ Select template_language:
+ 1 - jinja2
+ 2 - chameleon
+ 3 - mako
+ Choose from 1, 2, 3 [1]: 1
We then run through the following commands.
@@ -611,7 +618,7 @@ We have a few decisions made for us in this configuration:
#. *Choice of web server:* ``use = egg:waitress#main`` tells ``pserve`` to
use the ``waitress`` server.
-#. *Interfaces:* ``listen = 127.0.0.1:6543 [::1]:6543`` tells ``waitress`` to listen on all interfaces on port 6543 for both IPv4 and IPv6.
+#. *Interfaces:* ``listen = localhost:6543`` tells ``waitress`` to listen on all interfaces on port 6543 for both IPv4 and IPv6.
Additionally the ``development.ini`` generated by this cookiecutter wired up
Python's standard logging. We'll now see in the console, for example, a log on
@@ -640,8 +647,8 @@ add-on ``pyramid_debugtoolbar`` in its ``setup.py``:
.. literalinclude:: quick_tour/package/setup.py
:language: python
:lineno-match:
- :lines: 11-16
- :emphasize-lines: 4
+ :lines: 11-17
+ :emphasize-lines: 5
It was installed when you previously ran:
@@ -650,14 +657,7 @@ It was installed when you previously ran:
$ $VENV/bin/pip install -e ".[testing]"
The ``pyramid_debugtoolbar`` package is a Pyramid add-on, which means we need
-to include its configuration into our web application. The cookiecutter already took care of this for us in its ``__init__.py``:
-
-.. literalinclude:: quick_tour/package/hello_world/__init__.py
- :language: python
- :lineno-match:
- :lines: 8
-
-And it uses the ``pyramid.includes`` facility in our ``development.ini``:
+to include its configuration into our web application. The cookiecutter already took care of this for us in its ``development.ini`` using the ``pyramid.includes`` facility:
.. literalinclude:: quick_tour/package/development.ini
:language: ini
@@ -685,18 +685,17 @@ before its release.
Our ``pyramid-cookiecutter-starter`` cookiecutter generated a ``tests.py`` module with
one unit test and one functional test in it. It also configured ``setup.py`` with test requirements:
``py.test`` as the test runner, ``WebTest`` for running view tests, and the
-``pytest-cov`` tool which yells at us for code that isn't tested. The
-highlighted lines show this:
+``pytest-cov`` tool which yells at us for code that isn't tested:
.. literalinclude:: quick_tour/package/setup.py
:language: python
:lineno-match:
- :lines: 18-22
+ :lines: 19-23
.. literalinclude:: quick_tour/package/setup.py
:language: python
:lineno-match:
- :lines: 42-44
+ :lines: 43-45
We already installed the test requirements when we ran the command ``$VENV/bin/pip install -e ".[testing]"``. We can now run all our tests:
@@ -859,14 +858,16 @@ Pyramid and SQLAlchemy are great friends. That friendship includes a cookiecutte
.. code-block:: bash
$ cd ~
- $ env/bin/cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy
+ $ env/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy --checkout master
If prompted for the first item, accept the default ``yes`` by hitting return.
-#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. Is it
- okay to delete and re-clone it? [yes]:``
-#. ``project_name [Pyramid Scaffold]: sqla_demo``
-#. ``repo_name [scaffold]: sqla_demo``
+.. code-block:: text
+
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: sqla_demo
+ repo_name [sqla_demo]: sqla_demo
We then run through the following commands as before.
diff --git a/docs/quick_tour/hello_world/app.py b/docs/quick_tour/hello_world/app.py
index 75d22ac96..dc6f32310 100644
--- a/docs/quick_tour/hello_world/app.py
+++ b/docs/quick_tour/hello_world/app.py
@@ -8,9 +8,9 @@ def hello_world(request):
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/')
- config.add_view(hello_world, route_name='hello')
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_route('hello', '/')
+ config.add_view(hello_world, route_name='hello')
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
diff --git a/docs/quick_tour/jinja2/app.py b/docs/quick_tour/jinja2/app.py
index b7632807b..2683e0451 100644
--- a/docs/quick_tour/jinja2/app.py
+++ b/docs/quick_tour/jinja2/app.py
@@ -2,10 +2,10 @@ from wsgiref.simple_server import make_server
from pyramid.config import Configurator
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/howdy/{name}')
- config.include('pyramid_jinja2')
- config.scan('views')
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_route('hello', '/howdy/{name}')
+ config.include('pyramid_jinja2')
+ config.scan('views')
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
diff --git a/docs/quick_tour/json/app.py b/docs/quick_tour/json/app.py
index 40faddd00..a007dee14 100644
--- a/docs/quick_tour/json/app.py
+++ b/docs/quick_tour/json/app.py
@@ -2,12 +2,12 @@ from wsgiref.simple_server import make_server
from pyramid.config import Configurator
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/howdy/{name}')
- config.add_route('hello_json', 'hello.json')
- config.add_static_view(name='static', path='static')
- config.include('pyramid_jinja2')
- config.scan('views')
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_route('hello', '/howdy/{name}')
+ config.add_route('hello_json', 'hello.json')
+ config.add_static_view(name='static', path='static')
+ config.include('pyramid_jinja2')
+ config.scan('views')
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
diff --git a/docs/quick_tour/logging/README.txt b/docs/quick_tour/logging/README.txt
index fb7bde0a7..ff70a1354 100644
--- a/docs/quick_tour/logging/README.txt
+++ b/docs/quick_tour/logging/README.txt
@@ -1,5 +1,5 @@
hello_world
-===============================
+===========
Getting Started
---------------
diff --git a/docs/quick_tour/logging/development.ini b/docs/quick_tour/logging/development.ini
index 1f19e373d..5e7317fc7 100644
--- a/docs/quick_tour/logging/development.ini
+++ b/docs/quick_tour/logging/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -24,11 +24,11 @@ pyramid.includes =
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/quick_tour/logging/hello_world/templates/layout.jinja2 b/docs/quick_tour/logging/hello_world/templates/layout.jinja2
index 916127267..64142f819 100644
--- a/docs/quick_tour/logging/hello_world/templates/layout.jinja2
+++ b/docs/quick_tour/logging/hello_world/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>Cookiecutter Starter project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('hello_world:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -42,7 +42,7 @@
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -58,7 +58,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/quick_tour/logging/hello_world/templates/mytemplate.jinja2 b/docs/quick_tour/logging/hello_world/templates/mytemplate.jinja2
index cf2d7f996..f2e7283f8 100644
--- a/docs/quick_tour/logging/hello_world/templates/mytemplate.jinja2
+++ b/docs/quick_tour/logging/hello_world/templates/mytemplate.jinja2
@@ -3,6 +3,6 @@
{% block content %}
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">hello_world</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
{% endblock content %}
diff --git a/docs/quick_tour/logging/production.ini b/docs/quick_tour/logging/production.ini
index 9c12bc4ec..3eedddda1 100644
--- a/docs/quick_tour/logging/production.ini
+++ b/docs/quick_tour/logging/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -22,7 +22,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/quick_tour/logging/setup.py b/docs/quick_tour/logging/setup.py
index e32aecacd..44d90b990 100644
--- a/docs/quick_tour/logging/setup.py
+++ b/docs/quick_tour/logging/setup.py
@@ -9,6 +9,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
+ 'plaster_pastedeploy',
'pyramid',
'pyramid_jinja2',
'pyramid_debugtoolbar',
diff --git a/docs/quick_tour/package/README.txt b/docs/quick_tour/package/README.txt
index fb7bde0a7..ff70a1354 100644
--- a/docs/quick_tour/package/README.txt
+++ b/docs/quick_tour/package/README.txt
@@ -1,5 +1,5 @@
hello_world
-===============================
+===========
Getting Started
---------------
diff --git a/docs/quick_tour/package/development.ini b/docs/quick_tour/package/development.ini
index 1f19e373d..5e7317fc7 100644
--- a/docs/quick_tour/package/development.ini
+++ b/docs/quick_tour/package/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -24,11 +24,11 @@ pyramid.includes =
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/quick_tour/package/hello_world/templates/layout.jinja2 b/docs/quick_tour/package/hello_world/templates/layout.jinja2
index 916127267..64142f819 100644
--- a/docs/quick_tour/package/hello_world/templates/layout.jinja2
+++ b/docs/quick_tour/package/hello_world/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>Cookiecutter Starter project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('hello_world:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -42,7 +42,7 @@
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -58,7 +58,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2 b/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2
index cf2d7f996..f2e7283f8 100644
--- a/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2
+++ b/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2
@@ -3,6 +3,6 @@
{% block content %}
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">hello_world</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
{% endblock content %}
diff --git a/docs/quick_tour/package/production.ini b/docs/quick_tour/package/production.ini
index 9c12bc4ec..3eedddda1 100644
--- a/docs/quick_tour/package/production.ini
+++ b/docs/quick_tour/package/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -22,7 +22,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/quick_tour/package/setup.py b/docs/quick_tour/package/setup.py
index e32aecacd..44d90b990 100644
--- a/docs/quick_tour/package/setup.py
+++ b/docs/quick_tour/package/setup.py
@@ -9,6 +9,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
+ 'plaster_pastedeploy',
'pyramid',
'pyramid_jinja2',
'pyramid_debugtoolbar',
diff --git a/docs/quick_tour/requests/app.py b/docs/quick_tour/requests/app.py
index f55264cff..58e8823cc 100644
--- a/docs/quick_tour/requests/app.py
+++ b/docs/quick_tour/requests/app.py
@@ -16,9 +16,9 @@ def hello_world(request):
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/')
- config.add_view(hello_world, route_name='hello')
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_route('hello', '/')
+ config.add_view(hello_world, route_name='hello')
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
diff --git a/docs/quick_tour/routing/app.py b/docs/quick_tour/routing/app.py
index 12b547bfe..75c18b7d1 100644
--- a/docs/quick_tour/routing/app.py
+++ b/docs/quick_tour/routing/app.py
@@ -2,9 +2,9 @@ from wsgiref.simple_server import make_server
from pyramid.config import Configurator
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/howdy/{first}/{last}')
- config.scan('views')
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_route('hello', '/howdy/{first}/{last}')
+ config.scan('views')
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
- server.serve_forever() \ No newline at end of file
+ server.serve_forever()
diff --git a/docs/quick_tour/sessions/README.txt b/docs/quick_tour/sessions/README.txt
index fb7bde0a7..ff70a1354 100644
--- a/docs/quick_tour/sessions/README.txt
+++ b/docs/quick_tour/sessions/README.txt
@@ -1,5 +1,5 @@
hello_world
-===============================
+===========
Getting Started
---------------
diff --git a/docs/quick_tour/sessions/development.ini b/docs/quick_tour/sessions/development.ini
index 1f19e373d..5e7317fc7 100644
--- a/docs/quick_tour/sessions/development.ini
+++ b/docs/quick_tour/sessions/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -24,11 +24,11 @@ pyramid.includes =
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 b/docs/quick_tour/sessions/hello_world/templates/layout.jinja2
index 916127267..64142f819 100644
--- a/docs/quick_tour/sessions/hello_world/templates/layout.jinja2
+++ b/docs/quick_tour/sessions/hello_world/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>Cookiecutter Starter project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('hello_world:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -42,7 +42,7 @@
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -58,7 +58,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/quick_tour/sessions/hello_world/templates/mytemplate.jinja2 b/docs/quick_tour/sessions/hello_world/templates/mytemplate.jinja2
index c7776144c..b8562709c 100644
--- a/docs/quick_tour/sessions/hello_world/templates/mytemplate.jinja2
+++ b/docs/quick_tour/sessions/hello_world/templates/mytemplate.jinja2
@@ -3,7 +3,7 @@
{% block content %}
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">hello_world</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
<p>Counter: {{ request.session.counter }}</p>
</div>
{% endblock content %}
diff --git a/docs/quick_tour/sessions/production.ini b/docs/quick_tour/sessions/production.ini
index 9c12bc4ec..3eedddda1 100644
--- a/docs/quick_tour/sessions/production.ini
+++ b/docs/quick_tour/sessions/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -22,7 +22,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/quick_tour/sessions/setup.py b/docs/quick_tour/sessions/setup.py
index e32aecacd..44d90b990 100644
--- a/docs/quick_tour/sessions/setup.py
+++ b/docs/quick_tour/sessions/setup.py
@@ -9,6 +9,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
+ 'plaster_pastedeploy',
'pyramid',
'pyramid_jinja2',
'pyramid_debugtoolbar',
diff --git a/docs/quick_tour/sqla_demo/README.txt b/docs/quick_tour/sqla_demo/README.txt
index 1659e47ab..27bbff5a7 100644
--- a/docs/quick_tour/sqla_demo/README.txt
+++ b/docs/quick_tour/sqla_demo/README.txt
@@ -1,5 +1,5 @@
sqla_demo
-===============================
+=========
Getting Started
---------------
diff --git a/docs/quick_tour/sqla_demo/development.ini b/docs/quick_tour/sqla_demo/development.ini
index 17b57fd0d..8836a846e 100644
--- a/docs/quick_tour/sqla_demo/development.ini
+++ b/docs/quick_tour/sqla_demo/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite
+retry.attempts = 3
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -26,11 +28,11 @@ sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/quick_tour/sqla_demo/production.ini b/docs/quick_tour/sqla_demo/production.ini
index a85c354d3..4566e02a0 100644
--- a/docs/quick_tour/sqla_demo/production.ini
+++ b/docs/quick_tour/sqla_demo/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite
+retry.attempts = 3
+
###
# wsgi server configuration
###
@@ -24,7 +26,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/quick_tour/sqla_demo/setup.py b/docs/quick_tour/sqla_demo/setup.py
index 75c1403fb..855a15d58 100644
--- a/docs/quick_tour/sqla_demo/setup.py
+++ b/docs/quick_tour/sqla_demo/setup.py
@@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
- 'pyramid',
- 'pyramid_jinja2',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_debugtoolbar',
+ 'pyramid_jinja2',
+ 'pyramid_retry',
'pyramid_tm',
'SQLAlchemy',
'transaction',
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py
index 339326758..31aab9d26 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py
@@ -57,10 +57,14 @@ def includeme(config):
"""
settings = config.get_settings()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
# use pyramid_tm to hook the transaction lifecycle to the request
config.include('pyramid_tm')
+ # use pyramid_retry to retry a request when transient exceptions occur
+ config.include('pyramid_retry')
+
session_factory = get_session_factory(get_engine(settings))
config.registry['dbsession_factory'] = session_factory
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py b/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py
index 0682247b5..02285b3ff 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py
@@ -5,7 +5,7 @@ from sqlalchemy.schema import MetaData
# providers will autogenerate vastly different names making migrations more
# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
NAMING_CONVENTION = {
- "ix": 'ix_%(column_0_label)s',
+ "ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 b/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2
index 4607eb11f..904fa442f 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2
+++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>Cookiecutter Alchemy project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('sqla_demo:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -42,7 +42,7 @@
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -58,7 +58,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2 b/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2
index bd00845d5..26d72c0a6 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2
+++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2
@@ -3,6 +3,6 @@
{% block content %}
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">sqla_demo</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
{% endblock content %}
diff --git a/docs/quick_tour/static_assets/app.py b/docs/quick_tour/static_assets/app.py
index 1849c0a5a..c31fc797c 100644
--- a/docs/quick_tour/static_assets/app.py
+++ b/docs/quick_tour/static_assets/app.py
@@ -2,11 +2,11 @@ from wsgiref.simple_server import make_server
from pyramid.config import Configurator
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/howdy/{name}')
- config.add_static_view(name='static', path='static')
- config.include('pyramid_jinja2')
- config.scan('views')
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_route('hello', '/howdy/{name}')
+ config.add_static_view(name='static', path='static')
+ config.include('pyramid_jinja2')
+ config.scan('views')
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
diff --git a/docs/quick_tour/templating/app.py b/docs/quick_tour/templating/app.py
index 52b7faf55..8db99df91 100644
--- a/docs/quick_tour/templating/app.py
+++ b/docs/quick_tour/templating/app.py
@@ -2,10 +2,10 @@ from wsgiref.simple_server import make_server
from pyramid.config import Configurator
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/howdy/{name}')
- config.include('pyramid_chameleon')
- config.scan('views')
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_route('hello', '/howdy/{name}')
+ config.include('pyramid_chameleon')
+ config.scan('views')
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
diff --git a/docs/quick_tour/view_classes/app.py b/docs/quick_tour/view_classes/app.py
index 40faddd00..a007dee14 100644
--- a/docs/quick_tour/view_classes/app.py
+++ b/docs/quick_tour/view_classes/app.py
@@ -2,12 +2,12 @@ from wsgiref.simple_server import make_server
from pyramid.config import Configurator
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/howdy/{name}')
- config.add_route('hello_json', 'hello.json')
- config.add_static_view(name='static', path='static')
- config.include('pyramid_jinja2')
- config.scan('views')
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_route('hello', '/howdy/{name}')
+ config.add_route('hello_json', 'hello.json')
+ config.add_static_view(name='static', path='static')
+ config.include('pyramid_jinja2')
+ config.scan('views')
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
diff --git a/docs/quick_tour/views/app.py b/docs/quick_tour/views/app.py
index e8df6eff2..12d9d25b5 100644
--- a/docs/quick_tour/views/app.py
+++ b/docs/quick_tour/views/app.py
@@ -2,12 +2,12 @@ from wsgiref.simple_server import make_server
from pyramid.config import Configurator
if __name__ == '__main__':
- config = Configurator()
- config.add_route('home', '/')
- config.add_route('hello', '/howdy')
- config.add_route('redirect', '/goto')
- config.add_route('exception', '/problem')
- config.scan('views')
- app = config.make_wsgi_app()
+ with Configurator() as config:
+ config.add_route('home', '/')
+ config.add_route('hello', '/howdy')
+ config.add_route('redirect', '/goto')
+ config.add_route('exception', '/problem')
+ config.scan('views')
+ app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
diff --git a/docs/quick_tour/views/views.py b/docs/quick_tour/views/views.py
index 1449cbb38..95a2b60ca 100644
--- a/docs/quick_tour/views/views.py
+++ b/docs/quick_tour/views/views.py
@@ -1,4 +1,4 @@
-import cgi
+from pyramid.compat import escape
from pyramid.httpexceptions import HTTPFound
from pyramid.response import Response
@@ -16,8 +16,8 @@ def home_view(request):
def hello_view(request):
name = request.params.get('name', 'No Name')
body = '<p>Hi %s, this <a href="/goto">redirects</a></p>'
- # cgi.escape to prevent Cross-Site Scripting (XSS) [CWE 79]
- return Response(body % cgi.escape(name))
+ # pyramid.compat.escape to prevent Cross-Site Scripting (XSS) [CWE 79]
+ return Response(body % escape(name))
# /goto which issues HTTP redirect to the last view
diff --git a/docs/quick_tutorial/authentication.rst b/docs/quick_tutorial/authentication.rst
index 892beb3ec..684cce6a6 100644
--- a/docs/quick_tutorial/authentication.rst
+++ b/docs/quick_tutorial/authentication.rst
@@ -39,7 +39,7 @@ Steps
.. literalinclude:: authentication/setup.py
:language: python
- :emphasize-lines: 5-6
+ :emphasize-lines: 7
:linenos:
#. We can now install our project in development mode:
diff --git a/docs/quick_tutorial/authentication/development.ini b/docs/quick_tutorial/authentication/development.ini
index a4586d45f..cae509542 100644
--- a/docs/quick_tutorial/authentication/development.ini
+++ b/docs/quick_tutorial/authentication/development.ini
@@ -6,5 +6,5 @@ pyramid.includes =
tutorial.secret = 98zd
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/authentication/setup.py b/docs/quick_tutorial/authentication/setup.py
index 7a6ff4226..808a6f9a9 100644
--- a/docs/quick_tutorial/authentication/setup.py
+++ b/docs/quick_tutorial/authentication/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon',
'bcrypt'
]
diff --git a/docs/quick_tutorial/authentication/tutorial/views.py b/docs/quick_tutorial/authentication/tutorial/views.py
index b07538d5e..b2d9354ec 100644
--- a/docs/quick_tutorial/authentication/tutorial/views.py
+++ b/docs/quick_tutorial/authentication/tutorial/views.py
@@ -43,7 +43,8 @@ class TutorialViews:
if 'form.submitted' in request.params:
login = request.params['login']
password = request.params['password']
- if check_password(password, USERS.get(login)):
+ hashed_pw = USERS.get(login)
+ if hashed_pw and check_password(password, hashed_pw):
headers = remember(request, login)
return HTTPFound(location=came_from,
headers=headers)
diff --git a/docs/quick_tutorial/authorization/development.ini b/docs/quick_tutorial/authorization/development.ini
index a4586d45f..cae509542 100644
--- a/docs/quick_tutorial/authorization/development.ini
+++ b/docs/quick_tutorial/authorization/development.ini
@@ -6,5 +6,5 @@ pyramid.includes =
tutorial.secret = 98zd
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/authorization/setup.py b/docs/quick_tutorial/authorization/setup.py
index 7a6ff4226..808a6f9a9 100644
--- a/docs/quick_tutorial/authorization/setup.py
+++ b/docs/quick_tutorial/authorization/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon',
'bcrypt'
]
diff --git a/docs/quick_tutorial/authorization/tutorial/views.py b/docs/quick_tutorial/authorization/tutorial/views.py
index b2dc905c0..3876efb1c 100644
--- a/docs/quick_tutorial/authorization/tutorial/views.py
+++ b/docs/quick_tutorial/authorization/tutorial/views.py
@@ -45,7 +45,8 @@ class TutorialViews:
if 'form.submitted' in request.params:
login = request.params['login']
password = request.params['password']
- if check_password(password, USERS.get(login)):
+ hashed_pw = USERS.get(login)
+ if hashed_pw and check_password(password, hashed_pw):
headers = remember(request, login)
return HTTPFound(location=came_from,
headers=headers)
diff --git a/docs/quick_tutorial/cookiecutters.rst b/docs/quick_tutorial/cookiecutters.rst
index 8e7048f78..f8568206d 100644
--- a/docs/quick_tutorial/cookiecutters.rst
+++ b/docs/quick_tutorial/cookiecutters.rst
@@ -28,13 +28,21 @@ Steps
.. code-block:: bash
- $ $VENV/bin/cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter
+ $ $VENV/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master
If prompted for the first item, accept the default ``yes`` by hitting return.
- #. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it okay to delete and re-clone it? [yes]:``
- #. ``project_name [Pyramid Scaffold]: cc_starter``
- #. ``repo_name [scaffold]: cc_starter``
+ .. code-block:: text
+
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: cc_starter
+ repo_name [cc_starter]: cc_starter
+ Select template_language:
+ 1 - jinja2
+ 2 - chameleon
+ 3 - mako
+ Choose from 1, 2, 3 [1]: 1
#. We then run through the following commands.
diff --git a/docs/quick_tutorial/cookiecutters/README.txt b/docs/quick_tutorial/cookiecutters/README.txt
index 4b1f31bf3..55c5dcec6 100644
--- a/docs/quick_tutorial/cookiecutters/README.txt
+++ b/docs/quick_tutorial/cookiecutters/README.txt
@@ -1,5 +1,5 @@
cc_starter
-===============================
+==========
Getting Started
---------------
diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 b/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2
index 20da74879..13a65ff72 100644
--- a/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2
+++ b/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>Cookiecutter Starter project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('cc_starter:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -42,7 +42,7 @@
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -58,7 +58,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/templates/mytemplate.jinja2 b/docs/quick_tutorial/cookiecutters/cc_starter/templates/mytemplate.jinja2
index 979ee5071..f2e7283f8 100644
--- a/docs/quick_tutorial/cookiecutters/cc_starter/templates/mytemplate.jinja2
+++ b/docs/quick_tutorial/cookiecutters/cc_starter/templates/mytemplate.jinja2
@@ -3,6 +3,6 @@
{% block content %}
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">cc_starter</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
{% endblock content %}
diff --git a/docs/quick_tutorial/cookiecutters/development.ini b/docs/quick_tutorial/cookiecutters/development.ini
index 86b54b51d..ec621169d 100644
--- a/docs/quick_tutorial/cookiecutters/development.ini
+++ b/docs/quick_tutorial/cookiecutters/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -24,11 +24,11 @@ pyramid.includes =
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/quick_tutorial/cookiecutters/production.ini b/docs/quick_tutorial/cookiecutters/production.ini
index e24a065b1..8d2b9c79d 100644
--- a/docs/quick_tutorial/cookiecutters/production.ini
+++ b/docs/quick_tutorial/cookiecutters/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -22,7 +22,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/quick_tutorial/databases/development.ini b/docs/quick_tutorial/databases/development.ini
index 270643071..270cf7b63 100644
--- a/docs/quick_tutorial/databases/development.ini
+++ b/docs/quick_tutorial/databases/development.ini
@@ -8,8 +8,8 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/sqltutorial.sqlite
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
# Begin logging configuration
diff --git a/docs/quick_tutorial/databases/setup.py b/docs/quick_tutorial/databases/setup.py
index 238358fe4..66045fd7e 100644
--- a/docs/quick_tutorial/databases/setup.py
+++ b/docs/quick_tutorial/databases/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon',
'deform',
'sqlalchemy',
diff --git a/docs/quick_tutorial/debugtoolbar.rst b/docs/quick_tutorial/debugtoolbar.rst
index b02363d40..4402fcdbd 100644
--- a/docs/quick_tutorial/debugtoolbar.rst
+++ b/docs/quick_tutorial/debugtoolbar.rst
@@ -75,7 +75,7 @@ configuration for the debugtoolbar.
You'll now see an attractive button on the right side of your browser, which
you may click to provide introspective access to debugging information in a new
-rowser tab. Even better, if your web application generates an error, you will
+browser tab. Even better, if your web application generates an error, you will
see a nice traceback on the screen. When you want to disable this toolbar,
there's no need to change code: you can remove it from ``pyramid.includes`` in
the relevant ``.ini`` configuration file (thus showing why configuration files
diff --git a/docs/quick_tutorial/debugtoolbar/development.ini b/docs/quick_tutorial/debugtoolbar/development.ini
index 17b479011..58d23cff7 100644
--- a/docs/quick_tutorial/debugtoolbar/development.ini
+++ b/docs/quick_tutorial/debugtoolbar/development.ini
@@ -4,5 +4,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/debugtoolbar/setup.py b/docs/quick_tutorial/debugtoolbar/setup.py
index 9997984d3..a93cf6a73 100644
--- a/docs/quick_tutorial/debugtoolbar/setup.py
+++ b/docs/quick_tutorial/debugtoolbar/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
]
setup(name='tutorial',
diff --git a/docs/quick_tutorial/forms.rst b/docs/quick_tutorial/forms.rst
index 84ceb13d6..3c865ad09 100644
--- a/docs/quick_tutorial/forms.rst
+++ b/docs/quick_tutorial/forms.rst
@@ -41,7 +41,7 @@ Steps
pulls in Colander as a dependency:
.. literalinclude:: forms/setup.py
- :emphasize-lines: 5-6
+ :emphasize-lines: 7
:linenos:
#. We can now install our project in development mode:
diff --git a/docs/quick_tutorial/forms/development.ini b/docs/quick_tutorial/forms/development.ini
index 7066668bf..78d7479e7 100644
--- a/docs/quick_tutorial/forms/development.ini
+++ b/docs/quick_tutorial/forms/development.ini
@@ -5,5 +5,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/forms/setup.py b/docs/quick_tutorial/forms/setup.py
index 361ade013..5293ef7f0 100644
--- a/docs/quick_tutorial/forms/setup.py
+++ b/docs/quick_tutorial/forms/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon',
'deform'
]
diff --git a/docs/quick_tutorial/functional_testing.rst b/docs/quick_tutorial/functional_testing.rst
index 33793578a..518e3d67d 100644
--- a/docs/quick_tutorial/functional_testing.rst
+++ b/docs/quick_tutorial/functional_testing.rst
@@ -14,7 +14,7 @@ Unit tests are a common and popular approach to test-driven development (TDD).
In web applications, though, the templating and entire apparatus of a web site
are important parts of the delivered quality. We'd like a way to test these.
-`WebTest <http://docs.pylonsproject.org/projects/webtest/en/latest/>`_ is a
+`WebTest <https://docs.pylonsproject.org/projects/webtest/en/latest/>`_ is a
Python package that does functional testing. With WebTest you can write tests
which simulate a full HTTP request against a WSGI application, then test the
information in the response. For speed purposes, WebTest skips the
diff --git a/docs/quick_tutorial/functional_testing/development.ini b/docs/quick_tutorial/functional_testing/development.ini
index 17b479011..58d23cff7 100644
--- a/docs/quick_tutorial/functional_testing/development.ini
+++ b/docs/quick_tutorial/functional_testing/development.ini
@@ -4,5 +4,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/functional_testing/setup.py b/docs/quick_tutorial/functional_testing/setup.py
index 9997984d3..a93cf6a73 100644
--- a/docs/quick_tutorial/functional_testing/setup.py
+++ b/docs/quick_tutorial/functional_testing/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
]
setup(name='tutorial',
diff --git a/docs/quick_tutorial/hello_world.rst b/docs/quick_tutorial/hello_world.rst
index 2f2515fcd..94242f1f4 100644
--- a/docs/quick_tutorial/hello_world.rst
+++ b/docs/quick_tutorial/hello_world.rst
@@ -75,7 +75,7 @@ explanation:
"Start here when running from the command line", rather than when this
module is imported.
-#. *Lines 12-14*. Use Pyramid's :term:`configurator` to connect :term:`view`
+#. *Lines 12-14*. Use Pyramid's :term:`configurator` in a :term:`context manager` to connect :term:`view`
code to a particular URL :term:`route`.
#. *Lines 6-8*. Implement the view code that generates the :term:`response`.
diff --git a/docs/quick_tutorial/hello_world/app.py b/docs/quick_tutorial/hello_world/app.py
index 0a95f9ad3..ff0b950d3 100644
--- a/docs/quick_tutorial/hello_world/app.py
+++ b/docs/quick_tutorial/hello_world/app.py
@@ -1,4 +1,4 @@
-from wsgiref.simple_server import make_server
+from waitress import serve
from pyramid.config import Configurator
from pyramid.response import Response
@@ -9,9 +9,8 @@ def hello_world(request):
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/')
- config.add_view(hello_world, route_name='hello')
- app = config.make_wsgi_app()
- server = make_server('0.0.0.0', 6543, app)
- server.serve_forever()
+ with Configurator() as config:
+ config.add_route('hello', '/')
+ config.add_view(hello_world, route_name='hello')
+ app = config.make_wsgi_app()
+ serve(app, host='0.0.0.0', port=6543)
diff --git a/docs/quick_tutorial/ini.rst b/docs/quick_tutorial/ini.rst
index 96dfc5b5f..e4f30405f 100644
--- a/docs/quick_tutorial/ini.rst
+++ b/docs/quick_tutorial/ini.rst
@@ -89,7 +89,7 @@ application. Processing then proceeds as described in the Pyramid chapter on
- ``pserve`` looks for ``[app:main]`` and finds ``use = egg:tutorial``.
-- The projects's ``setup.py`` has defined an "entry point" (lines 9-12) for the
+- The projects's ``setup.py`` has defined an "entry point" (lines 10-13) for the
project's "main" entry point of ``tutorial:main``.
- The ``tutorial`` package's ``__init__`` has a ``main`` function.
@@ -99,10 +99,14 @@ application. Processing then proceeds as described in the Pyramid chapter on
The ``.ini`` file is also used for two other functions:
-- *Configuring the WSGI server*. ``[server:main]`` wires up the choice of which
- WSGI *server* for your WSGI *application*. In this case, we are using
- ``wsgiref`` bundled in the Python library. It also wires up the *port
- number*: ``port = 6543`` tells ``wsgiref`` to listen on port 6543.
+- *Configuring the WSGI server*. ``[server:main]`` wires up the choice
+ of which WSGI *server* for your WSGI *application*. In this case, we
+ are using ``waitress`` which we specified in
+ ``tutorial/setup.py`` and was installed in the :doc:`requirements` step at the start of this tutorial. It also wires up the *port number*:
+ ``listen = localhost:6543`` tells ``waitress`` to listen on host
+ ``localhost`` at port ``6543``.
+
+ .. note:: Running the command ``$VENV/bin/pip install -e .`` will check for previously installed packages in our virtual environment that are specified in our package's ``setup.py`` file, then install our package in editable mode, installing any requirements that were not previously installed. If a requirement was manually installed previously on the command line or otherwise, in this case Waitress, then ``$VENV/bin/pip install -e .`` will merely check that it is installed and move on.
- *Configuring Python logging*. Pyramid uses Python standard logging, which
needs a number of configuration values. The ``.ini`` serves this function.
diff --git a/docs/quick_tutorial/ini/development.ini b/docs/quick_tutorial/ini/development.ini
index cffbd66c9..5361188a3 100644
--- a/docs/quick_tutorial/ini/development.ini
+++ b/docs/quick_tutorial/ini/development.ini
@@ -2,5 +2,5 @@
use = egg:tutorial
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/ini/setup.py b/docs/quick_tutorial/ini/setup.py
index 9997984d3..a93cf6a73 100644
--- a/docs/quick_tutorial/ini/setup.py
+++ b/docs/quick_tutorial/ini/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
]
setup(name='tutorial',
diff --git a/docs/quick_tutorial/jinja2.rst b/docs/quick_tutorial/jinja2.rst
index 2fc68827b..4faa81fc4 100644
--- a/docs/quick_tutorial/jinja2.rst
+++ b/docs/quick_tutorial/jinja2.rst
@@ -85,7 +85,7 @@ Extra credit
#. We used ``config.include`` which is an imperative configuration to get the
:term:`Configurator` to load ``pyramid_jinja2``'s configuration. What is
- another way could include it into the config?
+ another way we could include it into the config?
.. seealso:: `Jinja2 homepage <http://jinja.pocoo.org/>`_, and
:ref:`pyramid_jinja2 Overview <jinja2:overview>`
diff --git a/docs/quick_tutorial/jinja2/development.ini b/docs/quick_tutorial/jinja2/development.ini
index 7066668bf..78d7479e7 100644
--- a/docs/quick_tutorial/jinja2/development.ini
+++ b/docs/quick_tutorial/jinja2/development.ini
@@ -5,5 +5,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/jinja2/setup.py b/docs/quick_tutorial/jinja2/setup.py
index 9997984d3..a93cf6a73 100644
--- a/docs/quick_tutorial/jinja2/setup.py
+++ b/docs/quick_tutorial/jinja2/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
]
setup(name='tutorial',
diff --git a/docs/quick_tutorial/json/development.ini b/docs/quick_tutorial/json/development.ini
index 7066668bf..78d7479e7 100644
--- a/docs/quick_tutorial/json/development.ini
+++ b/docs/quick_tutorial/json/development.ini
@@ -5,5 +5,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/json/setup.py b/docs/quick_tutorial/json/setup.py
index 2221b72e9..aefa352d4 100644
--- a/docs/quick_tutorial/json/setup.py
+++ b/docs/quick_tutorial/json/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon'
]
diff --git a/docs/quick_tutorial/logging/development.ini b/docs/quick_tutorial/logging/development.ini
index b869ca5b6..ff470acdb 100644
--- a/docs/quick_tutorial/logging/development.ini
+++ b/docs/quick_tutorial/logging/development.ini
@@ -5,8 +5,8 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
# Begin logging configuration
diff --git a/docs/quick_tutorial/logging/setup.py b/docs/quick_tutorial/logging/setup.py
index 2221b72e9..aefa352d4 100644
--- a/docs/quick_tutorial/logging/setup.py
+++ b/docs/quick_tutorial/logging/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon'
]
diff --git a/docs/quick_tutorial/more_view_classes/development.ini b/docs/quick_tutorial/more_view_classes/development.ini
index 7066668bf..78d7479e7 100644
--- a/docs/quick_tutorial/more_view_classes/development.ini
+++ b/docs/quick_tutorial/more_view_classes/development.ini
@@ -5,5 +5,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/more_view_classes/setup.py b/docs/quick_tutorial/more_view_classes/setup.py
index 2221b72e9..aefa352d4 100644
--- a/docs/quick_tutorial/more_view_classes/setup.py
+++ b/docs/quick_tutorial/more_view_classes/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon'
]
diff --git a/docs/quick_tutorial/package.rst b/docs/quick_tutorial/package.rst
index 94cb39fc9..66bafcdb9 100644
--- a/docs/quick_tutorial/package.rst
+++ b/docs/quick_tutorial/package.rst
@@ -108,4 +108,4 @@ idea to run a Python module inside a package directly as a script.
.. seealso:: :ref:`Python Packages <python:tut-packages>` and `Working in
"Development Mode"
- <https://packaging.python.org/en/latest/distributing/#working-in-development-mode>`_.
+ <https://packaging.python.org/tutorials/distributing-packages/#working-in-development-mode>`_.
diff --git a/docs/quick_tutorial/package/tutorial/app.py b/docs/quick_tutorial/package/tutorial/app.py
index 210075023..ff0b950d3 100644
--- a/docs/quick_tutorial/package/tutorial/app.py
+++ b/docs/quick_tutorial/package/tutorial/app.py
@@ -1,17 +1,16 @@
-from wsgiref.simple_server import make_server
+from waitress import serve
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
- print ('Incoming request')
+ print('Incoming request')
return Response('<body><h1>Hello World!</h1></body>')
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/')
- config.add_view(hello_world, route_name='hello')
- app = config.make_wsgi_app()
- server = make_server('0.0.0.0', 6543, app)
- server.serve_forever() \ No newline at end of file
+ with Configurator() as config:
+ config.add_route('hello', '/')
+ config.add_view(hello_world, route_name='hello')
+ app = config.make_wsgi_app()
+ serve(app, host='0.0.0.0', port=6543)
diff --git a/docs/quick_tutorial/request_response/development.ini b/docs/quick_tutorial/request_response/development.ini
index 7066668bf..78d7479e7 100644
--- a/docs/quick_tutorial/request_response/development.ini
+++ b/docs/quick_tutorial/request_response/development.ini
@@ -5,5 +5,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/request_response/setup.py b/docs/quick_tutorial/request_response/setup.py
index 9997984d3..a93cf6a73 100644
--- a/docs/quick_tutorial/request_response/setup.py
+++ b/docs/quick_tutorial/request_response/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
]
setup(name='tutorial',
diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst
index 70e68514b..a65cfe6d2 100644
--- a/docs/quick_tutorial/requirements.rst
+++ b/docs/quick_tutorial/requirements.rst
@@ -189,17 +189,18 @@ Install Pyramid
---------------
We have our Python standard prerequisites out of the way. The Pyramid
-part is pretty easy.
+part is pretty easy. We'll also install a WSGI server, Waitress.
.. parsed-literal::
# Mac and Linux
- $ $VENV/bin/pip install "pyramid==\ |release|\ "
+ $ $VENV/bin/pip install "pyramid==\ |release|\ " waitress
# Windows
- c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ "
+ c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ " waitress
-Our Python virtual environment now has the Pyramid software available.
+Our Python virtual environment now has the Pyramid software available
+as well as the ``waitress`` package.
You can optionally install some of the extra Python packages used in this
tutorial.
diff --git a/docs/quick_tutorial/retail_forms/development.ini b/docs/quick_tutorial/retail_forms/development.ini
index 7066668bf..78d7479e7 100644
--- a/docs/quick_tutorial/retail_forms/development.ini
+++ b/docs/quick_tutorial/retail_forms/development.ini
@@ -5,5 +5,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/retail_forms/setup.py b/docs/quick_tutorial/retail_forms/setup.py
index 361ade013..5293ef7f0 100644
--- a/docs/quick_tutorial/retail_forms/setup.py
+++ b/docs/quick_tutorial/retail_forms/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon',
'deform'
]
diff --git a/docs/quick_tutorial/routing.rst b/docs/quick_tutorial/routing.rst
index d88adfa1e..adbe76a62 100644
--- a/docs/quick_tutorial/routing.rst
+++ b/docs/quick_tutorial/routing.rst
@@ -121,4 +121,4 @@ Extra credit
result that you expected?
.. seealso:: `Weird Stuff You Can Do With URL Dispatch
- <http://www.plope.com/weird_pyramid_urldispatch>`_
+ <https://web.archive.org/web/20170131192830/http://www.plope.com/weird_pyramid_urldispatch>`_
diff --git a/docs/quick_tutorial/routing/development.ini b/docs/quick_tutorial/routing/development.ini
index 7066668bf..78d7479e7 100644
--- a/docs/quick_tutorial/routing/development.ini
+++ b/docs/quick_tutorial/routing/development.ini
@@ -5,5 +5,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/routing/setup.py b/docs/quick_tutorial/routing/setup.py
index 2221b72e9..aefa352d4 100644
--- a/docs/quick_tutorial/routing/setup.py
+++ b/docs/quick_tutorial/routing/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon'
]
diff --git a/docs/quick_tutorial/sessions/development.ini b/docs/quick_tutorial/sessions/development.ini
index 7066668bf..78d7479e7 100644
--- a/docs/quick_tutorial/sessions/development.ini
+++ b/docs/quick_tutorial/sessions/development.ini
@@ -5,5 +5,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/sessions/setup.py b/docs/quick_tutorial/sessions/setup.py
index 2221b72e9..aefa352d4 100644
--- a/docs/quick_tutorial/sessions/setup.py
+++ b/docs/quick_tutorial/sessions/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon'
]
diff --git a/docs/quick_tutorial/static_assets.rst b/docs/quick_tutorial/static_assets.rst
index b8482492d..81a01061a 100644
--- a/docs/quick_tutorial/static_assets.rst
+++ b/docs/quick_tutorial/static_assets.rst
@@ -43,13 +43,20 @@ Steps
.. literalinclude:: static_assets/tutorial/static/app.css
:language: css
-#. Make sure we haven't broken any existing code by running the tests:
+#. We add a functional test that asserts that the newly added static file is delivered:
+
+ .. literalinclude:: static_assets/tutorial/tests.py
+ :language: python
+ :pyobject: TutorialFunctionalTests.test_css
+ :lineno-match:
+
+#. Now run the tests:
.. code-block:: bash
$ $VENV/bin/py.test tutorial/tests.py -q
....
- 4 passed in 0.50 seconds
+ 5 passed in 0.50 seconds
#. Run your Pyramid application with:
diff --git a/docs/quick_tutorial/static_assets/development.ini b/docs/quick_tutorial/static_assets/development.ini
index 7066668bf..78d7479e7 100644
--- a/docs/quick_tutorial/static_assets/development.ini
+++ b/docs/quick_tutorial/static_assets/development.ini
@@ -5,5 +5,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/static_assets/setup.py b/docs/quick_tutorial/static_assets/setup.py
index 2221b72e9..aefa352d4 100644
--- a/docs/quick_tutorial/static_assets/setup.py
+++ b/docs/quick_tutorial/static_assets/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon'
]
diff --git a/docs/quick_tutorial/static_assets/tutorial/tests.py b/docs/quick_tutorial/static_assets/tutorial/tests.py
index 4381235ec..b560ddf82 100644
--- a/docs/quick_tutorial/static_assets/tutorial/tests.py
+++ b/docs/quick_tutorial/static_assets/tutorial/tests.py
@@ -42,3 +42,7 @@ class TutorialFunctionalTests(unittest.TestCase):
def test_hello(self):
res = self.testapp.get('/howdy', status=200)
self.assertIn(b'<h1>Hi Hello View', res.body)
+
+ def test_css(self):
+ res = self.testapp.get('/static/app.css', status=200)
+ self.assertIn(b'body', res.body)
diff --git a/docs/quick_tutorial/templating/development.ini b/docs/quick_tutorial/templating/development.ini
index 7066668bf..78d7479e7 100644
--- a/docs/quick_tutorial/templating/development.ini
+++ b/docs/quick_tutorial/templating/development.ini
@@ -5,5 +5,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/templating/setup.py b/docs/quick_tutorial/templating/setup.py
index 0b71b73e6..d1910178e 100644
--- a/docs/quick_tutorial/templating/setup.py
+++ b/docs/quick_tutorial/templating/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon',
]
diff --git a/docs/quick_tutorial/unit_testing.rst b/docs/quick_tutorial/unit_testing.rst
index 7c85d5289..002c62fde 100644
--- a/docs/quick_tutorial/unit_testing.rst
+++ b/docs/quick_tutorial/unit_testing.rst
@@ -29,7 +29,7 @@ broken the code. As you're writing your code, you might find this more
convenient than changing to your browser constantly and clicking reload.
We'll also leave discussion of `pytest-cov
-<http://pytest-cov.readthedocs.org/en/latest/>`_ for another section.
+<http://pytest-cov.readthedocs.io/en/latest/>`_ for another section.
Objectives
diff --git a/docs/quick_tutorial/unit_testing/development.ini b/docs/quick_tutorial/unit_testing/development.ini
index 17b479011..58d23cff7 100644
--- a/docs/quick_tutorial/unit_testing/development.ini
+++ b/docs/quick_tutorial/unit_testing/development.ini
@@ -4,5 +4,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/unit_testing/setup.py b/docs/quick_tutorial/unit_testing/setup.py
index 9997984d3..a93cf6a73 100644
--- a/docs/quick_tutorial/unit_testing/setup.py
+++ b/docs/quick_tutorial/unit_testing/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
]
setup(name='tutorial',
diff --git a/docs/quick_tutorial/view_classes.rst b/docs/quick_tutorial/view_classes.rst
index 05d97a9b1..49cdcddcc 100644
--- a/docs/quick_tutorial/view_classes.rst
+++ b/docs/quick_tutorial/view_classes.rst
@@ -12,7 +12,7 @@ Background
==========
So far our views have been simple, free-standing functions. Many times your
-views are related to one another. They may be different ways to look at or work
+views are related to one another. They may consist of different ways to look at or work
on the same data, or be a REST API that handles multiple operations. Grouping
these views together as a :ref:`view class <class_as_view>` makes sense:
diff --git a/docs/quick_tutorial/view_classes/development.ini b/docs/quick_tutorial/view_classes/development.ini
index 7066668bf..78d7479e7 100644
--- a/docs/quick_tutorial/view_classes/development.ini
+++ b/docs/quick_tutorial/view_classes/development.ini
@@ -5,5 +5,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/view_classes/setup.py b/docs/quick_tutorial/view_classes/setup.py
index 2221b72e9..aefa352d4 100644
--- a/docs/quick_tutorial/view_classes/setup.py
+++ b/docs/quick_tutorial/view_classes/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
'pyramid_chameleon'
]
diff --git a/docs/quick_tutorial/views/development.ini b/docs/quick_tutorial/views/development.ini
index 17b479011..58d23cff7 100644
--- a/docs/quick_tutorial/views/development.ini
+++ b/docs/quick_tutorial/views/development.ini
@@ -4,5 +4,5 @@ pyramid.includes =
pyramid_debugtoolbar
[server:main]
-use = egg:pyramid#wsgiref
-port = 6543
+use = egg:waitress#main
+listen = localhost:6543
diff --git a/docs/quick_tutorial/views/setup.py b/docs/quick_tutorial/views/setup.py
index 9997984d3..a93cf6a73 100644
--- a/docs/quick_tutorial/views/setup.py
+++ b/docs/quick_tutorial/views/setup.py
@@ -2,6 +2,7 @@ from setuptools import setup
requires = [
'pyramid',
+ 'waitress',
]
setup(name='tutorial',
diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst
index 0c3b58bac..a409284cc 100644
--- a/docs/tutorials/modwsgi/index.rst
+++ b/docs/tutorials/modwsgi/index.rst
@@ -39,9 +39,21 @@ specific path information for commands and files.
.. code-block:: bash
$ cd ~
- $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter
- project_name [Pyramid Scaffold]: myproject
- repo_name [scaffold]: myproject
+ $ cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master
+
+ If prompted for the first item, accept the default ``yes`` by hitting return.
+
+ .. code-block:: text
+
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: myproject
+ repo_name [myproject]: myproject
+ Select template_language:
+ 1 - jinja2
+ 2 - chameleon
+ 3 - mako
+ Choose from 1, 2, 3 [1]: 1
#. Create a :term:`virtual environment` which we'll use to install our
application. It is important to use the same base Python interpreter
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index d580e7816..9a8313748 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -49,7 +49,7 @@ Open ``setup.py`` and edit it to look like the following:
.. literalinclude:: src/authorization/setup.py
:linenos:
- :emphasize-lines: 21
+ :emphasize-lines: 23
:language: python
Only the highlighted line needs to be added.
@@ -155,9 +155,9 @@ statements:
Now add those policies to the configuration:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 18-23
+ :lines: 18-25
:lineno-match:
- :emphasize-lines: 1-3,5-6
+ :emphasize-lines: 2-4,6-7
:language: python
Only the highlighted lines need to be added.
@@ -233,7 +233,7 @@ Add the following import statements to the head of
.. literalinclude:: src/authorization/tutorial/views.py
:lines: 6-17
- :emphasize-lines: 1-14
+ :emphasize-lines: 1-12
:language: python
All the highlighted lines need to be added or edited.
@@ -327,7 +327,7 @@ Our ``tutorial/__init__.py`` will look like this when we're done:
.. literalinclude:: src/authorization/tutorial/__init__.py
:linenos:
- :emphasize-lines: 4-5,8,18-20,22-23
+ :emphasize-lines: 4-5,8,19-21,23-24
:language: python
Only the highlighted lines need to be added or edited.
diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst
index d00eab956..b6363088b 100644
--- a/docs/tutorials/wiki/basiclayout.rst
+++ b/docs/tutorials/wiki/basiclayout.rst
@@ -37,18 +37,22 @@ Open ``tutorial/__init__.py``. It should already contain the following:
#. *Line 11*. ``__init__.py`` defines a function named ``main``.
-#. *Line 14*. We construct a :term:`Configurator` with a root
- factory and the settings keywords parsed by :term:`PasteDeploy`. The root
- factory is named ``root_factory``.
+#. *Line 14*. Use an explicit transaction manager for apps so that they do not implicitly create new transactions when touching the manager outside of the ``pyramid_tm`` lifecycle.
-#. *Line 15*. Include support for the :term:`Chameleon` template rendering
+#. *Line 15*. Construct a :term:`Configurator` as a :term:`context manager` with the settings keyword parsed by :term:`PasteDeploy`.
+
+#. *Line 16*. Include support for the :term:`Chameleon` template rendering
bindings, allowing us to use the ``.pt`` templates.
-#. *Line 16*. Include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction <https://pypi.python.org/pypi/transaction>`_ package.
+#. *Line 17*. Include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction <https://pypi.python.org/pypi/transaction>`_ package.
+
+#. *Line 18*. Include support for ``pyramid_retry`` to retry a request when transient exceptions occur.
+
+#. *Line 19*. Include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application.
-#. *Line 17*. Include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application.
+#. *Line 20*. Set a root factory using our function named ``root_factory``.
-#. *Line 18*. Register a "static view", which answers requests whose URL
+#. *Line 21*. Register a "static view", which answers requests whose URL
paths start with ``/static``, using the
:meth:`pyramid.config.Configurator.add_static_view` method. This
statement registers a view that will serve up static assets, such as CSS
@@ -61,7 +65,7 @@ Open ``tutorial/__init__.py``. It should already contain the following:
package. Alternatively the cookiecutter could have used an *absolute* asset
specification as the path (``tutorial:static``).
-#. *Line 19*. Perform a :term:`scan`. A scan will find :term:`configuration
+#. *Line 22*. Perform a :term:`scan`. A scan will find :term:`configuration
decoration`, such as view configuration decorators (e.g., ``@view_config``)
in the source code of the ``tutorial`` package and will take actions based
on these decorators. We don't pass any arguments to
@@ -70,7 +74,7 @@ Open ``tutorial/__init__.py``. It should already contain the following:
The cookiecutter could have equivalently said ``config.scan('tutorial')``, but
it chose to omit the package name argument.
-#. *Line 20*. Use the
+#. *Line 23*. Use the
:meth:`pyramid.config.Configurator.make_wsgi_app` method
to return a :term:`WSGI` application.
@@ -100,7 +104,7 @@ Here is the source for ``models.py``:
By default, set these to ``None`` to indicate that this is the
:term:`root` object.
-#. *Lines 8-14*. ``appmaker`` is used to return the *application
+#. *Lines 8-12*. ``appmaker`` is used to return the *application
root* object. It is called on *every request* to the
:app:`Pyramid` application. It also performs bootstrapping by
*creating* an application root (inside the ZODB root object) if one
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index 442d5ed18..f4ca9b8d7 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -47,7 +47,7 @@ Open ``setup.py`` and edit it to look like the following:
.. literalinclude:: src/views/setup.py
:linenos:
- :emphasize-lines: 20
+ :emphasize-lines: 22
:language: python
Only the highlighted line needs to be added.
diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst
index c735bdf9d..f87d58b90 100644
--- a/docs/tutorials/wiki/installation.rst
+++ b/docs/tutorials/wiki/installation.rst
@@ -31,7 +31,7 @@ On UNIX
.. code-block:: bash
$ cd ~
- $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-zodb
+ $ cookiecutter gh:Pylons/pyramid-cookiecutter-zodb --checkout master
On Windows
^^^^^^^^^^
@@ -39,17 +39,18 @@ On Windows
.. code-block:: doscon
c:\> cd \
- c:\> cookiecutter https://github.com/Pylons/pyramid-cookiecutter-zodb
+ c:\> cookiecutter gh:Pylons/pyramid-cookiecutter-zodb --checkout master
On all operating systems
^^^^^^^^^^^^^^^^^^^^^^^^
If prompted for the first item, accept the default ``yes`` by hitting return.
-#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-zodb before. Is it
- okay to delete and re-clone it? [yes]:``
-#. ``project_name [Pyramid Scaffold]: myproj``
-#. ``repo_name [scaffold]: tutorial``
+.. code-block:: text
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-zodb before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: myproj
+ repo_name [myproj]: tutorial
Change directory into your newly created project
------------------------------------------------
@@ -179,12 +180,12 @@ Testing requirements are defined in our project's ``setup.py`` file, in the ``te
.. literalinclude:: src/installation/setup.py
:language: python
:lineno-match:
- :lines: 22-26
+ :lines: 24-28
.. literalinclude:: src/installation/setup.py
:language: python
:lineno-match:
- :lines: 46-48
+ :lines: 48-50
.. _running_tests:
@@ -378,13 +379,13 @@ assumptions:
tutorial, we'll only be using :term:`traversal` and :term:`ZODB`.
.. _pyramid_chameleon:
- http://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/
+ https://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/
.. _pyramid_tm:
- http://docs.pylonsproject.org/projects/pyramid-tm/en/latest/
+ https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/
.. _pyramid_zodbconn:
- http://docs.pylonsproject.org/projects/pyramid-zodbconn/en/latest/
+ https://docs.pylonsproject.org/projects/pyramid-zodbconn/en/latest/
.. _transaction:
- http://zodb.readthedocs.org/en/latest/transactions.html
+ https://zodb.readthedocs.io/en/latest/transactions.html
diff --git a/docs/tutorials/wiki/src/authorization/README.txt b/docs/tutorials/wiki/src/authorization/README.txt
index bd67221cc..5ec53bf9d 100644
--- a/docs/tutorials/wiki/src/authorization/README.txt
+++ b/docs/tutorials/wiki/src/authorization/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini
index 82c8cf3a1..fec08941d 100644
--- a/docs/tutorials/wiki/src/authorization/development.ini
+++ b/docs/tutorials/wiki/src/authorization/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+retry.attempts = 3
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -26,11 +28,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/authorization/production.ini b/docs/tutorials/wiki/src/authorization/production.ini
index 60b6fe253..f2fa8d6d5 100644
--- a/docs/tutorials/wiki/src/authorization/production.ini
+++ b/docs/tutorials/wiki/src/authorization/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+retry.attempts = 3
+
###
# wsgi server configuration
###
@@ -24,7 +26,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py
index 4a9f041e3..3f0b1317c 100644
--- a/docs/tutorials/wiki/src/authorization/setup.py
+++ b/docs/tutorials/wiki/src/authorization/setup.py
@@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
- 'pyramid',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
index 8af2ee5c0..58635ea74 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
@@ -15,15 +15,18 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
authn_policy = AuthTktAuthenticationPolicy(
'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
- config = Configurator(root_factory=root_factory, settings=settings)
- config.set_authentication_policy(authn_policy)
- config.set_authorization_policy(authz_policy)
- config.include('pyramid_chameleon')
- config.include('pyramid_tm')
- config.include('pyramid_zodbconn')
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.scan()
- return config.make_wsgi_app()
+ with Configurator(settings=settings) as config:
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
+ config.include('pyramid_chameleon')
+ config.include('pyramid_tm')
+ config.include('pyramid_retry')
+ config.include('pyramid_zodbconn')
+ config.set_root_factory(root_factory)
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/models.py b/docs/tutorials/wiki/src/authorization/tutorial/models.py
index 38fdd2dfc..ebd70e912 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/models.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/models.py
@@ -24,6 +24,4 @@ def appmaker(zodb_root):
frontpage.__name__ = 'FrontPage'
frontpage.__parent__ = app_root
zodb_root['app_root'] = app_root
- import transaction
- transaction.commit()
return zodb_root['app_root']
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
index 19adc5932..a14d1801d 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
@@ -12,15 +12,15 @@
TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -67,7 +67,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
index 02f7038fe..23a381a37 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
@@ -12,15 +12,15 @@
TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -69,7 +69,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
index 17a715b50..f3c987c53 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
@@ -12,15 +12,15 @@
TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -67,7 +67,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/basiclayout/README.txt b/docs/tutorials/wiki/src/basiclayout/README.txt
index bd67221cc..8a56d14af 100644
--- a/docs/tutorials/wiki/src/basiclayout/README.txt
+++ b/docs/tutorials/wiki/src/basiclayout/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
@@ -14,7 +14,7 @@ Getting Started
- Upgrade packaging tools.
- env/bin/pip install --upgrade pip setuptools wheel
+ env/bin/pip install --upgrade pip setuptools
- Install the project in editable mode with its testing requirements.
diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini
index 82c8cf3a1..fec08941d 100644
--- a/docs/tutorials/wiki/src/basiclayout/development.ini
+++ b/docs/tutorials/wiki/src/basiclayout/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+retry.attempts = 3
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -26,11 +28,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/basiclayout/production.ini b/docs/tutorials/wiki/src/basiclayout/production.ini
index 60b6fe253..f2fa8d6d5 100644
--- a/docs/tutorials/wiki/src/basiclayout/production.ini
+++ b/docs/tutorials/wiki/src/basiclayout/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+retry.attempts = 3
+
###
# wsgi server configuration
###
@@ -24,7 +26,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py
index 5d1e9c7b5..d743c984f 100644
--- a/docs/tutorials/wiki/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki/src/basiclayout/setup.py
@@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
- 'pyramid',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
index 728f7ac02..f2b3c9568 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
@@ -11,10 +11,13 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- config = Configurator(root_factory=root_factory, settings=settings)
- config.include('pyramid_chameleon')
- config.include('pyramid_tm')
- config.include('pyramid_zodbconn')
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.scan()
- return config.make_wsgi_app()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
+ with Configurator(settings=settings) as config:
+ config.include('pyramid_chameleon')
+ config.include('pyramid_tm')
+ config.include('pyramid_retry')
+ config.include('pyramid_zodbconn')
+ config.set_root_factory(root_factory)
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki/src/basiclayout/tutorial/models.py
index e5aa3e9f7..aca6a4129 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/models.py
@@ -9,6 +9,4 @@ def appmaker(zodb_root):
if 'app_root' not in zodb_root:
app_root = MyModel()
zodb_root['app_root'] = app_root
- import transaction
- transaction.commit()
return zodb_root['app_root']
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
index 3ac122711..a6736560f 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
@@ -11,15 +11,15 @@
<title>Cookiecutter ZODB project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -43,7 +43,7 @@
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -59,7 +59,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/installation/README.txt b/docs/tutorials/wiki/src/installation/README.txt
index bd67221cc..8a56d14af 100644
--- a/docs/tutorials/wiki/src/installation/README.txt
+++ b/docs/tutorials/wiki/src/installation/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
@@ -14,7 +14,7 @@ Getting Started
- Upgrade packaging tools.
- env/bin/pip install --upgrade pip setuptools wheel
+ env/bin/pip install --upgrade pip setuptools
- Install the project in editable mode with its testing requirements.
diff --git a/docs/tutorials/wiki/src/installation/development.ini b/docs/tutorials/wiki/src/installation/development.ini
index 82c8cf3a1..fec08941d 100644
--- a/docs/tutorials/wiki/src/installation/development.ini
+++ b/docs/tutorials/wiki/src/installation/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+retry.attempts = 3
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -26,11 +28,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/installation/production.ini b/docs/tutorials/wiki/src/installation/production.ini
index 60b6fe253..f2fa8d6d5 100644
--- a/docs/tutorials/wiki/src/installation/production.ini
+++ b/docs/tutorials/wiki/src/installation/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+retry.attempts = 3
+
###
# wsgi server configuration
###
@@ -24,7 +26,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/installation/setup.py b/docs/tutorials/wiki/src/installation/setup.py
index 5d1e9c7b5..d743c984f 100644
--- a/docs/tutorials/wiki/src/installation/setup.py
+++ b/docs/tutorials/wiki/src/installation/setup.py
@@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
- 'pyramid',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
diff --git a/docs/tutorials/wiki/src/installation/tutorial/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
index 728f7ac02..f2b3c9568 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
@@ -11,10 +11,13 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- config = Configurator(root_factory=root_factory, settings=settings)
- config.include('pyramid_chameleon')
- config.include('pyramid_tm')
- config.include('pyramid_zodbconn')
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.scan()
- return config.make_wsgi_app()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
+ with Configurator(settings=settings) as config:
+ config.include('pyramid_chameleon')
+ config.include('pyramid_tm')
+ config.include('pyramid_retry')
+ config.include('pyramid_zodbconn')
+ config.set_root_factory(root_factory)
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/installation/tutorial/models.py b/docs/tutorials/wiki/src/installation/tutorial/models.py
index e5aa3e9f7..aca6a4129 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/models.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/models.py
@@ -9,6 +9,4 @@ def appmaker(zodb_root):
if 'app_root' not in zodb_root:
app_root = MyModel()
zodb_root['app_root'] = app_root
- import transaction
- transaction.commit()
return zodb_root['app_root']
diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
index 3ac122711..a6736560f 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
@@ -11,15 +11,15 @@
<title>Cookiecutter ZODB project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -43,7 +43,7 @@
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -59,7 +59,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/models/README.txt b/docs/tutorials/wiki/src/models/README.txt
index bd67221cc..8a56d14af 100644
--- a/docs/tutorials/wiki/src/models/README.txt
+++ b/docs/tutorials/wiki/src/models/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
@@ -14,7 +14,7 @@ Getting Started
- Upgrade packaging tools.
- env/bin/pip install --upgrade pip setuptools wheel
+ env/bin/pip install --upgrade pip setuptools
- Install the project in editable mode with its testing requirements.
diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini
index 82c8cf3a1..fec08941d 100644
--- a/docs/tutorials/wiki/src/models/development.ini
+++ b/docs/tutorials/wiki/src/models/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+retry.attempts = 3
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -26,11 +28,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/models/production.ini b/docs/tutorials/wiki/src/models/production.ini
index 60b6fe253..f2fa8d6d5 100644
--- a/docs/tutorials/wiki/src/models/production.ini
+++ b/docs/tutorials/wiki/src/models/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+retry.attempts = 3
+
###
# wsgi server configuration
###
@@ -24,7 +26,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py
index 5d1e9c7b5..d743c984f 100644
--- a/docs/tutorials/wiki/src/models/setup.py
+++ b/docs/tutorials/wiki/src/models/setup.py
@@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
- 'pyramid',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py
index 728f7ac02..f2b3c9568 100644
--- a/docs/tutorials/wiki/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py
@@ -11,10 +11,13 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- config = Configurator(root_factory=root_factory, settings=settings)
- config.include('pyramid_chameleon')
- config.include('pyramid_tm')
- config.include('pyramid_zodbconn')
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.scan()
- return config.make_wsgi_app()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
+ with Configurator(settings=settings) as config:
+ config.include('pyramid_chameleon')
+ config.include('pyramid_tm')
+ config.include('pyramid_retry')
+ config.include('pyramid_zodbconn')
+ config.set_root_factory(root_factory)
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/models/tutorial/models.py b/docs/tutorials/wiki/src/models/tutorial/models.py
index aa907aee5..7c6597afa 100644
--- a/docs/tutorials/wiki/src/models/tutorial/models.py
+++ b/docs/tutorials/wiki/src/models/tutorial/models.py
@@ -17,6 +17,4 @@ def appmaker(zodb_root):
frontpage.__name__ = 'FrontPage'
frontpage.__parent__ = app_root
zodb_root['app_root'] = app_root
- import transaction
- transaction.commit()
return zodb_root['app_root']
diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
index 3ac122711..a6736560f 100644
--- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
@@ -11,15 +11,15 @@
<title>Cookiecutter ZODB project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -43,7 +43,7 @@
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -59,7 +59,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/tests/README.txt b/docs/tutorials/wiki/src/tests/README.txt
index bd67221cc..5ec53bf9d 100644
--- a/docs/tutorials/wiki/src/tests/README.txt
+++ b/docs/tutorials/wiki/src/tests/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki/src/tests/development.ini b/docs/tutorials/wiki/src/tests/development.ini
index 82c8cf3a1..fec08941d 100644
--- a/docs/tutorials/wiki/src/tests/development.ini
+++ b/docs/tutorials/wiki/src/tests/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+retry.attempts = 3
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -26,11 +28,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/tests/production.ini b/docs/tutorials/wiki/src/tests/production.ini
index 60b6fe253..f2fa8d6d5 100644
--- a/docs/tutorials/wiki/src/tests/production.ini
+++ b/docs/tutorials/wiki/src/tests/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+retry.attempts = 3
+
###
# wsgi server configuration
###
@@ -24,7 +26,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py
index 4a9f041e3..3f0b1317c 100644
--- a/docs/tutorials/wiki/src/tests/setup.py
+++ b/docs/tutorials/wiki/src/tests/setup.py
@@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
- 'pyramid',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
index 8af2ee5c0..58635ea74 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
@@ -15,15 +15,18 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
authn_policy = AuthTktAuthenticationPolicy(
'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
- config = Configurator(root_factory=root_factory, settings=settings)
- config.set_authentication_policy(authn_policy)
- config.set_authorization_policy(authz_policy)
- config.include('pyramid_chameleon')
- config.include('pyramid_tm')
- config.include('pyramid_zodbconn')
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.scan()
- return config.make_wsgi_app()
+ with Configurator(settings=settings) as config:
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
+ config.include('pyramid_chameleon')
+ config.include('pyramid_tm')
+ config.include('pyramid_retry')
+ config.include('pyramid_zodbconn')
+ config.set_root_factory(root_factory)
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/tests/tutorial/models.py b/docs/tutorials/wiki/src/tests/tutorial/models.py
index 38fdd2dfc..ebd70e912 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/models.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/models.py
@@ -24,6 +24,4 @@ def appmaker(zodb_root):
frontpage.__name__ = 'FrontPage'
frontpage.__parent__ = app_root
zodb_root['app_root'] = app_root
- import transaction
- transaction.commit()
return zodb_root['app_root']
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
index 19adc5932..a14d1801d 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
@@ -12,15 +12,15 @@
TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -67,7 +67,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
index 02f7038fe..23a381a37 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
@@ -12,15 +12,15 @@
TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -69,7 +69,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt
index f8cbe2e2c..2468d3912 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt
@@ -11,15 +11,15 @@
<title>ZODB Scaffold for The Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -41,11 +41,11 @@
<div class="row">
<div class="links">
<ul>
- <li class="current-version">Generated by v1.7</li>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
+ <li class="current-version">Generated by v1.9</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="https://docs.pylonsproject.org/projects/pyramid/en/1.9-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -61,7 +61,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
index 17a715b50..f3c987c53 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
@@ -12,15 +12,15 @@
TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -67,7 +67,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/views/README.txt b/docs/tutorials/wiki/src/views/README.txt
index bd67221cc..5ec53bf9d 100644
--- a/docs/tutorials/wiki/src/views/README.txt
+++ b/docs/tutorials/wiki/src/views/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
diff --git a/docs/tutorials/wiki/src/views/development.ini b/docs/tutorials/wiki/src/views/development.ini
index 82c8cf3a1..fec08941d 100644
--- a/docs/tutorials/wiki/src/views/development.ini
+++ b/docs/tutorials/wiki/src/views/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+retry.attempts = 3
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -26,11 +28,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/views/production.ini b/docs/tutorials/wiki/src/views/production.ini
index 60b6fe253..f2fa8d6d5 100644
--- a/docs/tutorials/wiki/src/views/production.ini
+++ b/docs/tutorials/wiki/src/views/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+retry.attempts = 3
+
###
# wsgi server configuration
###
@@ -24,7 +26,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py
index 598ad8146..bd3d15af1 100644
--- a/docs/tutorials/wiki/src/views/setup.py
+++ b/docs/tutorials/wiki/src/views/setup.py
@@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
- 'pyramid',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py
index 728f7ac02..f2b3c9568 100644
--- a/docs/tutorials/wiki/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py
@@ -11,10 +11,13 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- config = Configurator(root_factory=root_factory, settings=settings)
- config.include('pyramid_chameleon')
- config.include('pyramid_tm')
- config.include('pyramid_zodbconn')
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.scan()
- return config.make_wsgi_app()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
+ with Configurator(settings=settings) as config:
+ config.include('pyramid_chameleon')
+ config.include('pyramid_tm')
+ config.include('pyramid_retry')
+ config.include('pyramid_zodbconn')
+ config.set_root_factory(root_factory)
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/views/tutorial/models.py b/docs/tutorials/wiki/src/views/tutorial/models.py
index aa907aee5..7c6597afa 100644
--- a/docs/tutorials/wiki/src/views/tutorial/models.py
+++ b/docs/tutorials/wiki/src/views/tutorial/models.py
@@ -17,6 +17,4 @@ def appmaker(zodb_root):
frontpage.__name__ = 'FrontPage'
frontpage.__parent__ = app_root
zodb_root['app_root'] = app_root
- import transaction
- transaction.commit()
return zodb_root['app_root']
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt
index b23f45d56..7549aea17 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt
@@ -12,15 +12,15 @@
TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
<body>
@@ -63,7 +63,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
index 5caaef4af..b7a87b20a 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
@@ -12,15 +12,15 @@
TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -64,7 +64,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki2/authentication.rst b/docs/tutorials/wiki2/authentication.rst
index ff59ce70b..85977d1be 100644
--- a/docs/tutorials/wiki2/authentication.rst
+++ b/docs/tutorials/wiki2/authentication.rst
@@ -92,7 +92,7 @@ Our authentication policy is expecting a new setting, ``auth.secret``. Open
the file ``development.ini`` and add the highlighted line below:
.. literalinclude:: src/authentication/development.ini
- :lines: 17-19
+ :lines: 19-21
:emphasize-lines: 3
:lineno-match:
:language: ini
@@ -101,7 +101,7 @@ Finally, best practices tell us to use a different secret for production, so
open ``production.ini`` and add a different secret:
.. literalinclude:: src/authentication/production.ini
- :lines: 15-17
+ :lines: 17-19
:emphasize-lines: 3
:lineno-match:
:language: ini
diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst
index fd323fcfc..7076d03bf 100644
--- a/docs/tutorials/wiki2/installation.rst
+++ b/docs/tutorials/wiki2/installation.rst
@@ -43,7 +43,7 @@ On UNIX
.. code-block:: bash
$ cd ~
- $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy
+ $ cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy --checkout master
On Windows
^^^^^^^^^^
@@ -51,17 +51,18 @@ On Windows
.. code-block:: doscon
c:\> cd \
- c:\> cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy
+ c:\> cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy --checkout master
On all operating systems
^^^^^^^^^^^^^^^^^^^^^^^^
If prompted for the first item, accept the default ``yes`` by hitting return.
-#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. Is it
- okay to delete and re-clone it? [yes]:``
-#. ``project_name [Pyramid Scaffold]: myproj``
-#. ``repo_name [scaffold]: tutorial``
+.. code-block:: text
+ You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before.
+ Is it okay to delete and re-clone it? [yes]: yes
+ project_name [Pyramid Scaffold]: myproj
+ repo_name [myproj]: tutorial
Change directory into your newly created project
------------------------------------------------
@@ -189,12 +190,12 @@ Testing requirements are defined in our project's ``setup.py`` file, in the ``te
.. literalinclude:: src/installation/setup.py
:language: python
:lineno-match:
- :lines: 22-26
+ :lines: 24-28
.. literalinclude:: src/installation/setup.py
:language: python
:lineno-match:
- :lines: 46-48
+ :lines: 48-50
.. _sql_running_tests:
@@ -461,13 +462,13 @@ assumptions:
tutorial, we'll only be using :term:`URL dispatch` and :term:`SQLAlchemy`.
.. _pyramid_jinja2:
- http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/
+ https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/
.. _pyramid_tm:
- http://docs.pylonsproject.org/projects/pyramid-tm/en/latest/
+ https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/
.. _zope.sqlalchemy:
https://pypi.python.org/pypi/zope.sqlalchemy
.. _transaction:
- http://zodb.readthedocs.org/en/latest/transactions.html
+ https://zodb.readthedocs.io/en/latest/transactions.html
diff --git a/docs/tutorials/wiki2/src/authentication/README.txt b/docs/tutorials/wiki2/src/authentication/README.txt
index 8466fd7b5..7b33da610 100644
--- a/docs/tutorials/wiki2/src/authentication/README.txt
+++ b/docs/tutorials/wiki2/src/authentication/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
@@ -14,7 +14,7 @@ Getting Started
- Upgrade packaging tools.
- env/bin/pip install --upgrade pip setuptools wheel
+ env/bin/pip install --upgrade pip setuptools
- Install the project in editable mode with its testing requirements.
diff --git a/docs/tutorials/wiki2/src/authentication/development.ini b/docs/tutorials/wiki2/src/authentication/development.ini
index 1e08d1bce..cc2a5586e 100644
--- a/docs/tutorials/wiki2/src/authentication/development.ini
+++ b/docs/tutorials/wiki2/src/authentication/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
auth.secret = seekrit
# By default, the toolbar only appears for clients from IP addresses
@@ -28,11 +30,11 @@ auth.secret = seekrit
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/authentication/production.ini b/docs/tutorials/wiki2/src/authentication/production.ini
index 05d60feec..759807abf 100644
--- a/docs/tutorials/wiki2/src/authentication/production.ini
+++ b/docs/tutorials/wiki2/src/authentication/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
auth.secret = real-seekrit
###
@@ -26,7 +28,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/authentication/setup.py b/docs/tutorials/wiki2/src/authentication/setup.py
index cc1aa421c..abc24876d 100644
--- a/docs/tutorials/wiki2/src/authentication/setup.py
+++ b/docs/tutorials/wiki2/src/authentication/setup.py
@@ -11,9 +11,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'bcrypt',
'docutils',
- 'pyramid',
- 'pyramid_jinja2',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_debugtoolbar',
+ 'pyramid_jinja2',
+ 'pyramid_retry',
'pyramid_tm',
'SQLAlchemy',
'transaction',
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py
index 8147052ad..3c9ba8e54 100644
--- a/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py
@@ -58,10 +58,14 @@ def includeme(config):
"""
settings = config.get_settings()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
# use pyramid_tm to hook the transaction lifecycle to the request
config.include('pyramid_tm')
+ # use pyramid_retry to retry a request when transient exceptions occur
+ config.include('pyramid_retry')
+
session_factory = get_session_factory(get_engine(settings))
config.registry['dbsession_factory'] = session_factory
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py b/docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py
index 0682247b5..02285b3ff 100644
--- a/docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py
@@ -5,7 +5,7 @@ from sqlalchemy.schema import MetaData
# providers will autogenerate vastly different names making migrations more
# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
NAMING_CONVENTION = {
- "ix": 'ix_%(column_0_label)s',
+ "ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2
index 44d14304e..9b2dc82fc 100644
--- a/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>{% block subtitle %}{% endblock %}Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -58,7 +58,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
index 1b071434c..2d058d874 100644
--- a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
@@ -1,4 +1,4 @@
-import cgi
+from pyramid.compat import escape
import re
from docutils.core import publish_parts
@@ -32,10 +32,10 @@ def view_page(request):
exists = request.dbsession.query(Page).filter_by(name=word).all()
if exists:
view_url = request.route_url('view_page', pagename=word)
- return '<a href="%s">%s</a>' % (view_url, cgi.escape(word))
+ return '<a href="%s">%s</a>' % (view_url, escape(word))
else:
add_url = request.route_url('add_page', pagename=word)
- return '<a href="%s">%s</a>' % (add_url, cgi.escape(word))
+ return '<a href="%s">%s</a>' % (add_url, escape(word))
content = publish_parts(page.data, writer_name='html')['html_body']
content = wikiwords.sub(add_link, content)
diff --git a/docs/tutorials/wiki2/src/authorization/README.txt b/docs/tutorials/wiki2/src/authorization/README.txt
index 8466fd7b5..7b33da610 100644
--- a/docs/tutorials/wiki2/src/authorization/README.txt
+++ b/docs/tutorials/wiki2/src/authorization/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
@@ -14,7 +14,7 @@ Getting Started
- Upgrade packaging tools.
- env/bin/pip install --upgrade pip setuptools wheel
+ env/bin/pip install --upgrade pip setuptools
- Install the project in editable mode with its testing requirements.
diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini
index 1e08d1bce..cc2a5586e 100644
--- a/docs/tutorials/wiki2/src/authorization/development.ini
+++ b/docs/tutorials/wiki2/src/authorization/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
auth.secret = seekrit
# By default, the toolbar only appears for clients from IP addresses
@@ -28,11 +30,11 @@ auth.secret = seekrit
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/authorization/production.ini b/docs/tutorials/wiki2/src/authorization/production.ini
index 05d60feec..759807abf 100644
--- a/docs/tutorials/wiki2/src/authorization/production.ini
+++ b/docs/tutorials/wiki2/src/authorization/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
auth.secret = real-seekrit
###
@@ -26,7 +28,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py
index cc1aa421c..abc24876d 100644
--- a/docs/tutorials/wiki2/src/authorization/setup.py
+++ b/docs/tutorials/wiki2/src/authorization/setup.py
@@ -11,9 +11,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'bcrypt',
'docutils',
- 'pyramid',
- 'pyramid_jinja2',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_debugtoolbar',
+ 'pyramid_jinja2',
+ 'pyramid_retry',
'pyramid_tm',
'SQLAlchemy',
'transaction',
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py
index 8147052ad..3c9ba8e54 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py
@@ -58,10 +58,14 @@ def includeme(config):
"""
settings = config.get_settings()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
# use pyramid_tm to hook the transaction lifecycle to the request
config.include('pyramid_tm')
+ # use pyramid_retry to retry a request when transient exceptions occur
+ config.include('pyramid_retry')
+
session_factory = get_session_factory(get_engine(settings))
config.registry['dbsession_factory'] = session_factory
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py
index 0682247b5..02285b3ff 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py
@@ -5,7 +5,7 @@ from sqlalchemy.schema import MetaData
# providers will autogenerate vastly different names making migrations more
# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
NAMING_CONVENTION = {
- "ix": 'ix_%(column_0_label)s',
+ "ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2
index 44d14304e..9b2dc82fc 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>{% block subtitle %}{% endblock %}Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -58,7 +58,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
index 9358993ea..65c12ed3b 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
@@ -1,4 +1,4 @@
-import cgi
+from pyramid.compat import escape
import re
from docutils.core import publish_parts
@@ -25,10 +25,10 @@ def view_page(request):
exists = request.dbsession.query(Page).filter_by(name=word).all()
if exists:
view_url = request.route_url('view_page', pagename=word)
- return '<a href="%s">%s</a>' % (view_url, cgi.escape(word))
+ return '<a href="%s">%s</a>' % (view_url, escape(word))
else:
add_url = request.route_url('add_page', pagename=word)
- return '<a href="%s">%s</a>' % (add_url, cgi.escape(word))
+ return '<a href="%s">%s</a>' % (add_url, escape(word))
content = publish_parts(page.data, writer_name='html')['html_body']
content = wikiwords.sub(add_link, content)
diff --git a/docs/tutorials/wiki2/src/basiclayout/README.txt b/docs/tutorials/wiki2/src/basiclayout/README.txt
index 8466fd7b5..7b33da610 100644
--- a/docs/tutorials/wiki2/src/basiclayout/README.txt
+++ b/docs/tutorials/wiki2/src/basiclayout/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
@@ -14,7 +14,7 @@ Getting Started
- Upgrade packaging tools.
- env/bin/pip install --upgrade pip setuptools wheel
+ env/bin/pip install --upgrade pip setuptools
- Install the project in editable mode with its testing requirements.
diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini
index e9f6d8d3f..4a67896b2 100644
--- a/docs/tutorials/wiki2/src/basiclayout/development.ini
+++ b/docs/tutorials/wiki2/src/basiclayout/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -26,11 +28,11 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/basiclayout/production.ini b/docs/tutorials/wiki2/src/basiclayout/production.ini
index c01ad9a7e..a28e47a83 100644
--- a/docs/tutorials/wiki2/src/basiclayout/production.ini
+++ b/docs/tutorials/wiki2/src/basiclayout/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
###
# wsgi server configuration
###
@@ -24,7 +26,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py
index d3992a8f2..9fc5519a5 100644
--- a/docs/tutorials/wiki2/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki2/src/basiclayout/setup.py
@@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
- 'pyramid',
- 'pyramid_jinja2',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_debugtoolbar',
+ 'pyramid_jinja2',
+ 'pyramid_retry',
'pyramid_tm',
'SQLAlchemy',
'transaction',
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
index 5ca037787..d8a273e9e 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
@@ -57,10 +57,14 @@ def includeme(config):
"""
settings = config.get_settings()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
# use pyramid_tm to hook the transaction lifecycle to the request
config.include('pyramid_tm')
+ # use pyramid_retry to retry a request when transient exceptions occur
+ config.include('pyramid_retry')
+
session_factory = get_session_factory(get_engine(settings))
config.registry['dbsession_factory'] = session_factory
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py
index 0682247b5..02285b3ff 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py
@@ -5,7 +5,7 @@ from sqlalchemy.schema import MetaData
# providers will autogenerate vastly different names making migrations more
# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
NAMING_CONVENTION = {
- "ix": 'ix_%(column_0_label)s',
+ "ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2
index 1f658c834..6ce99d08e 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>Cookiecutter Alchemy project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -42,7 +42,7 @@
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -58,7 +58,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2
index 359f55ffa..26d72c0a6 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2
@@ -3,6 +3,6 @@
{% block content %}
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">myproj</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/installation/README.txt b/docs/tutorials/wiki2/src/installation/README.txt
index 8466fd7b5..7b33da610 100644
--- a/docs/tutorials/wiki2/src/installation/README.txt
+++ b/docs/tutorials/wiki2/src/installation/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
@@ -14,7 +14,7 @@ Getting Started
- Upgrade packaging tools.
- env/bin/pip install --upgrade pip setuptools wheel
+ env/bin/pip install --upgrade pip setuptools
- Install the project in editable mode with its testing requirements.
diff --git a/docs/tutorials/wiki2/src/installation/development.ini b/docs/tutorials/wiki2/src/installation/development.ini
index e9f6d8d3f..4a67896b2 100644
--- a/docs/tutorials/wiki2/src/installation/development.ini
+++ b/docs/tutorials/wiki2/src/installation/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -26,11 +28,11 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/installation/production.ini b/docs/tutorials/wiki2/src/installation/production.ini
index c01ad9a7e..a28e47a83 100644
--- a/docs/tutorials/wiki2/src/installation/production.ini
+++ b/docs/tutorials/wiki2/src/installation/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
###
# wsgi server configuration
###
@@ -24,7 +26,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/installation/setup.py b/docs/tutorials/wiki2/src/installation/setup.py
index d3992a8f2..9fc5519a5 100644
--- a/docs/tutorials/wiki2/src/installation/setup.py
+++ b/docs/tutorials/wiki2/src/installation/setup.py
@@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
- 'pyramid',
- 'pyramid_jinja2',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_debugtoolbar',
+ 'pyramid_jinja2',
+ 'pyramid_retry',
'pyramid_tm',
'SQLAlchemy',
'transaction',
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py
index 5ca037787..d8a273e9e 100644
--- a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py
@@ -57,10 +57,14 @@ def includeme(config):
"""
settings = config.get_settings()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
# use pyramid_tm to hook the transaction lifecycle to the request
config.include('pyramid_tm')
+ # use pyramid_retry to retry a request when transient exceptions occur
+ config.include('pyramid_retry')
+
session_factory = get_session_factory(get_engine(settings))
config.registry['dbsession_factory'] = session_factory
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/models/meta.py b/docs/tutorials/wiki2/src/installation/tutorial/models/meta.py
index 0682247b5..02285b3ff 100644
--- a/docs/tutorials/wiki2/src/installation/tutorial/models/meta.py
+++ b/docs/tutorials/wiki2/src/installation/tutorial/models/meta.py
@@ -5,7 +5,7 @@ from sqlalchemy.schema import MetaData
# providers will autogenerate vastly different names making migrations more
# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
NAMING_CONVENTION = {
- "ix": 'ix_%(column_0_label)s',
+ "ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2
index 1f658c834..6ce99d08e 100644
--- a/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>Cookiecutter Alchemy project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -42,7 +42,7 @@
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -58,7 +58,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2
index 359f55ffa..26d72c0a6 100644
--- a/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2
+++ b/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2
@@ -3,6 +3,6 @@
{% block content %}
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">myproj</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/models/README.txt b/docs/tutorials/wiki2/src/models/README.txt
index 8466fd7b5..7b33da610 100644
--- a/docs/tutorials/wiki2/src/models/README.txt
+++ b/docs/tutorials/wiki2/src/models/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
@@ -14,7 +14,7 @@ Getting Started
- Upgrade packaging tools.
- env/bin/pip install --upgrade pip setuptools wheel
+ env/bin/pip install --upgrade pip setuptools
- Install the project in editable mode with its testing requirements.
diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini
index e9f6d8d3f..4a67896b2 100644
--- a/docs/tutorials/wiki2/src/models/development.ini
+++ b/docs/tutorials/wiki2/src/models/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -26,11 +28,11 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini
index c01ad9a7e..a28e47a83 100644
--- a/docs/tutorials/wiki2/src/models/production.ini
+++ b/docs/tutorials/wiki2/src/models/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
###
# wsgi server configuration
###
@@ -24,7 +26,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py
index faf76aa27..c688c6866 100644
--- a/docs/tutorials/wiki2/src/models/setup.py
+++ b/docs/tutorials/wiki2/src/models/setup.py
@@ -10,9 +10,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'bcrypt',
- 'pyramid',
- 'pyramid_jinja2',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_debugtoolbar',
+ 'pyramid_jinja2',
+ 'pyramid_retry',
'pyramid_tm',
'SQLAlchemy',
'transaction',
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py
index 8147052ad..3c9ba8e54 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py
@@ -58,10 +58,14 @@ def includeme(config):
"""
settings = config.get_settings()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
# use pyramid_tm to hook the transaction lifecycle to the request
config.include('pyramid_tm')
+ # use pyramid_retry to retry a request when transient exceptions occur
+ config.include('pyramid_retry')
+
session_factory = get_session_factory(get_engine(settings))
config.registry['dbsession_factory'] = session_factory
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/meta.py b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py
index 0682247b5..02285b3ff 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/models/meta.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py
@@ -5,7 +5,7 @@ from sqlalchemy.schema import MetaData
# providers will autogenerate vastly different names making migrations more
# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
NAMING_CONVENTION = {
- "ix": 'ix_%(column_0_label)s',
+ "ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2
index 1f658c834..6ce99d08e 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>Cookiecutter Alchemy project for the Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -42,7 +42,7 @@
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -58,7 +58,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2
index 359f55ffa..26d72c0a6 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2
+++ b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2
@@ -3,6 +3,6 @@
{% block content %}
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">myproj</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/tests/README.txt b/docs/tutorials/wiki2/src/tests/README.txt
index 8466fd7b5..7b33da610 100644
--- a/docs/tutorials/wiki2/src/tests/README.txt
+++ b/docs/tutorials/wiki2/src/tests/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
@@ -14,7 +14,7 @@ Getting Started
- Upgrade packaging tools.
- env/bin/pip install --upgrade pip setuptools wheel
+ env/bin/pip install --upgrade pip setuptools
- Install the project in editable mode with its testing requirements.
diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini
index 1e08d1bce..cc2a5586e 100644
--- a/docs/tutorials/wiki2/src/tests/development.ini
+++ b/docs/tutorials/wiki2/src/tests/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
auth.secret = seekrit
# By default, the toolbar only appears for clients from IP addresses
@@ -28,11 +30,11 @@ auth.secret = seekrit
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/tests/production.ini b/docs/tutorials/wiki2/src/tests/production.ini
index 05d60feec..759807abf 100644
--- a/docs/tutorials/wiki2/src/tests/production.ini
+++ b/docs/tutorials/wiki2/src/tests/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
auth.secret = real-seekrit
###
@@ -26,7 +28,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py
index cc1aa421c..abc24876d 100644
--- a/docs/tutorials/wiki2/src/tests/setup.py
+++ b/docs/tutorials/wiki2/src/tests/setup.py
@@ -11,9 +11,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'bcrypt',
'docutils',
- 'pyramid',
- 'pyramid_jinja2',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_debugtoolbar',
+ 'pyramid_jinja2',
+ 'pyramid_retry',
'pyramid_tm',
'SQLAlchemy',
'transaction',
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py
index 8147052ad..3c9ba8e54 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py
@@ -58,10 +58,14 @@ def includeme(config):
"""
settings = config.get_settings()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
# use pyramid_tm to hook the transaction lifecycle to the request
config.include('pyramid_tm')
+ # use pyramid_retry to retry a request when transient exceptions occur
+ config.include('pyramid_retry')
+
session_factory = get_session_factory(get_engine(settings))
config.registry['dbsession_factory'] = session_factory
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py b/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py
index 0682247b5..02285b3ff 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py
@@ -5,7 +5,7 @@ from sqlalchemy.schema import MetaData
# providers will autogenerate vastly different names making migrations more
# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
NAMING_CONVENTION = {
- "ix": 'ix_%(column_0_label)s',
+ "ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2
index 44d14304e..9b2dc82fc 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>{% block subtitle %}{% endblock %}Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -58,7 +58,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
index 9358993ea..65c12ed3b 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
@@ -1,4 +1,4 @@
-import cgi
+from pyramid.compat import escape
import re
from docutils.core import publish_parts
@@ -25,10 +25,10 @@ def view_page(request):
exists = request.dbsession.query(Page).filter_by(name=word).all()
if exists:
view_url = request.route_url('view_page', pagename=word)
- return '<a href="%s">%s</a>' % (view_url, cgi.escape(word))
+ return '<a href="%s">%s</a>' % (view_url, escape(word))
else:
add_url = request.route_url('add_page', pagename=word)
- return '<a href="%s">%s</a>' % (add_url, cgi.escape(word))
+ return '<a href="%s">%s</a>' % (add_url, escape(word))
content = publish_parts(page.data, writer_name='html')['html_body']
content = wikiwords.sub(add_link, content)
diff --git a/docs/tutorials/wiki2/src/views/README.txt b/docs/tutorials/wiki2/src/views/README.txt
index 8466fd7b5..7b33da610 100644
--- a/docs/tutorials/wiki2/src/views/README.txt
+++ b/docs/tutorials/wiki2/src/views/README.txt
@@ -1,12 +1,12 @@
myproj
-===============================
+======
Getting Started
---------------
- Change directory into your newly created project.
- cd myproj
+ cd tutorial
- Create a Python virtual environment.
@@ -14,7 +14,7 @@ Getting Started
- Upgrade packaging tools.
- env/bin/pip install --upgrade pip setuptools wheel
+ env/bin/pip install --upgrade pip setuptools
- Install the project in editable mode with its testing requirements.
diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini
index e9f6d8d3f..4a67896b2 100644
--- a/docs/tutorials/wiki2/src/views/development.ini
+++ b/docs/tutorials/wiki2/src/views/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,6 +16,8 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -26,11 +28,11 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/views/production.ini b/docs/tutorials/wiki2/src/views/production.ini
index c01ad9a7e..a28e47a83 100644
--- a/docs/tutorials/wiki2/src/views/production.ini
+++ b/docs/tutorials/wiki2/src/views/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+retry.attempts = 3
+
###
# wsgi server configuration
###
@@ -24,7 +26,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py
index cc1aa421c..abc24876d 100644
--- a/docs/tutorials/wiki2/src/views/setup.py
+++ b/docs/tutorials/wiki2/src/views/setup.py
@@ -11,9 +11,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'bcrypt',
'docutils',
- 'pyramid',
- 'pyramid_jinja2',
+ 'plaster_pastedeploy',
+ 'pyramid >= 1.9a',
'pyramid_debugtoolbar',
+ 'pyramid_jinja2',
+ 'pyramid_retry',
'pyramid_tm',
'SQLAlchemy',
'transaction',
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py
index 8147052ad..3c9ba8e54 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py
@@ -58,10 +58,14 @@ def includeme(config):
"""
settings = config.get_settings()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
# use pyramid_tm to hook the transaction lifecycle to the request
config.include('pyramid_tm')
+ # use pyramid_retry to retry a request when transient exceptions occur
+ config.include('pyramid_retry')
+
session_factory = get_session_factory(get_engine(settings))
config.registry['dbsession_factory'] = session_factory
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/meta.py b/docs/tutorials/wiki2/src/views/tutorial/models/meta.py
index 0682247b5..02285b3ff 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/models/meta.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/models/meta.py
@@ -5,7 +5,7 @@ from sqlalchemy.schema import MetaData
# providers will autogenerate vastly different names making migrations more
# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
NAMING_CONVENTION = {
- "ix": 'ix_%(column_0_label)s',
+ "ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2
index 7575de8a7..5e7dfe894 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2
@@ -11,15 +11,15 @@
<title>{% block subtitle %}{% endblock %}Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Custom styles for this scaffold -->
<link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -49,7 +49,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views/default.py b/docs/tutorials/wiki2/src/views/tutorial/views/default.py
index bb6300b75..3b95e0f59 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/views/default.py
@@ -1,4 +1,4 @@
-import cgi
+from pyramid.compat import escape
import re
from docutils.core import publish_parts
@@ -31,10 +31,10 @@ def view_page(request):
exists = request.dbsession.query(Page).filter_by(name=word).all()
if exists:
view_url = request.route_url('view_page', pagename=word)
- return '<a href="%s">%s</a>' % (view_url, cgi.escape(word))
+ return '<a href="%s">%s</a>' % (view_url, escape(word))
else:
add_url = request.route_url('add_page', pagename=word)
- return '<a href="%s">%s</a>' % (add_url, cgi.escape(word))
+ return '<a href="%s">%s</a>' % (add_url, escape(word))
content = publish_parts(page.data, writer_name='html')['html_body']
content = wikiwords.sub(add_link, content)
diff --git a/docs/tutorials/wiki2/tests.rst b/docs/tutorials/wiki2/tests.rst
index e1f83a755..84b123df1 100644
--- a/docs/tutorials/wiki2/tests.rst
+++ b/docs/tutorials/wiki2/tests.rst
@@ -116,4 +116,4 @@ The expected result should look like the following:
................................
32 passed in 9.90 seconds
-.. _webtest: http://docs.pylonsproject.org/projects/webtest/en/latest/
+.. _webtest: https://docs.pylonsproject.org/projects/webtest/en/latest/
diff --git a/docs/typographical-conventions.rst b/docs/typographical-conventions.rst
index 5efc49682..f128effea 100644
--- a/docs/typographical-conventions.rst
+++ b/docs/typographical-conventions.rst
@@ -35,7 +35,7 @@ Links
Links are presented as follows, and may be clickable.
-`TryPyramid <https://TryPyramid.com>`_
+`TryPyramid <https://trypyramid.com>`_
.. seealso:: See also :ref:`typographical-conventions-cross-references` for other links within the documentation.
diff --git a/docs/whatsnew-1.8.rst b/docs/whatsnew-1.8.rst
index adc60b34b..ff16c1a4b 100644
--- a/docs/whatsnew-1.8.rst
+++ b/docs/whatsnew-1.8.rst
@@ -114,6 +114,13 @@ Minor Feature Additions
later calls to place translation directories at a higher priority then
earlier calls. See https://github.com/Pylons/pyramid/pull/2902
+- Added a new ``callback`` option to
+ :meth:`pyramid.config.Configurator.set_default_csrf_options`` which
+ can be used to determine per-request whether CSRF checking should be enabled
+ to allow for a mix authentication methods. Only cookie-based methods
+ generally require CSRF checking.
+ See https://github.com/Pylons/pyramid/pull/2778
+
Backwards Incompatibilities
---------------------------
diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst
new file mode 100644
index 000000000..17d6126d6
--- /dev/null
+++ b/docs/whatsnew-1.9.rst
@@ -0,0 +1,72 @@
+What's New in Pyramid 1.9
+=========================
+
+This article explains the new features in :app:`Pyramid` version 1.9 as compared to its predecessor, :app:`Pyramid` 1.8. It also documents backwards incompatibilities between the two versions and deprecations added to :app:`Pyramid` 1.9, as well as software dependency changes and notable documentation additions.
+
+Major Feature Additions
+-----------------------
+
+- The file format used by all ``p*`` command line scripts such as ``pserve`` and ``pshell``, as well as the :func:`pyramid.paster.bootstrap` function is now replaceable thanks to a new dependency on `plaster <https://docs.pylonsproject.org/projects/plaster/en/latest/>`_.
+
+ For now, Pyramid is still shipping with integrated support for the PasteDeploy INI format by depending on the `plaster_pastedeploy <https://github.com/Pylons/plaster_pastedeploy>`_ binding library. This may change in the future so it is recommended for applications to start depending on the appropriate plaster binding for their needs.
+
+ See https://github.com/Pylons/pyramid/pull/2985
+
+- Added an :term:`execution policy` hook to the request pipeline. An execution policy has the ability to control creation and execution of the request objects before they enter the rest of the pipeline. This means for a single request environ the policy may create more than one request object.
+
+ The execution policy can be replaced using the new :meth:`pyramid.config.Configurator.set_execution_policy` config directive.
+
+ The first library to use this feature is `pyramid_retry <https://docs.pylonsproject.org/projects/pyramid-retry/en/latest/>`_.
+
+ Pyramid's default :term:`execution policy` will attempt to handle and render uncaught exceptions. This is a subtle, but fundamental, change indicating that an :term:`exception view` may expect to be called outside of the default ``EXCVIEW`` tween. There are various predicates available to assist in defining valid exception views for various parts of the pipeline. For example, ``pyramid_tm`` defines the ``tm_active=True`` predicate which can be applied to exception views that require access to the default transaction. In general this means that exception views may be expected to cover more possible error conditions, including when exceptions occur from tweens that are placed **OVER** the ``EXCVIEW`` tween. If necessary, when provided a ``response`` object, you may inspect ``request.exception`` or ``request.exc_info`` to determine if the response was generated as the result of an exception.
+ See https://github.com/Pylons/pyramid/pull/2964
+
+- CSRF support has been refactored out of sessions and into its own independent API in the :mod:`pyramid.csrf` module. It supports a pluggable :class:`pyramid.interfaces.ICSRFStoragePolicy` which can be used to define your own mechanism for generating and validating CSRF tokens. By default, Pyramid continues to use the :class:`pyramid.csrf.LegacySessionCSRFStoragePolicy` that uses the ``request.session.get_csrf_token`` and ``request.session.new_csrf_token`` APIs under the hood to preserve compatibility with older Pyramid applications. Two new policies are shipped as well, :class:`pyramid.csrf.SessionCSRFStoragePolicy` and :class:`pyramid.csrf.CookieCSRFStoragePolicy` which will store the CSRF tokens in the session and in a standalone cookie, respectively. The storage policy can be changed by using the new :meth:`pyramid.config.Configurator.set_csrf_storage_policy` config directive.
+
+ CSRF tokens should be used via the new :func:`pyramid.csrf.get_csrf_token`, :func:`pyramid.csrf.new_csrf_token` and :func:`pyramid.csrf.check_csrf_token` APIs in order to continue working if the storage policy is changed. Also, the :func:`pyramid.csrf.get_csrf_token` function is now injected into templates to be used conveniently in UI code.
+
+ See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019
+
+Minor Feature Additions
+-----------------------
+
+- Support an ``open_url`` config setting in the ``pserve`` section of the config file. This url is used to open a web browser when ``pserve --browser`` is invoked. When this setting is unavailable the ``pserve`` script will attempt to guess the port the server is using from the ``server:<server_name>`` section of the config file but there is no requirement that the server is being run in this format so it may fail. See https://github.com/Pylons/pyramid/pull/2984
+
+- The :class:`pyramid.config.Configurator` can now be used as a context manager which will automatically push/pop threadlocals (similar to :meth:`pyramid.config.Configurator.begin` and :meth:`pyramid.config.Configurator.end`). It will also automatically perform a :meth:`pyramid.config.Configurator.commit` at the end and thus it is only recommended to be used at the top-level of your app. See https://github.com/Pylons/pyramid/pull/2874
+
+- The threadlocals are now available inside any function invoked via :meth:`pyramid.config.Configurator.include`. This means the only config-time code that cannot rely on threadlocals is code executed from non-actions inside the main. This can be alleviated by invoking :meth:`pyramid.config.Configurator.begin` and :meth:`pyramid.config.Configurator.end` appropriately or using the new context manager feature of the configurator. See https://github.com/Pylons/pyramid/pull/2989
+
+- The threadlocals are now available inside exception views invoked via :meth:`pyramid.request.Request.invoke_exception_view` even when the ``request`` argument is overridden. See https://github.com/Pylons/pyramid/pull/3060
+
+- When unsupported predicates are supplied to :meth:`pyramid.config.Configurator.add_view`, :meth:`pyramid.config.Configurator.add_route` and :meth:`pyramid.config.Configurator.add_subscriber` a much more helpful error message is output with a guess as to which predicate was intended. See https://github.com/Pylons/pyramid/pull/3054
+
+- Normalize the permission results to a proper class hierarchy. :class:`pyramid.security.ACLAllowed` is now a subclass of :class:`pyramid.security.Allowed` and :class:`pyramid.security.ACLDenied` is now a subclass of :class:`pyramid.security.Denied`. See https://github.com/Pylons/pyramid/pull/3084
+
+- Add a ``quote_via`` argument to :func:`pyramid.encode.urlencode` to follow the stdlib's version and enable custom quoting functions. See https://github.com/Pylons/pyramid/pull/3088
+
+- Support `_query=None` and `_anchor=None` in :meth:`pyramid.request.Request.route_url` as well as ``query=None`` and ``anchor=None`` in :meth:`pyramid.request.Request.resource_url`. Previously this would cause an `?` and a `#`, respectively, in the url with nothing after it. Now the unnecessary parts are dropped from the generated URL. See https://github.com/Pylons/pyramid/pull/3034
+
+Deprecations
+------------
+
+- Pyramid currently depends on ``plaster_pastedeploy`` to simplify the transition to ``plaster`` by maintaining integrated support for INI files. This dependency on ``plaster_pastedeploy`` should be considered subject to Pyramid's deprecation policy and may be removed in the future. Applications should depend on the appropriate plaster binding to satisfy their needs.
+
+- Retrieving CSRF token from the session has been deprecated in favor of equivalent methods in the :mod:`pyramid.csrf` module. The CSRF methods (``ISession.get_csrf_token`` and ``ISession.new_csrf_token``) are no longer required on the :class:`pyramid.interfaces.ISession` interface except when using the default :class:`pyramid.csrf.LegacySessionCSRFStoragePolicy`.
+
+ Also, ``pyramid.session.check_csrf_token`` is now located at :func:`pyramid.csrf.check_csrf_token` and ``pyramid.session.check_csrf_origin`` is moved to :func:`pyramid.csrf.check_csrf_origin`.
+
+ See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019
+
+Backward Incompatibilities
+--------------------------
+
+- ``request.exception`` and ``request.exc_info`` will only be set if the response was generated by the EXCVIEW tween. This is to avoid any confusion where a response was generated elsewhere in the pipeline and not in direct relation to the original exception. If anyone upstream wants to catch and render responses for exceptions they should set ``request.exception`` and ``request.exc_info`` themselves to indicate the exception that was squashed when generating the response.
+
+ Similar behavior occurs with :meth:`pyramid.request.Request.invoke_exception_view` in which the exception properties are set to reflect the exception if a response is successfully generated by the method.
+
+ This is a very minor incompatibility. Most tweens right now would give priority to the raised exception and ignore ``request.exception``. This change just improves and clarifies that bookkeeping by trying to be more clear about the relationship between the response and its squashed exception. See https://github.com/Pylons/pyramid/pull/3029 and https://github.com/Pylons/pyramid/pull/3031
+
+Documentation Enhancements
+--------------------------
+
+- Added the :term:`execution policy` to the routing diagram in :ref:`router_chapter`. See https://github.com/Pylons/pyramid/pull/2993
diff --git a/pyramid/authentication.py b/pyramid/authentication.py
index 03b204e1a..445d6fcd2 100644
--- a/pyramid/authentication.py
+++ b/pyramid/authentication.py
@@ -1084,10 +1084,12 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy):
from pyramid.view import forbidden_view_config
@forbidden_view_config()
- def basic_challenge(request):
- response = HTTPUnauthorized()
- response.headers.update(forget(request))
- return response
+ def forbidden_view(request):
+ if request.authenticated_userid is None:
+ response = HTTPUnauthorized()
+ response.headers.update(forget(request))
+ return response
+ return HTTPForbidden()
"""
def __init__(self, check, realm='Realm', debug=False):
self.check = check
diff --git a/pyramid/compat.py b/pyramid/compat.py
index 8c8645460..a7f9c1287 100644
--- a/pyramid/compat.py
+++ b/pyramid/compat.py
@@ -17,6 +17,11 @@ try:
except ImportError: # pragma: no cover
import pickle
+try:
+ from functools import lru_cache
+except ImportError:
+ from repoze.lru import lru_cache
+
# PY3 is left as bw-compat but PY2 should be used for most checks.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 304d3a85e..a34f0b4db 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -110,6 +110,17 @@ class Configurator(
A Configurator is used to configure a :app:`Pyramid`
:term:`application registry`.
+ The Configurator lifecycle can be managed by using a context manager to
+ automatically handle calling :meth:`pyramid.config.Configurator.begin` and
+ :meth:`pyramid.config.Configurator.end` as well as
+ :meth:`pyramid.config.Configurator.commit`.
+
+ .. code-block:: python
+
+ with Configurator(settings=settings) as config:
+ config.add_route('home', '/')
+ app = config.make_wsgi_app()
+
If the ``registry`` argument is not ``None``, it must
be an instance of the :class:`pyramid.registry.Registry` class
representing the registry to configure. If ``registry`` is ``None``, the
@@ -265,6 +276,11 @@ class Configurator(
.. versionadded:: 1.6
The ``root_package`` argument.
The ``response_factory`` argument.
+
+ .. versionadded:: 1.9
+ The ability to use the configurator as a context manager with the
+ ``with``-statement to make threadlocal configuration available for
+ further configuration with an implicit commit.
"""
manager = manager # for testing injection
venusian = venusian # for testing injection
@@ -380,6 +396,7 @@ class Configurator(
self.add_default_view_derivers()
self.add_default_route_predicates()
self.add_default_tweens()
+ self.add_default_security()
if exceptionresponse_view is not None:
exceptionresponse_view = self.maybe_dotted(exceptionresponse_view)
@@ -646,12 +663,22 @@ class Configurator(
_ctx = action_state # bw compat
def commit(self):
- """ Commit any pending configuration actions. If a configuration
+ """
+ Commit any pending configuration actions. If a configuration
conflict is detected in the pending configuration actions, this method
will raise a :exc:`ConfigurationConflictError`; within the traceback
of this error will be information about the source of the conflict,
usually including file names and line numbers of the cause of the
- configuration conflicts."""
+ configuration conflicts.
+
+ .. warning::
+ You should think very carefully before manually invoking
+ ``commit()``. Especially not as part of any reusable configuration
+ methods. Normally it should only be done by an application author at
+ the end of configuration in order to override certain aspects of an
+ addon.
+
+ """
self.begin()
try:
self.action_state.execute_actions(introspector=self.introspector)
@@ -753,6 +780,11 @@ class Configurator(
.. versionadded:: 1.2
The ``route_prefix`` parameter.
+ .. versionchanged:: 1.9
+ The included function is wrapped with a call to
+ :meth:`pyramid.config.Configurator.begin` and
+ :meth:`pyramid.config.Configurator.end` while it is executed.
+
"""
# """ <-- emacs
@@ -802,7 +834,11 @@ class Configurator(
)
configurator.basepath = os.path.dirname(sourcefile)
configurator.includepath = self.includepath + (spec,)
- c(configurator)
+ self.begin()
+ try:
+ c(configurator)
+ finally:
+ self.end()
def add_directive(self, name, directive, action_wrap=True):
"""
@@ -924,6 +960,16 @@ class Configurator(
"""
return self.manager.pop()
+ def __enter__(self):
+ self.begin()
+ return self
+
+ def __exit__(self, exc_type, exc_value, exc_traceback):
+ self.end()
+
+ if exc_value is None:
+ self.commit()
+
# this is *not* an action method (uses caller_package)
def scan(self, package=None, categories=None, onerror=None, ignore=None,
**kw):
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py
index f0b6252ae..202c3ef61 100644
--- a/pyramid/config/factories.py
+++ b/pyramid/config/factories.py
@@ -3,6 +3,7 @@ from zope.interface import implementer
from pyramid.interfaces import (
IDefaultRootFactory,
+ IExecutionPolicy,
IRequestFactory,
IResponseFactory,
IRequestExtensions,
@@ -10,6 +11,7 @@ from pyramid.interfaces import (
ISessionFactory,
)
+from pyramid.router import default_execution_policy
from pyramid.traversal import DefaultRootFactory
from pyramid.util import (
@@ -142,9 +144,10 @@ class FactoriesConfiguratorMixin(object):
When adding a property to the request, ``callable`` can either
be a callable that accepts the request as its single positional
- parameter, or it can be a property descriptor. If ``name`` is
- ``None``, the name of the property will be computed from the
- name of the ``callable``.
+ parameter, or it can be a property descriptor. If ``callable`` is
+ a property descriptor, it has to be an instance of a class which is
+ a subclass of ``property``. If ``name`` is ``None``, the name of
+ the property will be computed from the name of the ``callable``.
If the ``callable`` is a property descriptor a ``ValueError``
will be raised if ``name`` is ``None`` or ``reify`` is ``True``.
@@ -231,6 +234,29 @@ class FactoriesConfiguratorMixin(object):
'set_request_propery() is deprecated as of Pyramid 1.5; use '
'add_request_method() with the property=True argument instead')
+ @action_method
+ def set_execution_policy(self, policy):
+ """
+ Override the :app:`Pyramid` :term:`execution policy` in the
+ current configuration. The ``policy`` argument must be an instance
+ of an :class:`pyramid.interfaces.IExecutionPolicy` or a
+ :term:`dotted Python name` that points at an instance of an
+ execution policy.
+
+ """
+ policy = self.maybe_dotted(policy)
+ if policy is None:
+ policy = default_execution_policy
+
+ def register():
+ self.registry.registerUtility(policy, IExecutionPolicy)
+
+ intr = self.introspectable('execution policy', None,
+ self.object_description(policy),
+ 'execution policy')
+ intr['policy'] = policy
+ self.action(IExecutionPolicy, register, introspectables=(intr,))
+
@implementer(IRequestExtensions)
class _RequestExtensions(object):
diff --git a/pyramid/config/security.py b/pyramid/config/security.py
index 33593376b..20b816161 100644
--- a/pyramid/config/security.py
+++ b/pyramid/config/security.py
@@ -3,17 +3,24 @@ from zope.interface import implementer
from pyramid.interfaces import (
IAuthorizationPolicy,
IAuthenticationPolicy,
+ ICSRFStoragePolicy,
IDefaultCSRFOptions,
IDefaultPermission,
PHASE1_CONFIG,
PHASE2_CONFIG,
)
+from pyramid.csrf import LegacySessionCSRFStoragePolicy
from pyramid.exceptions import ConfigurationError
from pyramid.util import action_method
from pyramid.util import as_sorted_tuple
+
class SecurityConfiguratorMixin(object):
+
+ def add_default_security(self):
+ self.set_csrf_storage_policy(LegacySessionCSRFStoragePolicy())
+
@action_method
def set_authentication_policy(self, policy):
""" Override the :app:`Pyramid` :term:`authentication policy` in the
@@ -200,9 +207,14 @@ class SecurityConfiguratorMixin(object):
are not subject to CSRF attacks. For example, if a request is
authenticated using the ``Authorization`` header instead of a cookie,
this may return ``False`` for that request so that clients do not
- need to send the ``X-CSRF-Token` header. The callback is only tested
+ need to send the ``X-CSRF-Token`` header. The callback is only tested
for non-safe methods as defined by ``safe_methods``.
+ .. versionadded:: 1.7
+
+ .. versionchanged:: 1.8
+ Added the ``callback`` option.
+
"""
options = DefaultCSRFOptions(
require_csrf, token, header, safe_methods, callback,
@@ -218,9 +230,31 @@ class SecurityConfiguratorMixin(object):
intr['header'] = header
intr['safe_methods'] = as_sorted_tuple(safe_methods)
intr['callback'] = callback
+
self.action(IDefaultCSRFOptions, register, order=PHASE1_CONFIG,
introspectables=(intr,))
+ @action_method
+ def set_csrf_storage_policy(self, policy):
+ """
+ Set the :term:`CSRF storage policy` used by subsequent view
+ registrations.
+
+ ``policy`` is a class that implements the
+ :meth:`pyramid.interfaces.ICSRFStoragePolicy` interface and defines
+ how to generate and persist CSRF tokens.
+
+ """
+ def register():
+ self.registry.registerUtility(policy, ICSRFStoragePolicy)
+ intr = self.introspectable('csrf storage policy',
+ None,
+ policy,
+ 'csrf storage policy')
+ intr['policy'] = policy
+ self.action(ICSRFStoragePolicy, register, introspectables=(intr,))
+
+
@implementer(IDefaultCSRFOptions)
class DefaultCSRFOptions(object):
def __init__(self, require_csrf, token, header, safe_methods, callback):
diff --git a/pyramid/config/settings.py b/pyramid/config/settings.py
index 26eb48951..11a1f7d8c 100644
--- a/pyramid/config/settings.py
+++ b/pyramid/config/settings.py
@@ -56,6 +56,7 @@ def Settings(d=None, _environ_=os.environ, **kw):
keyword args)."""
if d is None:
d = {}
+ d = dict(d)
d.update(**kw)
eget = _environ_.get
@@ -73,7 +74,7 @@ def Settings(d=None, _environ_=os.environ, **kw):
value = eget(env_key, value)
value = type_(value)
d.update({k: value for k in keys})
- def O(settings_key, override_key):
+ def O(settings_key, override_key): # noqa: E743
for key in expand_key(settings_key):
d[key] = d[key] or d[override_key]
diff --git a/pyramid/config/util.py b/pyramid/config/util.py
index 67bba9593..63f06ff9b 100644
--- a/pyramid/config/util.py
+++ b/pyramid/config/util.py
@@ -36,7 +36,7 @@ class not_(object):
config.add_view(
'mypackage.views.my_view',
- route_name='ok',
+ route_name='ok',
request_method=not_('POST')
)
@@ -69,7 +69,7 @@ class Notted(object):
# if the underlying predicate doesnt return a value, it's not really
# a predicate, it's just something pretending to be a predicate,
# so dont update the hash
- if val:
+ if val:
val = '!' + val
return val
@@ -90,7 +90,7 @@ class Notted(object):
# over = before
class PredicateList(object):
-
+
def __init__(self):
self.sorter = TopologicalSorter()
self.last_added = None
@@ -152,7 +152,16 @@ class PredicateList(object):
weights.append(1 << n + 1)
preds.append(pred)
if kw:
- raise ConfigurationError('Unknown predicate values: %r' % (kw,))
+ from difflib import get_close_matches
+ closest = []
+ names = [ name for name, _ in ordered ]
+ for name in kw:
+ closest.extend(get_close_matches(name, names, 3))
+
+ raise ConfigurationError(
+ 'Unknown predicate values: %r (did you mean %s)'
+ % (kw, ','.join(closest))
+ )
# A "order" is computed for the predicate list. An order is
# a scoring.
#
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 65c9da585..e5ebc8e07 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -641,18 +641,22 @@ class ViewsConfiguratorMixin(object):
'check name'. If the value provided is ``True``, ``csrf_token`` will
be used as the check name.
- If CSRF checking is performed, the checked value will be the value
- of ``request.params[check_name]``. This value will be compared
- against the value of ``request.session.get_csrf_token()``, and the
- check will pass if these two values are the same. If the check
- passes, the associated view will be permitted to execute. If the
+ If CSRF checking is performed, the checked value will be the value of
+ ``request.params[check_name]``. This value will be compared against
+ the value of ``policy.get_csrf_token()`` (where ``policy`` is an
+ implementation of :meth:`pyramid.interfaces.ICSRFStoragePolicy`), and the
+ check will pass if these two values are the same. If the check
+ passes, the associated view will be permitted to execute. If the
check fails, the associated view will not be permitted to execute.
- Note that using this feature requires a :term:`session factory` to
- have been configured.
-
.. versionadded:: 1.4a2
+ .. versionchanged:: 1.9
+ This feature requires either a :term:`session factory` to have been
+ configured, or a :term:`CSRF storage policy` other than the default
+ to be in use.
+
+
physical_path
If specified, this value should be a string or a tuple representing
@@ -972,7 +976,7 @@ class ViewsConfiguratorMixin(object):
def register_view(classifier, request_iface, derived_view):
# A multiviews is a set of views which are registered for
# exactly the same context type/request type/name triad. Each
- # consituent view in a multiview differs only by the
+ # constituent view in a multiview differs only by the
# predicates which it possesses.
# To find a previously registered view for a context
@@ -1032,7 +1036,7 @@ class ViewsConfiguratorMixin(object):
# XXX we could try to be more efficient here and register
# a non-secured view for a multiview if none of the
- # multiview's consituent views have a permission
+ # multiview's constituent views have a permission
# associated with them, but this code is getting pretty
# rough already
if is_multiview:
@@ -1946,8 +1950,7 @@ class StaticURLInfo(object):
kw['subpath'] = subpath
return request.route_url(route_name, **kw)
else:
- app_url, scheme, host, port, qs, anchor = \
- parse_url_overrides(kw)
+ app_url, qs, anchor = parse_url_overrides(request, kw)
parsed = url_parse(url)
if not parsed.scheme:
url = urlparse.urlunparse(parsed._replace(
diff --git a/pyramid/csrf.py b/pyramid/csrf.py
new file mode 100644
index 000000000..7c836e5ad
--- /dev/null
+++ b/pyramid/csrf.py
@@ -0,0 +1,332 @@
+import uuid
+
+from webob.cookies import CookieProfile
+from zope.interface import implementer
+
+
+from pyramid.authentication import _SimpleSerializer
+
+from pyramid.compat import (
+ bytes_,
+ urlparse,
+ text_,
+)
+from pyramid.exceptions import (
+ BadCSRFOrigin,
+ BadCSRFToken,
+)
+from pyramid.interfaces import ICSRFStoragePolicy
+from pyramid.settings import aslist
+from pyramid.util import (
+ is_same_domain,
+ strings_differ
+)
+
+
+@implementer(ICSRFStoragePolicy)
+class LegacySessionCSRFStoragePolicy(object):
+ """ A CSRF storage policy that defers control of CSRF storage to the
+ session.
+
+ This policy maintains compatibility with legacy ISession implementations
+ that know how to manage CSRF tokens themselves via
+ ``ISession.new_csrf_token`` and ``ISession.get_csrf_token``.
+
+ Note that using this CSRF implementation requires that
+ a :term:`session factory` is configured.
+
+ .. versionadded:: 1.9
+
+ """
+ def new_csrf_token(self, request):
+ """ Sets a new CSRF token into the session and returns it. """
+ return request.session.new_csrf_token()
+
+ def get_csrf_token(self, request):
+ """ Returns the currently active CSRF token from the session,
+ generating a new one if needed."""
+ return request.session.get_csrf_token()
+
+ def check_csrf_token(self, request, supplied_token):
+ """ Returns ``True`` if the ``supplied_token`` is valid."""
+ expected_token = self.get_csrf_token(request)
+ return not strings_differ(
+ bytes_(expected_token), bytes_(supplied_token))
+
+
+@implementer(ICSRFStoragePolicy)
+class SessionCSRFStoragePolicy(object):
+ """ A CSRF storage policy that persists the CSRF token in the session.
+
+ Note that using this CSRF implementation requires that
+ a :term:`session factory` is configured.
+
+ ``key``
+
+ The session key where the CSRF token will be stored.
+ Default: `_csrft_`.
+
+ .. versionadded:: 1.9
+
+ """
+ _token_factory = staticmethod(lambda: text_(uuid.uuid4().hex))
+
+ def __init__(self, key='_csrft_'):
+ self.key = key
+
+ def new_csrf_token(self, request):
+ """ Sets a new CSRF token into the session and returns it. """
+ token = self._token_factory()
+ request.session[self.key] = token
+ return token
+
+ def get_csrf_token(self, request):
+ """ Returns the currently active CSRF token from the session,
+ generating a new one if needed."""
+ token = request.session.get(self.key, None)
+ if not token:
+ token = self.new_csrf_token(request)
+ return token
+
+ def check_csrf_token(self, request, supplied_token):
+ """ Returns ``True`` if the ``supplied_token`` is valid."""
+ expected_token = self.get_csrf_token(request)
+ return not strings_differ(
+ bytes_(expected_token), bytes_(supplied_token))
+
+
+@implementer(ICSRFStoragePolicy)
+class CookieCSRFStoragePolicy(object):
+ """ An alternative CSRF implementation that stores its information in
+ unauthenticated cookies, known as the 'Double Submit Cookie' method in the
+ `OWASP CSRF guidelines <https://www.owasp.org/index.php/
+ Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#
+ Double_Submit_Cookie>`_. This gives some additional flexibility with
+ regards to scaling as the tokens can be generated and verified by a
+ front-end server.
+
+ .. versionadded:: 1.9
+
+ """
+ _token_factory = staticmethod(lambda: text_(uuid.uuid4().hex))
+
+ def __init__(self, cookie_name='csrf_token', secure=False, httponly=False,
+ domain=None, max_age=None, path='/'):
+ serializer = _SimpleSerializer()
+ self.cookie_profile = CookieProfile(
+ cookie_name=cookie_name,
+ secure=secure,
+ max_age=max_age,
+ httponly=httponly,
+ path=path,
+ domains=[domain],
+ serializer=serializer
+ )
+ self.cookie_name = cookie_name
+
+ def new_csrf_token(self, request):
+ """ Sets a new CSRF token into the request and returns it. """
+ token = self._token_factory()
+ request.cookies[self.cookie_name] = token
+ def set_cookie(request, response):
+ self.cookie_profile.set_cookies(
+ response,
+ token,
+ )
+ request.add_response_callback(set_cookie)
+ return token
+
+ def get_csrf_token(self, request):
+ """ Returns the currently active CSRF token by checking the cookies
+ sent with the current request."""
+ bound_cookies = self.cookie_profile.bind(request)
+ token = bound_cookies.get_value()
+ if not token:
+ token = self.new_csrf_token(request)
+ return token
+
+ def check_csrf_token(self, request, supplied_token):
+ """ Returns ``True`` if the ``supplied_token`` is valid."""
+ expected_token = self.get_csrf_token(request)
+ return not strings_differ(
+ bytes_(expected_token), bytes_(supplied_token))
+
+
+def get_csrf_token(request):
+ """ Get the currently active CSRF token for the request passed, generating
+ a new one using ``new_csrf_token(request)`` if one does not exist. This
+ calls the equivalent method in the chosen CSRF protection implementation.
+
+ .. versionadded :: 1.9
+
+ """
+ registry = request.registry
+ csrf = registry.getUtility(ICSRFStoragePolicy)
+ return csrf.get_csrf_token(request)
+
+
+def new_csrf_token(request):
+ """ Generate a new CSRF token for the request passed and persist it in an
+ implementation defined manner. This calls the equivalent method in the
+ chosen CSRF protection implementation.
+
+ .. versionadded :: 1.9
+
+ """
+ registry = request.registry
+ csrf = registry.getUtility(ICSRFStoragePolicy)
+ return csrf.new_csrf_token(request)
+
+
+def check_csrf_token(request,
+ token='csrf_token',
+ header='X-CSRF-Token',
+ raises=True):
+ """ Check the CSRF token returned by the
+ :class:`pyramid.interfaces.ICSRFStoragePolicy` implementation against the
+ value in ``request.POST.get(token)`` (if a POST request) or
+ ``request.headers.get(header)``. If a ``token`` keyword is not supplied to
+ this function, the string ``csrf_token`` will be used to look up the token
+ in ``request.POST``. If a ``header`` keyword is not supplied to this
+ function, the string ``X-CSRF-Token`` will be used to look up the token in
+ ``request.headers``.
+
+ If the value supplied by post or by header cannot be verified by the
+ :class:`pyramid.interfaces.ICSRFStoragePolicy`, and ``raises`` is
+ ``True``, this function will raise an
+ :exc:`pyramid.exceptions.BadCSRFToken` exception. If the values differ
+ and ``raises`` is ``False``, this function will return ``False``. If the
+ CSRF check is successful, this function will return ``True``
+ unconditionally.
+
+ See :ref:`auto_csrf_checking` for information about how to secure your
+ application automatically against CSRF attacks.
+
+ .. versionadded:: 1.4a2
+
+ .. versionchanged:: 1.7a1
+ A CSRF token passed in the query string of the request is no longer
+ considered valid. It must be passed in either the request body or
+ a header.
+
+ .. versionchanged:: 1.9
+ Moved from :mod:`pyramid.session` to :mod:`pyramid.csrf` and updated
+ to use the configured :class:`pyramid.interfaces.ICSRFStoragePolicy` to
+ verify the CSRF token.
+
+ """
+ supplied_token = ""
+ # We first check the headers for a csrf token, as that is significantly
+ # cheaper than checking the POST body
+ if header is not None:
+ supplied_token = request.headers.get(header, "")
+
+ # If this is a POST/PUT/etc request, then we'll check the body to see if it
+ # has a token. We explicitly use request.POST here because CSRF tokens
+ # should never appear in an URL as doing so is a security issue. We also
+ # explicitly check for request.POST here as we do not support sending form
+ # encoded data over anything but a request.POST.
+ if supplied_token == "" and token is not None:
+ supplied_token = request.POST.get(token, "")
+
+ policy = request.registry.getUtility(ICSRFStoragePolicy)
+ if not policy.check_csrf_token(request, text_(supplied_token)):
+ if raises:
+ raise BadCSRFToken('check_csrf_token(): Invalid token')
+ return False
+ return True
+
+
+def check_csrf_origin(request, trusted_origins=None, raises=True):
+ """
+ Check the ``Origin`` of the request to see if it is a cross site request or
+ not.
+
+ If the value supplied by the ``Origin`` or ``Referer`` header isn't one of the
+ trusted origins and ``raises`` is ``True``, this function will raise a
+ :exc:`pyramid.exceptions.BadCSRFOrigin` exception, but if ``raises`` is
+ ``False``, this function will return ``False`` instead. If the CSRF origin
+ checks are successful this function will return ``True`` unconditionally.
+
+ Additional trusted origins may be added by passing a list of domain (and
+ ports if non-standard like ``['example.com', 'dev.example.com:8080']``) in
+ with the ``trusted_origins`` parameter. If ``trusted_origins`` is ``None``
+ (the default) this list of additional domains will be pulled from the
+ ``pyramid.csrf_trusted_origins`` setting.
+
+ Note that this function will do nothing if ``request.scheme`` is not
+ ``https``.
+
+ .. versionadded:: 1.7
+
+ .. versionchanged:: 1.9
+ Moved from :mod:`pyramid.session` to :mod:`pyramid.csrf`
+
+ """
+ def _fail(reason):
+ if raises:
+ raise BadCSRFOrigin(reason)
+ else:
+ return False
+
+ if request.scheme == "https":
+ # Suppose user visits http://example.com/
+ # An active network attacker (man-in-the-middle, MITM) sends a
+ # POST form that targets https://example.com/detonate-bomb/ and
+ # submits it via JavaScript.
+ #
+ # The attacker will need to provide a CSRF cookie and token, but
+ # that's no problem for a MITM when we cannot make any assumptions
+ # about what kind of session storage is being used. So the MITM can
+ # circumvent the CSRF protection. This is true for any HTTP connection,
+ # but anyone using HTTPS expects better! For this reason, for
+ # https://example.com/ we need additional protection that treats
+ # http://example.com/ as completely untrusted. Under HTTPS,
+ # Barth et al. found that the Referer header is missing for
+ # same-domain requests in only about 0.2% of cases or less, so
+ # we can use strict Referer checking.
+
+ # Determine the origin of this request
+ origin = request.headers.get("Origin")
+ if origin is None:
+ origin = request.referrer
+
+ # Fail if we were not able to locate an origin at all
+ if not origin:
+ return _fail("Origin checking failed - no Origin or Referer.")
+
+ # Parse our origin so we we can extract the required information from
+ # it.
+ originp = urlparse.urlparse(origin)
+
+ # Ensure that our Referer is also secure.
+ if originp.scheme != "https":
+ return _fail(
+ "Referer checking failed - Referer is insecure while host is "
+ "secure."
+ )
+
+ # Determine which origins we trust, which by default will include the
+ # current origin.
+ if trusted_origins is None:
+ trusted_origins = aslist(
+ request.registry.settings.get(
+ "pyramid.csrf_trusted_origins", [])
+ )
+
+ if request.host_port not in set(["80", "443"]):
+ trusted_origins.append("{0.domain}:{0.host_port}".format(request))
+ else:
+ trusted_origins.append(request.domain)
+
+ # Actually check to see if the request's origin matches any of our
+ # trusted origins.
+ if not any(is_same_domain(originp.netloc, host)
+ for host in trusted_origins):
+ reason = (
+ "Referer checking failed - {0} does not match any trusted "
+ "origins."
+ )
+ return _fail(reason.format(origin))
+
+ return True
diff --git a/pyramid/encode.py b/pyramid/encode.py
index 0be0107b3..73ff14e62 100644
--- a/pyramid/encode.py
+++ b/pyramid/encode.py
@@ -14,13 +14,21 @@ def url_quote(val, safe=''): # bw compat api
val = str(val).encode('utf-8')
return _url_quote(val, safe=safe)
-def urlencode(query, doseq=True):
+# bw compat api (dnr)
+def quote_plus(val, safe=''):
+ cls = val.__class__
+ if cls is text_type:
+ val = val.encode('utf-8')
+ elif cls is not binary_type:
+ val = str(val).encode('utf-8')
+ return _quote_plus(val, safe=safe)
+
+def urlencode(query, doseq=True, quote_via=quote_plus):
"""
- An alternate implementation of Python's stdlib `urllib.urlencode
- function <http://docs.python.org/library/urllib.html>`_ which
- accepts unicode keys and values within the ``query``
- dict/sequence; all Unicode keys and values are first converted to
- UTF-8 before being used to compose the query string.
+ An alternate implementation of Python's stdlib
+ :func:`urllib.parse.urlencode` function which accepts unicode keys and
+ values within the ``query`` dict/sequence; all Unicode keys and values are
+ first converted to UTF-8 before being used to compose the query string.
The value of ``query`` must be a sequence of two-tuples
representing key/value pairs *or* an object (often a dictionary)
@@ -35,12 +43,18 @@ def urlencode(query, doseq=True):
the ``doseq=True`` mode, no matter what the value of the second
argument.
- See the Python stdlib documentation for ``urllib.urlencode`` for
- more information.
+ Both the key and value are encoded using the ``quote_via`` function which
+ by default is using a similar algorithm to :func:`urllib.parse.quote_plus`
+ which converts spaces into '+' characters and '/' into '%2F'.
.. versionchanged:: 1.5
In a key/value pair, if the value is ``None`` then it will be
dropped from the resulting output.
+
+ .. versionchanged:: 1.9
+ Added the ``quote_via`` argument to allow alternate quoting algorithms
+ to be used.
+
"""
try:
# presumed to be a dictionary
@@ -52,28 +66,19 @@ def urlencode(query, doseq=True):
prefix = ''
for (k, v) in query:
- k = quote_plus(k)
+ k = quote_via(k)
if is_nonstr_iter(v):
for x in v:
- x = quote_plus(x)
+ x = quote_via(x)
result += '%s%s=%s' % (prefix, k, x)
prefix = '&'
elif v is None:
result += '%s%s=' % (prefix, k)
else:
- v = quote_plus(v)
+ v = quote_via(v)
result += '%s%s=%s' % (prefix, k, v)
prefix = '&'
return result
-
-# bw compat api (dnr)
-def quote_plus(val, safe=''):
- cls = val.__class__
- if cls is text_type:
- val = val.encode('utf-8')
- elif cls is not binary_type:
- val = str(val).encode('utf-8')
- return _quote_plus(val, safe=safe)
diff --git a/pyramid/events.py b/pyramid/events.py
index 35da2fa6f..93fc127a1 100644
--- a/pyramid/events.py
+++ b/pyramid/events.py
@@ -68,12 +68,34 @@ class subscriber(object):
:ref:`subscriber_predicates` for a description of how predicates can
narrow the set of circumstances in which a subscriber will be called.
+ Two additional keyword arguments which will be passed to the
+ :term:`venusian` ``attach`` function are ``_depth`` and ``_category``.
+
+ ``_depth`` is provided for people who wish to reuse this class from another
+ decorator. The default value is ``0`` and should be specified relative to
+ the ``subscriber`` invocation. It will be passed in to the
+ :term:`venusian` ``attach`` function as the depth of the callstack when
+ Venusian checks if the decorator is being used in a class or module
+ context. It's not often used, but it can be useful in this circumstance.
+
+ ``_category`` sets the decorator category name. It can be useful in
+ combination with the ``category`` argument of ``scan`` to control which
+ views should be processed.
+
+ See the :py:func:`venusian.attach` function in Venusian for more
+ information about the ``_depth`` and ``_category`` arguments.
+
+ .. versionchanged:: 1.9.1
+ Added the ``_depth`` and ``_category`` arguments.
+
"""
venusian = venusian # for unit testing
def __init__(self, *ifaces, **predicates):
self.ifaces = ifaces
self.predicates = predicates
+ self.depth = predicates.pop('_depth', 0)
+ self.category = predicates.pop('_category', 'pyramid')
def register(self, scanner, name, wrapped):
config = scanner.config
@@ -81,7 +103,8 @@ class subscriber(object):
config.add_subscriber(wrapped, iface, **self.predicates)
def __call__(self, wrapped):
- self.venusian.attach(wrapped, self.register, category='pyramid')
+ self.venusian.attach(wrapped, self.register, category=self.category,
+ depth=self.depth + 1)
return wrapped
@implementer(INewRequest)
@@ -202,7 +225,7 @@ class BeforeRender(dict):
"""
Subscribers to this event may introspect and modify the set of
:term:`renderer globals` before they are passed to a :term:`renderer`.
- This event object iself has a dictionary-like interface that can be used
+ This event object itself has a dictionary-like interface that can be used
for this purpose. For example::
from pyramid.events import subscriber
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 450cd9c24..e9cc007ac 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -503,8 +503,10 @@ class IAuthenticationPolicy(Interface):
class IAuthorizationPolicy(Interface):
""" An object representing a Pyramid authorization policy. """
def permits(context, principals, permission):
- """ Return ``True`` if any of the ``principals`` is allowed the
- ``permission`` in the current ``context``, else return ``False``
+ """ Return an instance of :class:`pyramid.security.Allowed` if any
+ of the ``principals`` is allowed the ``permission`` in the current
+ ``context``, else return an instance of
+ :class:`pyramid.security.Denied`.
"""
def principals_allowed_by_permission(context, permission):
@@ -677,12 +679,83 @@ class IViewPermission(Interface):
"""
class IRouter(Interface):
- """ WSGI application which routes requests to 'view' code based on
- a view registry."""
+ """
+ WSGI application which routes requests to 'view' code based on
+ a view registry.
+
+ """
registry = Attribute(
"""Component architecture registry local to this application.""")
-class ISettings(Interface):
+ def request_context(environ):
+ """
+ Create a new request context from a WSGI environ.
+
+ The request context is used to push/pop the threadlocals required
+ when processing the request. It also contains an initialized
+ :class:`pyramid.interfaces.IRequest` instance using the registered
+ :class:`pyramid.interfaces.IRequestFactory`. The context may be
+ used as a context manager to control the threadlocal lifecycle:
+
+ .. code-block:: python
+
+ with router.request_context(environ) as request:
+ ...
+
+ Alternatively, the context may be used without the ``with`` statement
+ by manually invoking its ``begin()`` and ``end()`` methods.
+
+ .. code-block:: python
+
+ ctx = router.request_context(environ)
+ request = ctx.begin()
+ try:
+ ...
+ finally:
+ ctx.end()
+
+ """
+
+ def invoke_request(request):
+ """
+ Invoke the :app:`Pyramid` request pipeline.
+
+ See :ref:`router_chapter` for information on the request pipeline.
+
+ The output should be a :class:`pyramid.interfaces.IResponse` object
+ or a raised exception.
+
+ """
+
+class IExecutionPolicy(Interface):
+ def __call__(environ, router):
+ """
+ This callable triggers the router to process a raw WSGI environ dict
+ into a response and controls the :app:`Pyramid` request pipeline.
+
+ The ``environ`` is the raw WSGI environ.
+
+ The ``router`` is an :class:`pyramid.interfaces.IRouter` object which
+ should be used to create a request object and send it into the
+ processing pipeline.
+
+ The return value should be a :class:`pyramid.interfaces.IResponse`
+ object or an exception that will be handled by WSGI middleware.
+
+ The default execution policy simply creates a request and sends it
+ through the pipeline, attempting to render any exception that escapes:
+
+ .. code-block:: python
+
+ def simple_execution_policy(environ, router):
+ with router.request_context(environ) as request:
+ try:
+ return router.invoke_request(request)
+ except Exception:
+ return request.invoke_exception_view(reraise=True)
+ """
+
+class ISettings(IDict):
""" Runtime settings utility for pyramid; represents the
deployment settings for the application. Implements a mapping
interface."""
@@ -886,6 +959,13 @@ class ISession(IDict):
usually accessed via ``request.session``.
Keys and values of a session must be pickleable.
+
+ .. versionchanged:: 1.9
+
+ Sessions are no longer required to implement ``get_csrf_token`` and
+ ``new_csrf_token``. CSRF token support was moved to the pluggable
+ :class:`pyramid.interfaces.ICSRFStoragePolicy` configuration hook.
+
"""
# attributes
@@ -940,19 +1020,39 @@ class ISession(IDict):
:meth:`pyramid.interfaces.ISession.flash`
"""
- def new_csrf_token():
- """ Create and set into the session a new, random cross-site request
- forgery protection token. Return the token. It will be a string."""
- def get_csrf_token():
- """ Return a random cross-site request forgery protection token. It
- will be a string. If a token was previously added to the session via
- ``new_csrf_token``, that token will be returned. If no CSRF token
- was previously set into the session, ``new_csrf_token`` will be
+class ICSRFStoragePolicy(Interface):
+ """ An object that offers the ability to verify CSRF tokens and generate
+ new ones."""
+
+ def new_csrf_token(request):
+ """ Create and return a new, random cross-site request forgery
+ protection token. The token will be an ascii-compatible unicode
+ string.
+
+ """
+
+ def get_csrf_token(request):
+ """ Return a cross-site request forgery protection token. It
+ will be an ascii-compatible unicode string. If a token was previously
+ set for this user via ``new_csrf_token``, that token will be returned.
+ If no CSRF token was previously set, ``new_csrf_token`` will be
called, which will create and set a token, and this token will be
returned.
+
"""
+ def check_csrf_token(request, token):
+ """ Determine if the supplied ``token`` is valid. Most implementations
+ should simply compare the ``token`` to the current value of
+ ``get_csrf_token`` but it is possible to verify the token using
+ any mechanism necessary using this method.
+
+ Returns ``True`` if the ``token`` is valid, otherwise ``False``.
+
+ """
+
+
class IIntrospector(Interface):
def get(category_name, discriminator, default=None):
""" Get the IIntrospectable related to the category_name and the
diff --git a/pyramid/paster.py b/pyramid/paster.py
index 5429a7860..f7544f0c5 100644
--- a/pyramid/paster.py
+++ b/pyramid/paster.py
@@ -1,14 +1,17 @@
-import os
+from pyramid.scripting import prepare
+from pyramid.scripts.common import get_config_loader
-from paste.deploy import (
- loadapp,
- appconfig,
- )
+def setup_logging(config_uri, global_conf=None):
+ """
+ Set up Python logging with the filename specified via ``config_uri``
+ (a string in the form ``filename#sectionname``).
-from pyramid.scripting import prepare
-from pyramid.scripts.common import setup_logging # noqa, api
+ Extra defaults can optionally be specified as a dict in ``global_conf``.
+ """
+ loader = get_config_loader(config_uri)
+ loader.setup_logging(global_conf)
-def get_app(config_uri, name=None, options=None, loadapp=loadapp):
+def get_app(config_uri, name=None, options=None):
""" Return the WSGI application named ``name`` in the PasteDeploy
config file specified by ``config_uri``.
@@ -18,20 +21,13 @@ def get_app(config_uri, name=None, options=None, loadapp=loadapp):
If the ``name`` is None, this will attempt to parse the name from
the ``config_uri`` string expecting the format ``inifile#name``.
- If no name is found, the name will default to "main"."""
- path, section = _getpathsec(config_uri, name)
- config_name = 'config:%s' % path
- here_dir = os.getcwd()
+ If no name is found, the name will default to "main".
- app = loadapp(
- config_name,
- name=section,
- relative_to=here_dir,
- global_conf=options)
-
- return app
+ """
+ loader = get_config_loader(config_uri)
+ return loader.get_wsgi_app(name, options)
-def get_appsettings(config_uri, name=None, options=None, appconfig=appconfig):
+def get_appsettings(config_uri, name=None, options=None):
""" Return a dictionary representing the key/value pairs in an ``app``
section within the file represented by ``config_uri``.
@@ -41,24 +37,11 @@ def get_appsettings(config_uri, name=None, options=None, appconfig=appconfig):
If the ``name`` is None, this will attempt to parse the name from
the ``config_uri`` string expecting the format ``inifile#name``.
- If no name is found, the name will default to "main"."""
- path, section = _getpathsec(config_uri, name)
- config_name = 'config:%s' % path
- here_dir = os.getcwd()
- return appconfig(
- config_name,
- name=section,
- relative_to=here_dir,
- global_conf=options)
-
-def _getpathsec(config_uri, name):
- if '#' in config_uri:
- path, section = config_uri.split('#', 1)
- else:
- path, section = config_uri, 'main'
- if name:
- section = name
- return path, section
+ If no name is found, the name will default to "main".
+
+ """
+ loader = get_config_loader(config_uri)
+ return loader.get_wsgi_app_settings(name, options)
def bootstrap(config_uri, request=None, options=None):
""" Load a WSGI application from the PasteDeploy config file specified
diff --git a/pyramid/path.py b/pyramid/path.py
index ceb0a0cf3..3fac7e940 100644
--- a/pyramid/path.py
+++ b/pyramid/path.py
@@ -73,7 +73,7 @@ def package_path(package):
# will be the same: a directory name to the package itself
try:
package.__abspath__ = prefix
- except:
+ except Exception:
# this is only an optimization, ignore any error
pass
return prefix
diff --git a/pyramid/predicates.py b/pyramid/predicates.py
index 7c3a778ca..3d7bb1b4b 100644
--- a/pyramid/predicates.py
+++ b/pyramid/predicates.py
@@ -4,7 +4,7 @@ from pyramid.exceptions import ConfigurationError
from pyramid.compat import is_nonstr_iter
-from pyramid.session import check_csrf_token
+from pyramid.csrf import check_csrf_token
from pyramid.traversal import (
find_interface,
traversal_path,
diff --git a/pyramid/registry.py b/pyramid/registry.py
index 20b3643e9..7b75aeefc 100644
--- a/pyramid/registry.py
+++ b/pyramid/registry.py
@@ -12,7 +12,7 @@ from pyramid.interfaces import (
IIntrospector,
IIntrospectable,
ISettings,
- )
+)
from pyramid.path import (
CALLER_PACKAGE,
@@ -276,7 +276,9 @@ class Deferred(object):
@reify
def value(self):
- return self.func()
+ result = self.func()
+ del self.func
+ return result
def resolve(self):
return self.value
diff --git a/pyramid/renderers.py b/pyramid/renderers.py
index 47705d5d9..6019f50fb 100644
--- a/pyramid/renderers.py
+++ b/pyramid/renderers.py
@@ -1,3 +1,4 @@
+from functools import partial
import json
import os
import re
@@ -19,6 +20,7 @@ from pyramid.compat import (
text_type,
)
+from pyramid.csrf import get_csrf_token
from pyramid.decorator import reify
from pyramid.events import BeforeRender
@@ -428,6 +430,7 @@ class RendererHelper(object):
'context':context,
'request':request,
'req':request,
+ 'get_csrf_token':partial(get_csrf_token, request),
}
return self.render_to_response(response, system, request=request)
@@ -441,13 +444,13 @@ class RendererHelper(object):
'context':getattr(request, 'context', None),
'request':request,
'req':request,
+ 'get_csrf_token':partial(get_csrf_token, request),
}
system_values = BeforeRender(system_values, value)
registry = self.registry
registry.notify(system_values)
-
result = renderer(value, system_values)
return result
diff --git a/pyramid/request.py b/pyramid/request.py
index c1c1da514..201f1d648 100644
--- a/pyramid/request.py
+++ b/pyramid/request.py
@@ -175,7 +175,7 @@ class Request(
version number from which this documentation is autogenerated, but
it will be the 'prevailing WebOb version' at the time of the
release of this :app:`Pyramid` version. See
- http://webob.org/ for further information.
+ https://webob.org/ for further information.
"""
exception = None
exc_info = None
diff --git a/pyramid/response.py b/pyramid/response.py
index 1d9daae7d..1e2546ed0 100644
--- a/pyramid/response.py
+++ b/pyramid/response.py
@@ -145,19 +145,43 @@ class response_adapter(object):
config = Configurator()
config.scan('somepackage_containing_adapters')
+ Two additional keyword arguments which will be passed to the
+ :term:`venusian` ``attach`` function are ``_depth`` and ``_category``.
+
+ ``_depth`` is provided for people who wish to reuse this class from another
+ decorator. The default value is ``0`` and should be specified relative to
+ the ``response_adapter`` invocation. It will be passed in to the
+ :term:`venusian` ``attach`` function as the depth of the callstack when
+ Venusian checks if the decorator is being used in a class or module
+ context. It's not often used, but it can be useful in this circumstance.
+
+ ``_category`` sets the decorator category name. It can be useful in
+ combination with the ``category`` argument of ``scan`` to control which
+ views should be processed.
+
+ See the :py:func:`venusian.attach` function in Venusian for more
+ information about the ``_depth`` and ``_category`` arguments.
+
+ .. versionchanged:: 1.9.1
+ Added the ``_depth`` and ``_category`` arguments.
+
"""
venusian = venusian # for unit testing
- def __init__(self, *types_or_ifaces):
+ def __init__(self, *types_or_ifaces, **kwargs):
self.types_or_ifaces = types_or_ifaces
+ self.depth = kwargs.pop('_depth', 0)
+ self.category = kwargs.pop('_category', 'pyramid')
+ self.kwargs = kwargs
def register(self, scanner, name, wrapped):
config = scanner.config
for type_or_iface in self.types_or_ifaces:
- config.add_response_adapter(wrapped, type_or_iface)
+ config.add_response_adapter(wrapped, type_or_iface, **self.kwargs)
def __call__(self, wrapped):
- self.venusian.attach(wrapped, self.register, category='pyramid')
+ self.venusian.attach(wrapped, self.register, category=self.category,
+ depth=self.depth + 1)
return wrapped
diff --git a/pyramid/router.py b/pyramid/router.py
index fd11925e9..49b7b601b 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -5,6 +5,7 @@ from zope.interface import (
from pyramid.interfaces import (
IDebugLogger,
+ IExecutionPolicy,
IRequest,
IRequestExtensions,
IRootFactory,
@@ -27,7 +28,7 @@ from pyramid.httpexceptions import HTTPNotFound
from pyramid.request import Request
from pyramid.view import _call_view
from pyramid.request import apply_request_extensions
-from pyramid.threadlocal import manager
+from pyramid.threadlocal import RequestContext
from pyramid.traversal import (
DefaultRootFactory,
@@ -40,8 +41,6 @@ class Router(object):
debug_notfound = False
debug_routematch = False
- threadlocal_manager = manager
-
def __init__(self, registry):
q = registry.queryUtility
self.logger = q(IDebugLogger)
@@ -49,6 +48,8 @@ class Router(object):
self.routes_mapper = q(IRoutesMapper)
self.request_factory = q(IRequestFactory, default=Request)
self.request_extensions = q(IRequestExtensions)
+ self.execution_policy = q(
+ IExecutionPolicy, default=default_execution_policy)
self.orig_handle_request = self.handle_request
tweens = q(ITweens)
if tweens is not None:
@@ -182,44 +183,81 @@ class Router(object):
:term:`tween` in the tween stack closest to the request ingress. If
``use_tweens`` is ``False``, the request will be sent to the main
router handler, and no tweens will be invoked.
-
+
See the API for pyramid.request for complete documentation.
"""
- registry = self.registry
- has_listeners = self.registry.has_listeners
- notify = self.registry.notify
- threadlocals = {'registry':registry, 'request':request}
- manager = self.threadlocal_manager
- manager.push(threadlocals)
- request.registry = registry
+ request.registry = self.registry
request.invoke_subrequest = self.invoke_subrequest
-
- if use_tweens:
+ extensions = self.request_extensions
+ if extensions is not None:
+ apply_request_extensions(request, extensions=extensions)
+ with RequestContext(request):
+ return self.invoke_request(request, _use_tweens=use_tweens)
+
+ def request_context(self, environ):
+ """
+ Create a new request context from a WSGI environ.
+
+ The request context is used to push/pop the threadlocals required
+ when processing the request. It also contains an initialized
+ :class:`pyramid.interfaces.IRequest` instance using the registered
+ :class:`pyramid.interfaces.IRequestFactory`. The context may be
+ used as a context manager to control the threadlocal lifecycle:
+
+ .. code-block:: python
+
+ with router.request_context(environ) as request:
+ ...
+
+ Alternatively, the context may be used without the ``with`` statement
+ by manually invoking its ``begin()`` and ``end()`` methods.
+
+ .. code-block:: python
+
+ ctx = router.request_context(environ)
+ request = ctx.begin()
+ try:
+ ...
+ finally:
+ ctx.end()
+
+ """
+ request = self.request_factory(environ)
+ request.registry = self.registry
+ request.invoke_subrequest = self.invoke_subrequest
+ extensions = self.request_extensions
+ if extensions is not None:
+ apply_request_extensions(request, extensions=extensions)
+ return RequestContext(request)
+
+ def invoke_request(self, request, _use_tweens=True):
+ """
+ Execute a request through the request processing pipeline and
+ return the generated response.
+
+ """
+ registry = self.registry
+ has_listeners = registry.has_listeners
+ notify = registry.notify
+
+ if _use_tweens:
handle_request = self.handle_request
else:
handle_request = self.orig_handle_request
try:
+ response = handle_request(request)
- try:
- extensions = self.request_extensions
- if extensions is not None:
- apply_request_extensions(request, extensions=extensions)
- response = handle_request(request)
-
- if request.response_callbacks:
- request._process_response_callbacks(response)
+ if request.response_callbacks:
+ request._process_response_callbacks(response)
- has_listeners and notify(NewResponse(request, response))
-
- return response
+ has_listeners and notify(NewResponse(request, response))
- finally:
- if request.finished_callbacks:
- request._process_finished_callbacks()
+ return response
finally:
- manager.pop()
+ if request.finished_callbacks:
+ request._process_finished_callbacks()
def __call__(self, environ, start_response):
"""
@@ -229,6 +267,12 @@ class Router(object):
within the application registry; call ``start_response`` and
return an iterable.
"""
- request = self.request_factory(environ)
- response = self.invoke_subrequest(request, use_tweens=True)
- return response(request.environ, start_response)
+ response = self.execution_policy(environ, self)
+ return response(environ, start_response)
+
+def default_execution_policy(environ, router):
+ with router.request_context(environ) as request:
+ try:
+ return router.invoke_request(request)
+ except Exception:
+ return request.invoke_exception_view(reraise=True)
diff --git a/pyramid/scaffolds/__init__.py b/pyramid/scaffolds/__init__.py
index 719b4fe76..71a220e22 100644
--- a/pyramid/scaffolds/__init__.py
+++ b/pyramid/scaffolds/__init__.py
@@ -35,9 +35,9 @@ class PyramidTemplate(Template):
msg = dedent(
"""
%(separator)s
- Tutorials: http://docs.pylonsproject.org/projects/pyramid_tutorials/en/latest/
- Documentation: http://docs.pylonsproject.org/projects/pyramid/en/latest/
- Twitter: https://twitter.com/trypyramid
+ Tutorials: https://docs.pylonsproject.org/projects/pyramid_tutorials/en/latest/
+ Documentation: https://docs.pylonsproject.org/projects/pyramid/en/latest/
+ Twitter: https://twitter.com/PylonsProject
Mailing List: https://groups.google.com/forum/#!forum/pylons-discuss
Welcome to Pyramid. Sorry for the convenience.
diff --git a/pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl b/pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl
index f626d1ef0..521816ce7 100644
--- a/pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl
+++ b/pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl
@@ -57,6 +57,7 @@ def includeme(config):
"""
settings = config.get_settings()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
# use pyramid_tm to hook the transaction lifecycle to the request
config.include('pyramid_tm')
diff --git a/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl
index b5cfdc94d..d610a94dc 100644
--- a/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl
+++ b/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl
@@ -18,8 +18,8 @@
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js" integrity="sha384-f1r2UzjsxZ9T4V1f2zBO/evUqSEOpeaUUZcMTz1Up63bl4ruYnFYeM+BxI4NhyI0" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -41,10 +41,10 @@
<div class="links">
<ul>
<li class="current-version">Generated by v{{pyramid_version}}</li>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/">Docs</a></li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -60,7 +60,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js" integrity="sha384-aBL3Lzi6c9LNDGvpHkZrrm3ZVsIwohDD7CDozL0pk8FwCrfmV7H9w8j3L7ikEv6h" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js" integrity="sha384-s1ITto93iSMDxlp/79qhWHi+LsIi9Gx6yL+cOKDuymvihkfol83TYbLbOw+W/wv4" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/pyramid/scaffolds/alchemy/development.ini_tmpl b/pyramid/scaffolds/alchemy/development.ini_tmpl
index 64ac5ab6c..3cfb3996d 100644
--- a/pyramid/scaffolds/alchemy/development.ini_tmpl
+++ b/pyramid/scaffolds/alchemy/development.ini_tmpl
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
###
[app:main]
@@ -26,11 +26,11 @@ sqlalchemy.url = sqlite:///%(here)s/{{project}}.sqlite
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
###
[loggers]
diff --git a/pyramid/scaffolds/alchemy/production.ini_tmpl b/pyramid/scaffolds/alchemy/production.ini_tmpl
index afc1c8f0a..043229a71 100644
--- a/pyramid/scaffolds/alchemy/production.ini_tmpl
+++ b/pyramid/scaffolds/alchemy/production.ini_tmpl
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
###
[app:main]
@@ -20,7 +20,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
###
[loggers]
diff --git a/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl b/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl
index a784c741b..7ed979808 100644
--- a/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl
+++ b/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl
@@ -18,8 +18,8 @@
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js" integrity="sha384-f1r2UzjsxZ9T4V1f2zBO/evUqSEOpeaUUZcMTz1Up63bl4ruYnFYeM+BxI4NhyI0" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -41,10 +41,10 @@
<div class="links">
<ul>
<li class="current-version">Generated by v{{pyramid_version}}</li>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/">Docs</a></li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -60,7 +60,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js" integrity="sha384-aBL3Lzi6c9LNDGvpHkZrrm3ZVsIwohDD7CDozL0pk8FwCrfmV7H9w8j3L7ikEv6h" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js" integrity="sha384-s1ITto93iSMDxlp/79qhWHi+LsIi9Gx6yL+cOKDuymvihkfol83TYbLbOw+W/wv4" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/pyramid/scaffolds/starter/development.ini_tmpl b/pyramid/scaffolds/starter/development.ini_tmpl
index de58ea63e..c6e42d97c 100644
--- a/pyramid/scaffolds/starter/development.ini_tmpl
+++ b/pyramid/scaffolds/starter/development.ini_tmpl
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
###
[app:main]
@@ -24,11 +24,11 @@ pyramid.includes =
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
###
[loggers]
diff --git a/pyramid/scaffolds/starter/production.ini_tmpl b/pyramid/scaffolds/starter/production.ini_tmpl
index 8f0ae66ed..1107a6b2f 100644
--- a/pyramid/scaffolds/starter/production.ini_tmpl
+++ b/pyramid/scaffolds/starter/production.ini_tmpl
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
###
[app:main]
@@ -22,7 +22,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
###
[loggers]
diff --git a/pyramid/scaffolds/zodb/+package+/__init__.py b/pyramid/scaffolds/zodb/+package+/__init__.py
index f2a86df47..a956d0faf 100644
--- a/pyramid/scaffolds/zodb/+package+/__init__.py
+++ b/pyramid/scaffolds/zodb/+package+/__init__.py
@@ -12,6 +12,8 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(root_factory=root_factory, settings=settings)
+ settings = config.get_settings()
+ settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.scan()
diff --git a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl
index 72b480249..dd93ebd2e 100644
--- a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl
+++ b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl
@@ -18,8 +18,8 @@
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js" integrity="sha384-f1r2UzjsxZ9T4V1f2zBO/evUqSEOpeaUUZcMTz1Up63bl4ruYnFYeM+BxI4NhyI0" crossorigin="anonymous"></script>
<![endif]-->
</head>
@@ -42,10 +42,10 @@
<div class="links">
<ul>
<li class="current-version">Generated by v{{pyramid_version}}</li>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/">Docs</a></li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
@@ -61,7 +61,7 @@
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js" integrity="sha384-aBL3Lzi6c9LNDGvpHkZrrm3ZVsIwohDD7CDozL0pk8FwCrfmV7H9w8j3L7ikEv6h" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js" integrity="sha384-s1ITto93iSMDxlp/79qhWHi+LsIi9Gx6yL+cOKDuymvihkfol83TYbLbOw+W/wv4" crossorigin="anonymous"></script>
</body>
</html>
diff --git a/pyramid/scaffolds/zodb/development.ini_tmpl b/pyramid/scaffolds/zodb/development.ini_tmpl
index a155590f8..7d898bcd4 100644
--- a/pyramid/scaffolds/zodb/development.ini_tmpl
+++ b/pyramid/scaffolds/zodb/development.ini_tmpl
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
###
[app:main]
@@ -29,11 +29,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-listen = 127.0.0.1:6543 [::1]:6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
###
[loggers]
diff --git a/pyramid/scaffolds/zodb/production.ini_tmpl b/pyramid/scaffolds/zodb/production.ini_tmpl
index dbfc634f8..7c2e90c2e 100644
--- a/pyramid/scaffolds/zodb/production.ini_tmpl
+++ b/pyramid/scaffolds/zodb/production.ini_tmpl
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
+# https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html
###
[app:main]
@@ -28,7 +28,7 @@ listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
+# https://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html
###
[loggers]
diff --git a/pyramid/scripts/common.py b/pyramid/scripts/common.py
index fc141f6e2..f4b8027db 100644
--- a/pyramid/scripts/common.py
+++ b/pyramid/scripts/common.py
@@ -1,6 +1,4 @@
-import os
-from pyramid.compat import configparser
-from logging.config import fileConfig
+import plaster
def parse_vars(args):
"""
@@ -17,26 +15,9 @@ def parse_vars(args):
result[name] = value
return result
-def setup_logging(config_uri, global_conf=None,
- fileConfig=fileConfig,
- configparser=configparser):
+def get_config_loader(config_uri):
"""
- Set up logging via :func:`logging.config.fileConfig` with the filename
- specified via ``config_uri`` (a string in the form
- ``filename#sectionname``).
+ Find a ``plaster.ILoader`` object supporting the "wsgi" protocol.
- ConfigParser defaults are specified for the special ``__file__``
- and ``here`` variables, similar to PasteDeploy config loading.
- Extra defaults can optionally be specified as a dict in ``global_conf``.
"""
- path = config_uri.split('#', 1)[0]
- parser = configparser.ConfigParser()
- parser.read([path])
- if parser.has_section('loggers'):
- config_file = os.path.abspath(path)
- full_global_conf = dict(
- __file__=config_file,
- here=os.path.dirname(config_file))
- if global_conf:
- full_global_conf.update(global_conf)
- return fileConfig(config_file, full_global_conf)
+ return plaster.get_loader(config_uri, protocols=['wsgi'])
diff --git a/pyramid/scripts/prequest.py b/pyramid/scripts/prequest.py
index 66feff624..f0681afd7 100644
--- a/pyramid/scripts/prequest.py
+++ b/pyramid/scripts/prequest.py
@@ -5,9 +5,8 @@ import textwrap
from pyramid.compat import url_unquote
from pyramid.request import Request
-from pyramid.paster import get_app
+from pyramid.scripts.common import get_config_loader
from pyramid.scripts.common import parse_vars
-from pyramid.scripts.common import setup_logging
def main(argv=sys.argv, quiet=False):
command = PRequestCommand(argv, quiet)
@@ -110,7 +109,7 @@ class PRequestCommand(object):
"passed here.",
)
- get_app = staticmethod(get_app)
+ _get_config_loader = staticmethod(get_config_loader)
stdin = sys.stdin
def __init__(self, argv, quiet=False):
@@ -121,17 +120,18 @@ class PRequestCommand(object):
if not self.quiet:
print(msg)
- def configure_logging(self, app_spec):
- setup_logging(app_spec)
-
def run(self):
if not self.args.config_uri or not self.args.path_info:
self.out('You must provide at least two arguments')
return 2
- app_spec = self.args.config_uri
+ config_uri = self.args.config_uri
+ config_vars = parse_vars(self.args.config_vars)
path = self.args.path_info
- self.configure_logging(app_spec)
+ loader = self._get_config_loader(config_uri)
+ loader.setup_logging(config_vars)
+
+ app = loader.get_wsgi_app(self.args.app_name, config_vars)
if not path.startswith('/'):
path = '/' + path
@@ -158,9 +158,6 @@ class PRequestCommand(object):
name, value = item.split(':', 1)
headers[name] = value.strip()
- app = self.get_app(app_spec, self.args.app_name,
- options=parse_vars(self.args.config_vars))
-
request_method = (self.args.method or 'GET').upper()
environ = {
diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py
index 80c8238a2..69d61ae8f 100644
--- a/pyramid/scripts/proutes.py
+++ b/pyramid/scripts/proutes.py
@@ -7,10 +7,11 @@ import re
from zope.interface import Interface
from pyramid.paster import bootstrap
-from pyramid.compat import (string_types, configparser)
+from pyramid.compat import string_types
from pyramid.interfaces import IRouteRequest
from pyramid.config import not_
+from pyramid.scripts.common import get_config_loader
from pyramid.scripts.common import parse_vars
from pyramid.static import static_view
from pyramid.view import _find_views
@@ -175,7 +176,6 @@ def get_route_data(route, registry):
(route.name, route_intr['external_url'], UNKNOWN_KEY, ANY_KEY)
]
-
route_request_methods = route_intr['request_methods']
view_intr = registry.introspector.related(route_intr)
@@ -245,9 +245,9 @@ class PRoutesCommand(object):
will be assumed. Example: 'proutes myapp.ini'.
"""
- bootstrap = (bootstrap,)
+ bootstrap = staticmethod(bootstrap) # testing
+ get_config_loader = staticmethod(get_config_loader) # testing
stdout = sys.stdout
- ConfigParser = configparser.ConfigParser # testing
parser = argparse.ArgumentParser(
description=textwrap.dedent(description),
formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -308,18 +308,12 @@ class PRoutesCommand(object):
return True
- def proutes_file_config(self, filename):
- config = self.ConfigParser()
- config.read(filename)
- try:
- items = config.items('proutes')
- for k, v in items:
- if 'format' == k:
- cols = re.split(r'[,|\s\n]+', v)
- self.column_format = [x.strip() for x in cols]
-
- except configparser.NoSectionError:
- return
+ def proutes_file_config(self, loader, global_conf=None):
+ settings = loader.get_settings('proutes', global_conf)
+ format = settings.get('format')
+ if format:
+ cols = re.split(r'[,|\s\n]+', format)
+ self.column_format = [x.strip() for x in cols]
def out(self, msg): # pragma: no cover
if not self.quiet:
@@ -336,12 +330,15 @@ class PRoutesCommand(object):
return 2
config_uri = self.args.config_uri
- env = self.bootstrap[0](config_uri, options=parse_vars(self.args.config_vars))
+ config_vars = parse_vars(self.args.config_vars)
+ loader = self.get_config_loader(config_uri)
+ loader.setup_logging(config_vars)
+ self.proutes_file_config(loader, config_vars)
+
+ env = self.bootstrap(config_uri, options=config_vars)
registry = env['registry']
mapper = self._get_mapper(registry)
- self.proutes_file_config(config_uri)
-
if self.args.format:
columns = self.args.format.split(',')
self.column_format = [x.strip() for x in columns]
diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py
index e2d97f5ec..8ee6e1467 100644
--- a/pyramid/scripts/pserve.py
+++ b/pyramid/scripts/pserve.py
@@ -18,27 +18,20 @@ import time
import webbrowser
import hupper
-from paste.deploy import (
- loadapp,
- loadserver,
-)
-from paste.deploy.loadwsgi import (
- SERVER,
- loadcontext,
-)
from pyramid.compat import PY2
-from pyramid.compat import configparser
+from pyramid.scripts.common import get_config_loader
from pyramid.scripts.common import parse_vars
-from pyramid.scripts.common import setup_logging
from pyramid.path import AssetResolver
from pyramid.settings import aslist
+
def main(argv=sys.argv, quiet=False):
command = PServeCommand(argv, quiet=quiet)
return command.run()
+
class PServeCommand(object):
description = """\
@@ -85,7 +78,9 @@ class PServeCommand(object):
'-b', '--browser',
dest='browser',
action='store_true',
- help="Open a web browser to server url")
+ help=("Open a web browser to the server url. The server url is "
+ "determined from the 'open_url' setting in the 'pserve' "
+ "section of the configuration file."))
parser.add_argument(
'-v', '--verbose',
default=default_verbosity,
@@ -113,10 +108,9 @@ class PServeCommand(object):
"passed here.",
)
+ _get_config_loader = staticmethod(get_config_loader) # for testing
- ConfigParser = configparser.ConfigParser # testing
- loadapp = staticmethod(loadapp) # testing
- loadserver = staticmethod(loadserver) # testing
+ open_url = None
_scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
@@ -124,32 +118,22 @@ class PServeCommand(object):
self.args = self.parser.parse_args(argv[1:])
if quiet:
self.args.verbose = 0
- self.watch_files = []
+ if self.args.reload:
+ self.worker_kwargs = {'argv': argv, "quiet": quiet}
+ self.watch_files = set()
- def out(self, msg): # pragma: no cover
+ def out(self, msg): # pragma: no cover
if self.args.verbose > 0:
print(msg)
- def get_config_vars(self):
- restvars = self.args.config_vars
- return parse_vars(restvars)
-
- def pserve_file_config(self, filename, global_conf=None):
- here = os.path.abspath(os.path.dirname(filename))
- defaults = {}
- if global_conf:
- defaults.update(global_conf)
- defaults['here'] = here
+ def get_config_path(self, loader):
+ return os.path.abspath(loader.uri.path)
- config = self.ConfigParser(defaults=defaults)
- config.optionxform = str
- config.read(filename)
- try:
- items = dict(config.items('pserve'))
- except configparser.NoSectionError:
- return
-
- watch_files = aslist(items.get('watch_files', ''), flatten=False)
+ def pserve_file_config(self, loader, global_conf=None):
+ settings = loader.get_settings('pserve', global_conf)
+ config_path = self.get_config_path(loader)
+ here = os.path.dirname(config_path)
+ watch_files = aslist(settings.get('watch_files', ''), flatten=False)
# track file paths relative to the ini file
resolver = AssetResolver(package=None)
@@ -158,23 +142,33 @@ class PServeCommand(object):
file = resolver.resolve(file).abspath()
elif not os.path.isabs(file):
file = os.path.join(here, file)
- self.watch_files.append(os.path.abspath(file))
+ self.watch_files.add(os.path.abspath(file))
+
+ # attempt to determine the url of the server
+ open_url = settings.get('open_url')
+ if open_url:
+ self.open_url = open_url
+
+ def guess_server_url(self, loader, server_name, global_conf=None):
+ server_name = server_name or 'main'
+ settings = loader.get_settings('server:' + server_name, global_conf)
+ if 'port' in settings:
+ return 'http://127.0.0.1:{port}'.format(**settings)
def run(self): # pragma: no cover
if not self.args.config_uri:
self.out('You must give a config file')
return 2
+ config_uri = self.args.config_uri
+ config_vars = parse_vars(self.args.config_vars)
app_spec = self.args.config_uri
-
- vars = self.get_config_vars()
app_name = self.args.app_name
- base = os.getcwd()
- if not self._scheme_re.search(app_spec):
- config_path = os.path.join(base, app_spec)
- app_spec = 'config:' + app_spec
- else:
- config_path = None
+ loader = self._get_config_loader(config_uri)
+ loader.setup_logging(config_vars)
+
+ self.pserve_file_config(loader, global_conf=config_vars)
+
server_name = self.args.server_name
if self.args.server:
server_spec = 'egg:pyramid'
@@ -183,18 +177,31 @@ class PServeCommand(object):
else:
server_spec = app_spec
+ server_loader = loader
+ if server_spec != app_spec:
+ server_loader = self.get_config_loader(server_spec)
+
# do not open the browser on each reload so check hupper first
if self.args.browser and not hupper.is_active():
- def open_browser():
- context = loadcontext(
- SERVER, app_spec, name=server_name, relative_to=base,
- global_conf=vars)
- url = 'http://127.0.0.1:{port}/'.format(**context.config())
- time.sleep(1)
- webbrowser.open(url)
- t = threading.Thread(target=open_browser)
- t.setDaemon(True)
- t.start()
+ url = self.open_url
+
+ if not url:
+ url = self.guess_server_url(
+ server_loader, server_name, config_vars)
+
+ if not url:
+ self.out('WARNING: could not determine the server\'s url to '
+ 'open the browser. To fix this set the "open_url" '
+ 'setting in the [pserve] section of the '
+ 'configuration file.')
+
+ else:
+ def open_browser():
+ time.sleep(1)
+ webbrowser.open(url)
+ t = threading.Thread(target=open_browser)
+ t.setDaemon(True)
+ t.start()
if self.args.reload and not hupper.is_active():
if self.args.verbose > 1:
@@ -203,23 +210,23 @@ class PServeCommand(object):
'pyramid.scripts.pserve.main',
reload_interval=int(self.args.reload_interval),
verbose=self.args.verbose,
+ worker_kwargs=self.worker_kwargs
)
return 0
- if config_path:
- setup_logging(config_path, global_conf=vars)
- self.pserve_file_config(config_path, global_conf=vars)
- self.watch_files.append(config_path)
+ config_path = self.get_config_path(loader)
+ self.watch_files.add(config_path)
+
+ server_path = self.get_config_path(server_loader)
+ self.watch_files.add(server_path)
if hupper.is_active():
reloader = hupper.get_reloader()
- reloader.watch_files(self.watch_files)
+ reloader.watch_files(list(self.watch_files))
- server = self.loadserver(
- server_spec, name=server_name, relative_to=base, global_conf=vars)
+ server = server_loader.get_wsgi_server(server_name, config_vars)
- app = self.loadapp(
- app_spec, name=app_name, relative_to=base, global_conf=vars)
+ app = loader.get_wsgi_app(app_name, config_vars)
if self.args.verbose > 0:
if hasattr(os, 'getpid'):
@@ -239,8 +246,9 @@ class PServeCommand(object):
msg = ''
self.out('Exiting%s (-v to see traceback)' % msg)
+
# For paste.deploy server instantiation (egg:pyramid#wsgiref)
-def wsgiref_server_runner(wsgi_app, global_conf, **kw): # pragma: no cover
+def wsgiref_server_runner(wsgi_app, global_conf, **kw): # pragma: no cover
from wsgiref.simple_server import make_server
host = kw.get('host', '0.0.0.0')
port = int(kw.get('port', 8080))
@@ -248,13 +256,14 @@ def wsgiref_server_runner(wsgi_app, global_conf, **kw): # pragma: no cover
print('Starting HTTP server on http://%s:%s' % (host, port))
server.serve_forever()
+
# For paste.deploy server instantiation (egg:pyramid#cherrypy)
def cherrypy_server_runner(
app, global_conf=None, host='127.0.0.1', port=None,
ssl_pem=None, protocol_version=None, numthreads=None,
server_name=None, max=None, request_queue_size=None,
timeout=None
- ): # pragma: no cover
+ ): # pragma: no cover
"""
Entry point for CherryPy's WSGI server
@@ -332,18 +341,26 @@ def cherrypy_server_runner(
if var is not None:
kwargs[var_name] = int(var)
- from cherrypy import wsgiserver
+ try:
+ from cheroot.wsgi import Server as WSGIServer
+ except ImportError:
+ from cherrypy.wsgiserver import CherryPyWSGIServer as WSGIServer
- server = wsgiserver.CherryPyWSGIServer(bind_addr, app,
- server_name=server_name, **kwargs)
+ server = WSGIServer(bind_addr, app,
+ server_name=server_name, **kwargs)
if ssl_pem is not None:
if PY2:
server.ssl_certificate = server.ssl_private_key = ssl_pem
else:
# creates wsgiserver.ssl_builtin as side-effect
- wsgiserver.get_ssl_adapter_class()
- server.ssl_adapter = wsgiserver.ssl_builtin.BuiltinSSLAdapter(
- ssl_pem, ssl_pem)
+ try:
+ from cheroot.server import get_ssl_adapter_class
+ from cheroot.ssl.builtin import BuiltinSSLAdapter
+ except ImportError:
+ from cherrypy.wsgiserver import get_ssl_adapter_class
+ from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter
+ get_ssl_adapter_class()
+ server.ssl_adapter = BuiltinSSLAdapter(ssl_pem, ssl_pem)
if protocol_version:
server.protocol = protocol_version
@@ -361,5 +378,6 @@ def cherrypy_server_runner(
return server
-if __name__ == '__main__': # pragma: no cover
+
+if __name__ == '__main__': # pragma: no cover
sys.exit(main() or 0)
diff --git a/pyramid/scripts/pshell.py b/pyramid/scripts/pshell.py
index 83e640c32..bb201dbc2 100644
--- a/pyramid/scripts/pshell.py
+++ b/pyramid/scripts/pshell.py
@@ -5,15 +5,14 @@ import sys
import textwrap
import pkg_resources
-from pyramid.compat import configparser
from pyramid.compat import exec_
from pyramid.util import DottedNameResolver
from pyramid.paster import bootstrap
from pyramid.settings import aslist
+from pyramid.scripts.common import get_config_loader
from pyramid.scripts.common import parse_vars
-from pyramid.scripts.common import setup_logging
def main(argv=sys.argv, quiet=False):
command = PShellCommand(argv, quiet)
@@ -41,7 +40,8 @@ class PShellCommand(object):
than one Pyramid application within it, the loader will use the
last one.
"""
- bootstrap = (bootstrap,) # for testing
+ bootstrap = staticmethod(bootstrap) # for testing
+ get_config_loader = staticmethod(get_config_loader) # for testing
pkg_resources = pkg_resources # for testing
parser = argparse.ArgumentParser(
@@ -78,7 +78,6 @@ class PShellCommand(object):
"passed here.",
)
- ConfigParser = configparser.ConfigParser # testing
default_runner = python_shell_runner # testing
loaded_objects = {}
@@ -91,20 +90,13 @@ class PShellCommand(object):
self.quiet = quiet
self.args = self.parser.parse_args(argv[1:])
- def pshell_file_config(self, filename):
- config = self.ConfigParser()
- config.optionxform = str
- config.read(filename)
- try:
- items = config.items('pshell')
- except configparser.NoSectionError:
- return
-
+ def pshell_file_config(self, loader, defaults):
+ settings = loader.get_settings('pshell', defaults)
resolver = DottedNameResolver(None)
self.loaded_objects = {}
self.object_help = {}
self.setup = None
- for k, v in items:
+ for k, v in settings.items():
if k == 'setup':
self.setup = v
elif k == 'default_shell':
@@ -124,13 +116,12 @@ class PShellCommand(object):
self.out('Requires a config file argument')
return 2
config_uri = self.args.config_uri
- config_file = config_uri.split('#', 1)[0]
- setup_logging(config_file)
- self.pshell_file_config(config_file)
+ config_vars = parse_vars(self.args.config_vars)
+ loader = self.get_config_loader(config_uri)
+ loader.setup_logging(config_vars)
+ self.pshell_file_config(loader, config_vars)
- # bootstrap the environ
- env = self.bootstrap[0](config_uri,
- options=parse_vars(self.args.config_vars))
+ env = self.bootstrap(config_uri, options=config_vars)
# remove the closer from the env
self.closer = env.pop('closer')
diff --git a/pyramid/scripts/ptweens.py b/pyramid/scripts/ptweens.py
index 5ca77e52a..d5cbebe12 100644
--- a/pyramid/scripts/ptweens.py
+++ b/pyramid/scripts/ptweens.py
@@ -7,6 +7,7 @@ from pyramid.interfaces import ITweens
from pyramid.tweens import MAIN
from pyramid.tweens import INGRESS
from pyramid.paster import bootstrap
+from pyramid.paster import setup_logging
from pyramid.scripts.common import parse_vars
def main(argv=sys.argv, quiet=False):
@@ -47,7 +48,8 @@ class PTweensCommand(object):
)
stdout = sys.stdout
- bootstrap = (bootstrap,) # testing
+ bootstrap = staticmethod(bootstrap) # testing
+ setup_logging = staticmethod(setup_logging) # testing
def __init__(self, argv, quiet=False):
self.quiet = quiet
@@ -76,7 +78,9 @@ class PTweensCommand(object):
self.out('Requires a config file argument')
return 2
config_uri = self.args.config_uri
- env = self.bootstrap[0](config_uri, options=parse_vars(self.args.config_vars))
+ config_vars = parse_vars(self.args.config_vars)
+ self.setup_logging(config_uri, global_conf=config_vars)
+ env = self.bootstrap(config_uri, options=config_vars)
registry = env['registry']
tweens = self._get_tweens(registry)
if tweens is not None:
diff --git a/pyramid/scripts/pviews.py b/pyramid/scripts/pviews.py
index 4d3312917..c0df2f078 100644
--- a/pyramid/scripts/pviews.py
+++ b/pyramid/scripts/pviews.py
@@ -4,6 +4,7 @@ import textwrap
from pyramid.interfaces import IMultiView
from pyramid.paster import bootstrap
+from pyramid.paster import setup_logging
from pyramid.request import Request
from pyramid.scripts.common import parse_vars
from pyramid.view import _find_views
@@ -51,7 +52,8 @@ class PViewsCommand(object):
)
- bootstrap = (bootstrap,) # testing
+ bootstrap = staticmethod(bootstrap) # testing
+ setup_logging = staticmethod(setup_logging) # testing
def __init__(self, argv, quiet=False):
self.quiet = quiet
@@ -252,13 +254,15 @@ class PViewsCommand(object):
self.out('Command requires a config file arg and a url arg')
return 2
config_uri = self.args.config_uri
+ config_vars = parse_vars(self.args.config_vars)
url = self.args.url
+ self.setup_logging(config_uri, global_conf=config_vars)
+
if not url.startswith('/'):
url = '/%s' % url
request = Request.blank(url)
- env = self.bootstrap[0](config_uri, options=parse_vars(self.args.config_vars),
- request=request)
+ env = self.bootstrap(config_uri, options=config_vars, request=request)
view = self._find_view(request)
self.out('')
self.out("URL = %s" % url)
diff --git a/pyramid/security.py b/pyramid/security.py
index 82e6b73a9..d12314684 100644
--- a/pyramid/security.py
+++ b/pyramid/security.py
@@ -21,10 +21,13 @@ _marker = object()
class AllPermissionsList(object):
""" Stand in 'permission list' to represent all permissions """
+
def __iter__(self):
- return ()
+ return iter(())
+
def __contains__(self, other):
return True
+
def __eq__(self, other):
return isinstance(other, self.__class__)
@@ -242,6 +245,14 @@ def view_execution_permitted(context, request, name=''):
class PermitsResult(int):
def __new__(cls, s, *args):
+ """
+ Create a new instance.
+
+ :param fmt: A format string explaining the reason for denial.
+ :param args: Arguments are stored and used with the format string
+ to generate the ``msg``.
+
+ """
inst = int.__new__(cls, cls.boolval)
inst.s = s
inst.args = args
@@ -249,6 +260,7 @@ class PermitsResult(int):
@property
def msg(self):
+ """ A string indicating why the result was generated."""
return self.s % self.args
def __str__(self):
@@ -260,24 +272,52 @@ class PermitsResult(int):
self.msg)
class Denied(PermitsResult):
- """ An instance of ``Denied`` is returned when a security-related
+ """
+ An instance of ``Denied`` is returned when a security-related
API or other :app:`Pyramid` code denies an action unrelated to
an ACL check. It evaluates equal to all boolean false types. It
has an attribute named ``msg`` describing the circumstances for
- the deny."""
+ the deny.
+
+ """
boolval = 0
class Allowed(PermitsResult):
- """ An instance of ``Allowed`` is returned when a security-related
+ """
+ An instance of ``Allowed`` is returned when a security-related
API or other :app:`Pyramid` code allows an action unrelated to
an ACL check. It evaluates equal to all boolean true types. It
has an attribute named ``msg`` describing the circumstances for
- the allow."""
+ the allow.
+
+ """
boolval = 1
-class ACLPermitsResult(int):
+class ACLPermitsResult(PermitsResult):
def __new__(cls, ace, acl, permission, principals, context):
- inst = int.__new__(cls, cls.boolval)
+ """
+ Create a new instance.
+
+ :param ace: The :term:`ACE` that matched, triggering the result.
+ :param acl: The :term:`ACL` containing ``ace``.
+ :param permission: The required :term:`permission`.
+ :param principals: The list of :term:`principals <principal>` provided.
+ :param context: The :term:`context` providing the :term:`lineage`
+ searched.
+
+ """
+ fmt = ('%s permission %r via ACE %r in ACL %r on context %r for '
+ 'principals %r')
+ inst = PermitsResult.__new__(
+ cls,
+ fmt,
+ cls.__name__,
+ permission,
+ ace,
+ acl,
+ context,
+ principals,
+ )
inst.permission = permission
inst.ace = ace
inst.acl = acl
@@ -285,44 +325,31 @@ class ACLPermitsResult(int):
inst.context = context
return inst
- @property
- def msg(self):
- s = ('%s permission %r via ACE %r in ACL %r on context %r for '
- 'principals %r')
- return s % (self.__class__.__name__,
- self.permission,
- self.ace,
- self.acl,
- self.context,
- self.principals)
-
- def __str__(self):
- return self.msg
+class ACLDenied(ACLPermitsResult, Denied):
+ """
+ An instance of ``ACLDenied`` is a specialization of
+ :class:`pyramid.security.Denied` that represents that a security check
+ made explicitly against ACL was denied. It evaluates equal to all
+ boolean false types. It also has the following attributes: ``acl``,
+ ``ace``, ``permission``, ``principals``, and ``context``. These
+ attributes indicate the security values involved in the request. Its
+ ``__str__`` method prints a summary of these attributes for debugging
+ purposes. The same summary is available as the ``msg`` attribute.
- def __repr__(self):
- return '<%s instance at %s with msg %r>' % (self.__class__.__name__,
- id(self),
- self.msg)
+ """
-class ACLDenied(ACLPermitsResult):
- """ An instance of ``ACLDenied`` represents that a security check made
- explicitly against ACL was denied. It evaluates equal to all boolean
- false types. It also has the following attributes: ``acl``, ``ace``,
- ``permission``, ``principals``, and ``context``. These attributes
- indicate the security values involved in the request. Its __str__ method
- prints a summary of these attributes for debugging purposes. The same
- summary is available as the ``msg`` attribute."""
- boolval = 0
+class ACLAllowed(ACLPermitsResult, Allowed):
+ """
+ An instance of ``ACLAllowed`` is a specialization of
+ :class:`pyramid.security.Allowed` that represents that a security check
+ made explicitly against ACL was allowed. It evaluates equal to all
+ boolean true types. It also has the following attributes: ``acl``,
+ ``ace``, ``permission``, ``principals``, and ``context``. These
+ attributes indicate the security values involved in the request. Its
+ ``__str__`` method prints a summary of these attributes for debugging
+ purposes. The same summary is available as the ``msg`` attribute.
-class ACLAllowed(ACLPermitsResult):
- """ An instance of ``ACLAllowed`` represents that a security check made
- explicitly against ACL was allowed. It evaluates equal to all boolean
- true types. It also has the following attributes: ``acl``, ``ace``,
- ``permission``, ``principals``, and ``context``. These attributes
- indicate the security values involved in the request. Its __str__ method
- prints a summary of these attributes for debugging purposes. The same
- summary is available as the ``msg`` attribute."""
- boolval = 1
+ """
class AuthenticationAPIMixin(object):
@@ -392,7 +419,8 @@ class AuthorizationAPIMixin(object):
:type permission: unicode, str
:param context: A resource object or ``None``
:type context: object
- :returns: `pyramid.security.PermitsResult`
+ :returns: Either :class:`pyramid.security.Allowed` or
+ :class:`pyramid.security.Denied`.
.. versionadded:: 1.5
diff --git a/pyramid/session.py b/pyramid/session.py
index 47b80f617..33119343b 100644
--- a/pyramid/session.py
+++ b/pyramid/session.py
@@ -16,19 +16,15 @@ from pyramid.compat import (
text_,
bytes_,
native_,
- urlparse,
)
-
-from pyramid.exceptions import (
- BadCSRFOrigin,
- BadCSRFToken,
+from pyramid.csrf import (
+ check_csrf_origin,
+ check_csrf_token,
)
+
from pyramid.interfaces import ISession
-from pyramid.settings import aslist
-from pyramid.util import (
- is_same_domain,
- strings_differ,
-)
+from pyramid.util import strings_differ
+
def manage_accessed(wrapped):
""" Decorator which causes a cookie to be renewed when an accessor
@@ -109,149 +105,6 @@ def signed_deserialize(serialized, secret, hmac=hmac):
return pickle.loads(pickled)
-def check_csrf_origin(request, trusted_origins=None, raises=True):
- """
- Check the Origin of the request to see if it is a cross site request or
- not.
-
- If the value supplied by the Origin or Referer header isn't one of the
- trusted origins and ``raises`` is ``True``, this function will raise a
- :exc:`pyramid.exceptions.BadCSRFOrigin` exception but if ``raises`` is
- ``False`` this function will return ``False`` instead. If the CSRF origin
- checks are successful this function will return ``True`` unconditionally.
-
- Additional trusted origins may be added by passing a list of domain (and
- ports if nonstandard like `['example.com', 'dev.example.com:8080']`) in
- with the ``trusted_origins`` parameter. If ``trusted_origins`` is ``None``
- (the default) this list of additional domains will be pulled from the
- ``pyramid.csrf_trusted_origins`` setting.
-
- Note that this function will do nothing if request.scheme is not https.
-
- .. versionadded:: 1.7
- """
- def _fail(reason):
- if raises:
- raise BadCSRFOrigin(reason)
- else:
- return False
-
- if request.scheme == "https":
- # Suppose user visits http://example.com/
- # An active network attacker (man-in-the-middle, MITM) sends a
- # POST form that targets https://example.com/detonate-bomb/ and
- # submits it via JavaScript.
- #
- # The attacker will need to provide a CSRF cookie and token, but
- # that's no problem for a MITM when we cannot make any assumptions
- # about what kind of session storage is being used. So the MITM can
- # circumvent the CSRF protection. This is true for any HTTP connection,
- # but anyone using HTTPS expects better! For this reason, for
- # https://example.com/ we need additional protection that treats
- # http://example.com/ as completely untrusted. Under HTTPS,
- # Barth et al. found that the Referer header is missing for
- # same-domain requests in only about 0.2% of cases or less, so
- # we can use strict Referer checking.
-
- # Determine the origin of this request
- origin = request.headers.get("Origin")
- if origin is None:
- origin = request.referrer
-
- # Fail if we were not able to locate an origin at all
- if not origin:
- return _fail("Origin checking failed - no Origin or Referer.")
-
- # Parse our origin so we we can extract the required information from
- # it.
- originp = urlparse.urlparse(origin)
-
- # Ensure that our Referer is also secure.
- if originp.scheme != "https":
- return _fail(
- "Referer checking failed - Referer is insecure while host is "
- "secure."
- )
-
- # Determine which origins we trust, which by default will include the
- # current origin.
- if trusted_origins is None:
- trusted_origins = aslist(
- request.registry.settings.get(
- "pyramid.csrf_trusted_origins", [])
- )
-
- if request.host_port not in set(["80", "443"]):
- trusted_origins.append("{0.domain}:{0.host_port}".format(request))
- else:
- trusted_origins.append(request.domain)
-
- # Actually check to see if the request's origin matches any of our
- # trusted origins.
- if not any(is_same_domain(originp.netloc, host)
- for host in trusted_origins):
- reason = (
- "Referer checking failed - {0} does not match any trusted "
- "origins."
- )
- return _fail(reason.format(origin))
-
- return True
-
-
-def check_csrf_token(request,
- token='csrf_token',
- header='X-CSRF-Token',
- raises=True):
- """ Check the CSRF token in the request's session against the value in
- ``request.POST.get(token)`` (if a POST request) or
- ``request.headers.get(header)``. If a ``token`` keyword is not supplied to
- this function, the string ``csrf_token`` will be used to look up the token
- in ``request.POST``. If a ``header`` keyword is not supplied to this
- function, the string ``X-CSRF-Token`` will be used to look up the token in
- ``request.headers``.
-
- If the value supplied by post or by header doesn't match the value
- supplied by ``request.session.get_csrf_token()``, and ``raises`` is
- ``True``, this function will raise an
- :exc:`pyramid.exceptions.BadCSRFToken` exception.
- If the values differ and ``raises`` is ``False``, this function will
- return ``False``. If the CSRF check is successful, this function will
- return ``True`` unconditionally.
-
- Note that using this function requires that a :term:`session factory` is
- configured.
-
- See :ref:`auto_csrf_checking` for information about how to secure your
- application automatically against CSRF attacks.
-
- .. versionadded:: 1.4a2
-
- .. versionchanged:: 1.7a1
- A CSRF token passed in the query string of the request is no longer
- considered valid. It must be passed in either the request body or
- a header.
- """
- supplied_token = ""
- # If this is a POST/PUT/etc request, then we'll check the body to see if it
- # has a token. We explicitly use request.POST here because CSRF tokens
- # should never appear in an URL as doing so is a security issue. We also
- # explicitly check for request.POST here as we do not support sending form
- # encoded data over anything but a request.POST.
- if token is not None:
- supplied_token = request.POST.get(token, "")
-
- # If we were unable to locate a CSRF token in a request body, then we'll
- # check to see if there are any headers that have a value for us.
- if supplied_token == "" and header is not None:
- supplied_token = request.headers.get(header, "")
-
- expected_token = request.session.get_csrf_token()
- if strings_differ(bytes_(expected_token), bytes_(supplied_token)):
- if raises:
- raise BadCSRFToken('check_csrf_token(): Invalid token')
- return False
- return True
class PickleSerializer(object):
""" A serializer that uses the pickle protocol to dump Python
@@ -759,3 +612,13 @@ def SignedCookieSessionFactory(
reissue_time=reissue_time,
set_on_exception=set_on_exception,
)
+
+check_csrf_origin = check_csrf_origin # api
+deprecated('check_csrf_origin',
+ 'pyramid.session.check_csrf_origin is deprecated as of Pyramid '
+ '1.9. Use pyramid.csrf.check_csrf_origin instead.')
+
+check_csrf_token = check_csrf_token # api
+deprecated('check_csrf_token',
+ 'pyramid.session.check_csrf_token is deprecated as of Pyramid '
+ '1.9. Use pyramid.csrf.check_csrf_token instead.')
diff --git a/pyramid/static.py b/pyramid/static.py
index a8088129e..70fdf877b 100644
--- a/pyramid/static.py
+++ b/pyramid/static.py
@@ -17,14 +17,15 @@ from pkg_resources import (
resource_isdir,
)
-from repoze.lru import lru_cache
-
from pyramid.asset import (
abspath_from_asset_spec,
resolve_asset_spec,
)
-from pyramid.compat import text_
+from pyramid.compat import (
+ lru_cache,
+ text_,
+)
from pyramid.httpexceptions import (
HTTPNotFound,
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 877b351db..7ff4c2f73 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -479,6 +479,7 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True,
config.add_default_view_derivers()
config.add_default_route_predicates()
config.add_default_tweens()
+ config.add_default_security()
config.commit()
global have_zca
try:
@@ -625,7 +626,7 @@ def testConfig(registry=None,
with testConfig() as config:
config.add_route('bar', '/bar/{id}')
req = DummyRequest()
- resp = myview(req),
+ resp = myview(req)
"""
config = setUp(registry=registry,
request=request,
diff --git a/pyramid/tests/pkgs/subrequestapp/__init__.py b/pyramid/tests/pkgs/subrequestapp/__init__.py
index b8f44cd7f..e4b1d386a 100644
--- a/pyramid/tests/pkgs/subrequestapp/__init__.py
+++ b/pyramid/tests/pkgs/subrequestapp/__init__.py
@@ -7,7 +7,8 @@ def view_one(request):
return response
def view_two(request):
- return 'This came from view_two'
+ # check that request.foo is valid for a subrequest
+ return 'This came from view_two, foo=%s' % (request.foo,)
def view_three(request):
subreq = Request.blank('/view_four')
@@ -46,5 +47,6 @@ def main():
config.add_view(view_three, route_name='three')
config.add_view(view_four, route_name='four')
config.add_view(view_five, route_name='five')
+ config.add_request_method(lambda r: 'bar', 'foo', property=True)
return config
diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py
index 452d762f8..eb1f3534c 100644
--- a/pyramid/tests/test_config/test_factories.py
+++ b/pyramid/tests/test_config/test_factories.py
@@ -144,6 +144,24 @@ class TestFactoriesMixin(unittest.TestCase):
self.assertRaises(ConfigurationError, get_bad_name)
+ def test_set_execution_policy(self):
+ from pyramid.interfaces import IExecutionPolicy
+ config = self._makeOne(autocommit=True)
+ def dummy_policy(environ, router): pass
+ config.set_execution_policy(dummy_policy)
+ registry = config.registry
+ result = registry.queryUtility(IExecutionPolicy)
+ self.assertEqual(result, dummy_policy)
+
+ def test_set_execution_policy_to_None(self):
+ from pyramid.interfaces import IExecutionPolicy
+ from pyramid.router import default_execution_policy
+ config = self._makeOne(autocommit=True)
+ config.set_execution_policy(None)
+ registry = config.registry
+ result = registry.queryUtility(IExecutionPolicy)
+ self.assertEqual(result, default_execution_policy)
+
class TestDeprecatedFactoriesMixinMethods(unittest.TestCase):
def setUp(self):
from zope.deprecation import __show__
@@ -203,4 +221,3 @@ class TestDeprecatedFactoriesMixinMethods(unittest.TestCase):
config.set_request_property(bar, name='bar')
self.assertRaises(ConfigurationConflictError, config.commit)
-
diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py
index 0d5413d16..ab584cc3d 100644
--- a/pyramid/tests/test_config/test_init.py
+++ b/pyramid/tests/test_config/test_init.py
@@ -141,6 +141,22 @@ class ConfiguratorTests(unittest.TestCase):
self.assertEqual(manager.pushed, pushed)
self.assertEqual(manager.popped, True)
+ def test_context_manager(self):
+ from pyramid.config import Configurator
+ config = Configurator()
+ manager = DummyThreadLocalManager()
+ config.manager = manager
+ view = lambda r: None
+ with config as ctx:
+ self.assertTrue(config is ctx)
+ self.assertEqual(manager.pushed,
+ {'registry': config.registry, 'request': None})
+ self.assertFalse(manager.popped)
+ config.add_view(view)
+ self.assertTrue(manager.popped)
+ config.add_view(view) # did not raise a conflict because of commit
+ config.commit()
+
def test_ctor_with_package_registry(self):
import sys
from pyramid.config import Configurator
@@ -817,6 +833,16 @@ pyramid.tests.test_config.dummy_include2""",
self.assertEqual(results['root_package'], tests)
self.assertEqual(results['package'], test_config)
+ def test_include_threadlocals_active(self):
+ from pyramid.tests import test_config
+ from pyramid.threadlocal import get_current_registry
+ stack = []
+ def include(config):
+ stack.append(get_current_registry())
+ config = self._makeOne()
+ config.include(include)
+ self.assertTrue(stack[0] is config.registry)
+
def test_action_branching_kw_is_None(self):
config = self._makeOne(autocommit=True)
self.assertEqual(config.action('discrim'), None)
diff --git a/pyramid/tests/test_config/test_settings.py b/pyramid/tests/test_config/test_settings.py
index 2dbe9b1bb..a3afd24e7 100644
--- a/pyramid/tests/test_config/test_settings.py
+++ b/pyramid/tests/test_config/test_settings.py
@@ -1,5 +1,6 @@
import unittest
+
class TestSettingsConfiguratorMixin(unittest.TestCase):
def _makeOne(self, *arg, **kw):
from pyramid.config import Configurator
@@ -11,12 +12,12 @@ class TestSettingsConfiguratorMixin(unittest.TestCase):
settings = config._set_settings(None)
self.assertTrue(settings)
- def test__set_settings_uses_original_dict(self):
+ def test__set_settings_does_not_uses_original_dict(self):
config = self._makeOne()
dummy = {}
result = config._set_settings(dummy)
- self.assertTrue(dummy is result)
- self.assertEqual(dummy['pyramid.debug_all'], False)
+ self.assertTrue(dummy is not result)
+ self.assertNotIn('pyramid.debug_all', dummy)
def test__set_settings_as_dictwithvalues(self):
config = self._makeOne()
@@ -63,6 +64,24 @@ class TestSettingsConfiguratorMixin(unittest.TestCase):
settings = reg.getUtility(ISettings)
self.assertEqual(settings['a'], 1)
+ def test_settings_parameter_dict_is_never_updated(self):
+ class ReadOnlyDict(dict):
+ def __readonly__(self, *args, **kwargs): # pragma: no cover
+ raise RuntimeError("Cannot modify ReadOnlyDict")
+ __setitem__ = __readonly__
+ __delitem__ = __readonly__
+ pop = __readonly__
+ popitem = __readonly__
+ clear = __readonly__
+ update = __readonly__
+ setdefault = __readonly__
+ del __readonly__
+
+ initial = ReadOnlyDict()
+ config = self._makeOne(settings=initial)
+ config._set_settings({'a': '1'})
+
+
class TestSettings(unittest.TestCase):
def _getTargetClass(self):
diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py
index 398b6fba8..bb86a1f56 100644
--- a/pyramid/tests/test_config/test_util.py
+++ b/pyramid/tests/test_config/test_util.py
@@ -365,6 +365,16 @@ class TestPredicateList(unittest.TestCase):
from pyramid.exceptions import ConfigurationError
self.assertRaises(ConfigurationError, self._callFUT, unknown=1)
+ def test_predicate_close_matches(self):
+ from pyramid.exceptions import ConfigurationError
+ with self.assertRaises(ConfigurationError) as context:
+ self._callFUT(method='GET')
+ expected_msg = (
+ "Unknown predicate values: {'method': 'GET'} "
+ "(did you mean request_method)"
+ )
+ self.assertEqual(context.exception.args[0], expected_msg)
+
def test_notted(self):
from pyramid.config import not_
from pyramid.testing import DummyRequest
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 211632730..860254385 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -18,6 +18,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
def _makeOne(self, *arg, **kw):
from pyramid.config import Configurator
config = Configurator(*arg, **kw)
+ config.set_default_csrf_options(require_csrf=False)
return config
def _getViewCallable(self, config, ctx_iface=None, exc_iface=None,
@@ -2373,7 +2374,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
view = lambda r: 'OK'
config.set_default_csrf_options(require_csrf=True)
config.add_view(view, context=Exception, renderer=null_renderer)
- view_intr = introspector.introspectables[1]
+ view_intr = introspector.introspectables[-1]
self.assertTrue(view_intr.type_name, 'view')
self.assertEqual(view_intr['callable'], view)
derived_view = view_intr['derived_callable']
@@ -3410,6 +3411,7 @@ class DummyRequest:
subpath = ()
matchdict = None
request_iface = IRequest
+ application_url = 'http://example.com/foo'
def __init__(self, environ=None):
if environ is None:
diff --git a/pyramid/tests/test_csrf.py b/pyramid/tests/test_csrf.py
new file mode 100644
index 000000000..f01780ad8
--- /dev/null
+++ b/pyramid/tests/test_csrf.py
@@ -0,0 +1,406 @@
+import unittest
+
+from pyramid import testing
+from pyramid.config import Configurator
+
+
+class TestLegacySessionCSRFStoragePolicy(unittest.TestCase):
+ class MockSession(object):
+ def __init__(self, current_token='02821185e4c94269bdc38e6eeae0a2f8'):
+ self.current_token = current_token
+
+ def new_csrf_token(self):
+ self.current_token = 'e5e9e30a08b34ff9842ff7d2b958c14b'
+ return self.current_token
+
+ def get_csrf_token(self):
+ return self.current_token
+
+ def _makeOne(self):
+ from pyramid.csrf import LegacySessionCSRFStoragePolicy
+ return LegacySessionCSRFStoragePolicy()
+
+ def test_register_session_csrf_policy(self):
+ from pyramid.csrf import LegacySessionCSRFStoragePolicy
+ from pyramid.interfaces import ICSRFStoragePolicy
+
+ config = Configurator()
+ config.set_csrf_storage_policy(self._makeOne())
+ config.commit()
+
+ policy = config.registry.queryUtility(ICSRFStoragePolicy)
+
+ self.assertTrue(isinstance(policy, LegacySessionCSRFStoragePolicy))
+
+ def test_session_csrf_implementation_delegates_to_session(self):
+ policy = self._makeOne()
+ request = DummyRequest(session=self.MockSession())
+
+ self.assertEqual(
+ policy.get_csrf_token(request),
+ '02821185e4c94269bdc38e6eeae0a2f8'
+ )
+ self.assertEqual(
+ policy.new_csrf_token(request),
+ 'e5e9e30a08b34ff9842ff7d2b958c14b'
+ )
+
+ def test_check_csrf_token(self):
+ request = DummyRequest(session=self.MockSession('foo'))
+
+ policy = self._makeOne()
+ self.assertTrue(policy.check_csrf_token(request, 'foo'))
+ self.assertFalse(policy.check_csrf_token(request, 'bar'))
+
+
+class TestSessionCSRFStoragePolicy(unittest.TestCase):
+ def _makeOne(self, **kw):
+ from pyramid.csrf import SessionCSRFStoragePolicy
+ return SessionCSRFStoragePolicy(**kw)
+
+ def test_register_session_csrf_policy(self):
+ from pyramid.csrf import SessionCSRFStoragePolicy
+ from pyramid.interfaces import ICSRFStoragePolicy
+
+ config = Configurator()
+ config.set_csrf_storage_policy(self._makeOne())
+ config.commit()
+
+ policy = config.registry.queryUtility(ICSRFStoragePolicy)
+
+ self.assertTrue(isinstance(policy, SessionCSRFStoragePolicy))
+
+ def test_it_creates_a_new_token(self):
+ request = DummyRequest(session={})
+
+ policy = self._makeOne()
+ policy._token_factory = lambda: 'foo'
+ self.assertEqual(policy.get_csrf_token(request), 'foo')
+
+ def test_get_csrf_token_returns_the_new_token(self):
+ request = DummyRequest(session={'_csrft_': 'foo'})
+
+ policy = self._makeOne()
+ self.assertEqual(policy.get_csrf_token(request), 'foo')
+
+ token = policy.new_csrf_token(request)
+ self.assertNotEqual(token, 'foo')
+ self.assertEqual(token, policy.get_csrf_token(request))
+
+ def test_check_csrf_token(self):
+ request = DummyRequest(session={})
+
+ policy = self._makeOne()
+ self.assertFalse(policy.check_csrf_token(request, 'foo'))
+
+ request.session = {'_csrft_': 'foo'}
+ self.assertTrue(policy.check_csrf_token(request, 'foo'))
+ self.assertFalse(policy.check_csrf_token(request, 'bar'))
+
+
+class TestCookieCSRFStoragePolicy(unittest.TestCase):
+ def _makeOne(self, **kw):
+ from pyramid.csrf import CookieCSRFStoragePolicy
+ return CookieCSRFStoragePolicy(**kw)
+
+ def test_register_cookie_csrf_policy(self):
+ from pyramid.csrf import CookieCSRFStoragePolicy
+ from pyramid.interfaces import ICSRFStoragePolicy
+
+ config = Configurator()
+ config.set_csrf_storage_policy(self._makeOne())
+ config.commit()
+
+ policy = config.registry.queryUtility(ICSRFStoragePolicy)
+
+ self.assertTrue(isinstance(policy, CookieCSRFStoragePolicy))
+
+ def test_get_cookie_csrf_with_no_existing_cookie_sets_cookies(self):
+ response = MockResponse()
+ request = DummyRequest()
+
+ policy = self._makeOne()
+ token = policy.get_csrf_token(request)
+ request.response_callback(request, response)
+ self.assertEqual(
+ response.headerlist,
+ [('Set-Cookie', 'csrf_token={}; Path=/'.format(token))]
+ )
+
+ def test_existing_cookie_csrf_does_not_set_cookie(self):
+ request = DummyRequest()
+ request.cookies = {'csrf_token': 'e6f325fee5974f3da4315a8ccf4513d2'}
+
+ policy = self._makeOne()
+ token = policy.get_csrf_token(request)
+
+ self.assertEqual(
+ token,
+ 'e6f325fee5974f3da4315a8ccf4513d2'
+ )
+ self.assertIsNone(request.response_callback)
+
+ def test_new_cookie_csrf_with_existing_cookie_sets_cookies(self):
+ request = DummyRequest()
+ request.cookies = {'csrf_token': 'e6f325fee5974f3da4315a8ccf4513d2'}
+
+ policy = self._makeOne()
+ token = policy.new_csrf_token(request)
+
+ response = MockResponse()
+ request.response_callback(request, response)
+ self.assertEqual(
+ response.headerlist,
+ [('Set-Cookie', 'csrf_token={}; Path=/'.format(token))]
+ )
+
+ def test_get_csrf_token_returns_the_new_token(self):
+ request = DummyRequest()
+ request.cookies = {'csrf_token': 'foo'}
+
+ policy = self._makeOne()
+ self.assertEqual(policy.get_csrf_token(request), 'foo')
+
+ token = policy.new_csrf_token(request)
+ self.assertNotEqual(token, 'foo')
+ self.assertEqual(token, policy.get_csrf_token(request))
+
+ def test_check_csrf_token(self):
+ request = DummyRequest()
+
+ policy = self._makeOne()
+ self.assertFalse(policy.check_csrf_token(request, 'foo'))
+
+ request.cookies = {'csrf_token': 'foo'}
+ self.assertTrue(policy.check_csrf_token(request, 'foo'))
+ self.assertFalse(policy.check_csrf_token(request, 'bar'))
+
+class Test_get_csrf_token(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def _callFUT(self, *args, **kwargs):
+ from pyramid.csrf import get_csrf_token
+ return get_csrf_token(*args, **kwargs)
+
+ def test_no_override_csrf_utility_registered(self):
+ request = testing.DummyRequest()
+ self._callFUT(request)
+
+ def test_success(self):
+ self.config.set_csrf_storage_policy(DummyCSRF())
+ request = testing.DummyRequest()
+
+ csrf_token = self._callFUT(request)
+
+ self.assertEquals(csrf_token, '02821185e4c94269bdc38e6eeae0a2f8')
+
+
+class Test_new_csrf_token(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def _callFUT(self, *args, **kwargs):
+ from pyramid.csrf import new_csrf_token
+ return new_csrf_token(*args, **kwargs)
+
+ def test_no_override_csrf_utility_registered(self):
+ request = testing.DummyRequest()
+ self._callFUT(request)
+
+ def test_success(self):
+ self.config.set_csrf_storage_policy(DummyCSRF())
+ request = testing.DummyRequest()
+
+ csrf_token = self._callFUT(request)
+
+ self.assertEquals(csrf_token, 'e5e9e30a08b34ff9842ff7d2b958c14b')
+
+
+class Test_check_csrf_token(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ # set up CSRF
+ self.config.set_default_csrf_options(require_csrf=False)
+
+ def _callFUT(self, *args, **kwargs):
+ from ..csrf import check_csrf_token
+ return check_csrf_token(*args, **kwargs)
+
+ def test_success_token(self):
+ request = testing.DummyRequest()
+ request.method = "POST"
+ request.POST = {'csrf_token': request.session.get_csrf_token()}
+ self.assertEqual(self._callFUT(request, token='csrf_token'), True)
+
+ def test_success_header(self):
+ request = testing.DummyRequest()
+ request.headers['X-CSRF-Token'] = request.session.get_csrf_token()
+ self.assertEqual(self._callFUT(request, header='X-CSRF-Token'), True)
+
+ def test_success_default_token(self):
+ request = testing.DummyRequest()
+ request.method = "POST"
+ request.POST = {'csrf_token': request.session.get_csrf_token()}
+ self.assertEqual(self._callFUT(request), True)
+
+ def test_success_default_header(self):
+ request = testing.DummyRequest()
+ request.headers['X-CSRF-Token'] = request.session.get_csrf_token()
+ self.assertEqual(self._callFUT(request), True)
+
+ def test_failure_raises(self):
+ from pyramid.exceptions import BadCSRFToken
+ request = testing.DummyRequest()
+ self.assertRaises(BadCSRFToken, self._callFUT, request,
+ 'csrf_token')
+
+ def test_failure_no_raises(self):
+ request = testing.DummyRequest()
+ result = self._callFUT(request, 'csrf_token', raises=False)
+ self.assertEqual(result, False)
+
+
+class Test_check_csrf_token_without_defaults_configured(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def _callFUT(self, *args, **kwargs):
+ from ..csrf import check_csrf_token
+ return check_csrf_token(*args, **kwargs)
+
+ def test_success_token(self):
+ request = testing.DummyRequest()
+ request.method = "POST"
+ request.POST = {'csrf_token': request.session.get_csrf_token()}
+ self.assertEqual(self._callFUT(request, token='csrf_token'), True)
+
+ def test_failure_raises(self):
+ from pyramid.exceptions import BadCSRFToken
+ request = testing.DummyRequest()
+ self.assertRaises(BadCSRFToken, self._callFUT, request,
+ 'csrf_token')
+
+ def test_failure_no_raises(self):
+ request = testing.DummyRequest()
+ result = self._callFUT(request, 'csrf_token', raises=False)
+ self.assertEqual(result, False)
+
+
+class Test_check_csrf_origin(unittest.TestCase):
+ def _callFUT(self, *args, **kwargs):
+ from ..csrf import check_csrf_origin
+ return check_csrf_origin(*args, **kwargs)
+
+ def test_success_with_http(self):
+ request = testing.DummyRequest()
+ request.scheme = "http"
+ self.assertTrue(self._callFUT(request))
+
+ def test_success_with_https_and_referrer(self):
+ request = testing.DummyRequest()
+ request.scheme = "https"
+ request.host = "example.com"
+ request.host_port = "443"
+ request.referrer = "https://example.com/login/"
+ request.registry.settings = {}
+ self.assertTrue(self._callFUT(request))
+
+ def test_success_with_https_and_origin(self):
+ request = testing.DummyRequest()
+ request.scheme = "https"
+ request.host = "example.com"
+ request.host_port = "443"
+ request.headers = {"Origin": "https://example.com/"}
+ request.referrer = "https://not-example.com/"
+ request.registry.settings = {}
+ self.assertTrue(self._callFUT(request))
+
+ def test_success_with_additional_trusted_host(self):
+ request = testing.DummyRequest()
+ request.scheme = "https"
+ request.host = "example.com"
+ request.host_port = "443"
+ request.referrer = "https://not-example.com/login/"
+ request.registry.settings = {
+ "pyramid.csrf_trusted_origins": ["not-example.com"],
+ }
+ self.assertTrue(self._callFUT(request))
+
+ def test_success_with_nonstandard_port(self):
+ request = testing.DummyRequest()
+ request.scheme = "https"
+ request.host = "example.com:8080"
+ request.host_port = "8080"
+ request.referrer = "https://example.com:8080/login/"
+ request.registry.settings = {}
+ self.assertTrue(self._callFUT(request))
+
+ def test_fails_with_wrong_host(self):
+ from pyramid.exceptions import BadCSRFOrigin
+ request = testing.DummyRequest()
+ request.scheme = "https"
+ request.host = "example.com"
+ request.host_port = "443"
+ request.referrer = "https://not-example.com/login/"
+ request.registry.settings = {}
+ self.assertRaises(BadCSRFOrigin, self._callFUT, request)
+ self.assertFalse(self._callFUT(request, raises=False))
+
+ def test_fails_with_no_origin(self):
+ from pyramid.exceptions import BadCSRFOrigin
+ request = testing.DummyRequest()
+ request.scheme = "https"
+ request.referrer = None
+ self.assertRaises(BadCSRFOrigin, self._callFUT, request)
+ self.assertFalse(self._callFUT(request, raises=False))
+
+ def test_fails_when_http_to_https(self):
+ from pyramid.exceptions import BadCSRFOrigin
+ request = testing.DummyRequest()
+ request.scheme = "https"
+ request.host = "example.com"
+ request.host_port = "443"
+ request.referrer = "http://example.com/evil/"
+ request.registry.settings = {}
+ self.assertRaises(BadCSRFOrigin, self._callFUT, request)
+ self.assertFalse(self._callFUT(request, raises=False))
+
+ def test_fails_with_nonstandard_port(self):
+ from pyramid.exceptions import BadCSRFOrigin
+ request = testing.DummyRequest()
+ request.scheme = "https"
+ request.host = "example.com:8080"
+ request.host_port = "8080"
+ request.referrer = "https://example.com/login/"
+ request.registry.settings = {}
+ self.assertRaises(BadCSRFOrigin, self._callFUT, request)
+ self.assertFalse(self._callFUT(request, raises=False))
+
+
+class DummyRequest(object):
+ registry = None
+ session = None
+ response_callback = None
+
+ def __init__(self, registry=None, session=None):
+ self.registry = registry
+ self.session = session
+ self.cookies = {}
+
+ def add_response_callback(self, callback):
+ self.response_callback = callback
+
+
+class MockResponse(object):
+ def __init__(self):
+ self.headerlist = []
+
+
+class DummyCSRF(object):
+ def new_csrf_token(self, request):
+ return 'e5e9e30a08b34ff9842ff7d2b958c14b'
+
+ def get_csrf_token(self, request):
+ return '02821185e4c94269bdc38e6eeae0a2f8'
diff --git a/pyramid/tests/test_encode.py b/pyramid/tests/test_encode.py
index 8fb766d88..d3a9f7095 100644
--- a/pyramid/tests/test_encode.py
+++ b/pyramid/tests/test_encode.py
@@ -5,9 +5,9 @@ from pyramid.compat import (
)
class UrlEncodeTests(unittest.TestCase):
- def _callFUT(self, query, doseq=False):
+ def _callFUT(self, query, doseq=False, **kw):
from pyramid.encode import urlencode
- return urlencode(query, doseq)
+ return urlencode(query, doseq, **kw)
def test_ascii_only(self):
result = self._callFUT([('a',1), ('b',2)])
@@ -53,6 +53,13 @@ class UrlEncodeTests(unittest.TestCase):
result = self._callFUT([('a', '1'), ('b', None), ('c', None)])
self.assertEqual(result, 'a=1&b=&c=')
+ def test_quote_via(self):
+ def my_quoter(value):
+ return 'xxx' + value
+ result = self._callFUT([('a', '1'), ('b', None), ('c', None)],
+ quote_via=my_quoter)
+ self.assertEqual(result, 'xxxa=xxx1&xxxb=&xxxc=')
+
class URLQuoteTests(unittest.TestCase):
def _callFUT(self, val, safe=''):
from pyramid.encode import url_quote
diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py
index 52e53c34e..4f9011cc0 100644
--- a/pyramid/tests/test_events.py
+++ b/pyramid/tests/test_events.py
@@ -209,7 +209,16 @@ class TestSubscriber(unittest.TestCase):
def foo(): pass
dec(foo)
self.assertEqual(dummy_venusian.attached,
- [(foo, dec.register, 'pyramid')])
+ [(foo, dec.register, 'pyramid', 1)])
+
+ def test___call___with_venusian_args(self):
+ dec = self._makeOne(_category='foo', _depth=1)
+ dummy_venusian = DummyVenusian()
+ dec.venusian = dummy_venusian
+ def foo(): pass
+ dec(foo)
+ self.assertEqual(dummy_venusian.attached,
+ [(foo, dec.register, 'foo', 2)])
def test_regsister_with_predicates(self):
from zope.interface import Interface
@@ -308,8 +317,8 @@ class DummyVenusian(object):
def __init__(self):
self.attached = []
- def attach(self, wrapped, fn, category=None):
- self.attached.append((wrapped, fn, category))
+ def attach(self, wrapped, fn, category=None, depth=None):
+ self.attached.append((wrapped, fn, category, depth))
class Dummy:
pass
diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py
index c2786c391..f23e54609 100644
--- a/pyramid/tests/test_integration.py
+++ b/pyramid/tests/test_integration.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import datetime
+import gc
import locale
import os
import unittest
@@ -8,6 +9,7 @@ import unittest
from pyramid.wsgi import wsgiapp
from pyramid.view import view_config
from pyramid.static import static_view
+from pyramid.testing import skip_on
from pyramid.compat import (
text_,
url_quote,
@@ -610,7 +612,7 @@ class SubrequestAppTest(unittest.TestCase):
def test_one(self):
res = self.testapp.get('/view_one', status=200)
- self.assertTrue(b'This came from view_two' in res.body)
+ self.assertTrue(b'This came from view_two, foo=bar' in res.body)
def test_three(self):
res = self.testapp.get('/view_three', status=500)
@@ -741,3 +743,29 @@ def _assertBody(body, filename):
data = data.replace(b'\r', b'')
data = data.replace(b'\n', b'')
assert(body == data)
+
+
+class MemoryLeaksTest(unittest.TestCase):
+
+ def tearDown(self):
+ import pyramid.config
+ pyramid.config.global_registries.empty()
+
+ def get_gc_count(self):
+ last_collected = 0
+ while True:
+ collected = gc.collect()
+ if collected == last_collected:
+ break
+ last_collected = collected
+ return len(gc.get_objects())
+
+ @skip_on('pypy')
+ def test_memory_leaks(self):
+ from pyramid.config import Configurator
+ Configurator().make_wsgi_app() # Initialize all global objects
+
+ initial_count = self.get_gc_count()
+ Configurator().make_wsgi_app()
+ current_count = self.get_gc_count()
+ self.assertEqual(current_count, initial_count)
diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py
index 22a5cde3d..784458647 100644
--- a/pyramid/tests/test_paster.py
+++ b/pyramid/tests/test_paster.py
@@ -1,58 +1,32 @@
import os
import unittest
+from pyramid.tests.test_scripts.dummy import DummyLoader
here = os.path.dirname(__file__)
class Test_get_app(unittest.TestCase):
- def _callFUT(self, config_file, section_name, **kw):
- from pyramid.paster import get_app
- return get_app(config_file, section_name, **kw)
+ def _callFUT(self, config_file, section_name, options=None, _loader=None):
+ import pyramid.paster
+ old_loader = pyramid.paster.get_config_loader
+ try:
+ if _loader is not None:
+ pyramid.paster.get_config_loader = _loader
+ return pyramid.paster.get_app(config_file, section_name,
+ options=options)
+ finally:
+ pyramid.paster.get_config_loader = old_loader
def test_it(self):
app = DummyApp()
- loadapp = DummyLoadWSGI(app)
- result = self._callFUT('/foo/bar/myapp.ini', 'myapp', loadapp=loadapp)
- self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini')
- self.assertEqual(loadapp.section_name, 'myapp')
- self.assertEqual(loadapp.relative_to, os.getcwd())
- self.assertEqual(result, app)
-
- def test_it_with_hash(self):
- app = DummyApp()
- loadapp = DummyLoadWSGI(app)
+ loader = DummyLoader(app=app)
result = self._callFUT(
- '/foo/bar/myapp.ini#myapp', None, loadapp=loadapp
- )
- self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini')
- self.assertEqual(loadapp.section_name, 'myapp')
- self.assertEqual(loadapp.relative_to, os.getcwd())
- self.assertEqual(result, app)
-
- def test_it_with_hash_and_name_override(self):
- app = DummyApp()
- loadapp = DummyLoadWSGI(app)
- result = self._callFUT(
- '/foo/bar/myapp.ini#myapp', 'yourapp', loadapp=loadapp
- )
- self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini')
- self.assertEqual(loadapp.section_name, 'yourapp')
- self.assertEqual(loadapp.relative_to, os.getcwd())
- self.assertEqual(result, app)
-
- def test_it_with_options(self):
- app = DummyApp()
- loadapp = DummyLoadWSGI(app)
- options = {'a':1}
- result = self._callFUT(
- '/foo/bar/myapp.ini#myapp',
- 'yourapp',
- loadapp=loadapp,
- options=options,
- )
- self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini')
- self.assertEqual(loadapp.section_name, 'yourapp')
- self.assertEqual(loadapp.relative_to, os.getcwd())
- self.assertEqual(loadapp.kw, {'global_conf':options})
+ '/foo/bar/myapp.ini', 'myapp', options={'a': 'b'},
+ _loader=loader)
+ self.assertEqual(loader.uri.path, '/foo/bar/myapp.ini')
+ self.assertEqual(len(loader.calls), 1)
+ self.assertEqual(loader.calls[0]['op'], 'app')
+ self.assertEqual(loader.calls[0]['name'], 'myapp')
+ self.assertEqual(loader.calls[0]['defaults'], {'a': 'b'})
self.assertEqual(result, app)
def test_it_with_dummyapp_requiring_options(self):
@@ -63,38 +37,28 @@ class Test_get_app(unittest.TestCase):
self.assertEqual(app.settings['foo'], 'baz')
class Test_get_appsettings(unittest.TestCase):
- def _callFUT(self, config_file, section_name, **kw):
- from pyramid.paster import get_appsettings
- return get_appsettings(config_file, section_name, **kw)
+ def _callFUT(self, config_file, section_name, options=None, _loader=None):
+ import pyramid.paster
+ old_loader = pyramid.paster.get_config_loader
+ try:
+ if _loader is not None:
+ pyramid.paster.get_config_loader = _loader
+ return pyramid.paster.get_appsettings(config_file, section_name,
+ options=options)
+ finally:
+ pyramid.paster.get_config_loader = old_loader
def test_it(self):
- values = {'a':1}
- appconfig = DummyLoadWSGI(values)
- result = self._callFUT('/foo/bar/myapp.ini', 'myapp',
- appconfig=appconfig)
- self.assertEqual(appconfig.config_name, 'config:/foo/bar/myapp.ini')
- self.assertEqual(appconfig.section_name, 'myapp')
- self.assertEqual(appconfig.relative_to, os.getcwd())
- self.assertEqual(result, values)
-
- def test_it_with_hash(self):
- values = {'a':1}
- appconfig = DummyLoadWSGI(values)
- result = self._callFUT('/foo/bar/myapp.ini#myapp', None,
- appconfig=appconfig)
- self.assertEqual(appconfig.config_name, 'config:/foo/bar/myapp.ini')
- self.assertEqual(appconfig.section_name, 'myapp')
- self.assertEqual(appconfig.relative_to, os.getcwd())
- self.assertEqual(result, values)
-
- def test_it_with_hash_and_name_override(self):
- values = {'a':1}
- appconfig = DummyLoadWSGI(values)
- result = self._callFUT('/foo/bar/myapp.ini#myapp', 'yourapp',
- appconfig=appconfig)
- self.assertEqual(appconfig.config_name, 'config:/foo/bar/myapp.ini')
- self.assertEqual(appconfig.section_name, 'yourapp')
- self.assertEqual(appconfig.relative_to, os.getcwd())
+ values = {'a': 1}
+ loader = DummyLoader(app_settings=values)
+ result = self._callFUT(
+ '/foo/bar/myapp.ini', 'myapp', options={'a': 'b'},
+ _loader=loader)
+ self.assertEqual(loader.uri.path, '/foo/bar/myapp.ini')
+ self.assertEqual(len(loader.calls), 1)
+ self.assertEqual(loader.calls[0]['op'], 'app_settings')
+ self.assertEqual(loader.calls[0]['name'], 'myapp')
+ self.assertEqual(loader.calls[0]['defaults'], {'a': 'b'})
self.assertEqual(result, values)
def test_it_with_dummyapp_requiring_options(self):
@@ -105,40 +69,39 @@ class Test_get_appsettings(unittest.TestCase):
self.assertEqual(result['foo'], 'baz')
class Test_setup_logging(unittest.TestCase):
- def _callFUT(self, config_file, global_conf=None):
- from pyramid.paster import setup_logging
- dummy_cp = DummyConfigParserModule
- return setup_logging(
- config_uri=config_file,
- global_conf=global_conf,
- fileConfig=self.fileConfig,
- configparser=dummy_cp,
- )
+ def _callFUT(self, config_file, global_conf=None, _loader=None):
+ import pyramid.paster
+ old_loader = pyramid.paster.get_config_loader
+ try:
+ if _loader is not None:
+ pyramid.paster.get_config_loader = _loader
+ return pyramid.paster.setup_logging(config_file, global_conf)
+ finally:
+ pyramid.paster.get_config_loader = old_loader
def test_it_no_global_conf(self):
- config_file, dict = self._callFUT('/abc')
- # os.path.abspath is a sop to Windows
- self.assertEqual(config_file, os.path.abspath('/abc'))
- self.assertEqual(dict['__file__'], os.path.abspath('/abc'))
- self.assertEqual(dict['here'], os.path.abspath('/'))
+ loader = DummyLoader()
+ self._callFUT('/abc.ini', _loader=loader)
+ self.assertEqual(loader.uri.path, '/abc.ini')
+ self.assertEqual(len(loader.calls), 1)
+ self.assertEqual(loader.calls[0]['op'], 'logging')
+ self.assertEqual(loader.calls[0]['defaults'], None)
def test_it_global_conf_empty(self):
- config_file, dict = self._callFUT('/abc', global_conf={})
- # os.path.abspath is a sop to Windows
- self.assertEqual(config_file, os.path.abspath('/abc'))
- self.assertEqual(dict['__file__'], os.path.abspath('/abc'))
- self.assertEqual(dict['here'], os.path.abspath('/'))
+ loader = DummyLoader()
+ self._callFUT('/abc.ini', global_conf={}, _loader=loader)
+ self.assertEqual(loader.uri.path, '/abc.ini')
+ self.assertEqual(len(loader.calls), 1)
+ self.assertEqual(loader.calls[0]['op'], 'logging')
+ self.assertEqual(loader.calls[0]['defaults'], {})
def test_it_global_conf_not_empty(self):
- config_file, dict = self._callFUT('/abc', global_conf={'key': 'val'})
- # os.path.abspath is a sop to Windows
- self.assertEqual(config_file, os.path.abspath('/abc'))
- self.assertEqual(dict['__file__'], os.path.abspath('/abc'))
- self.assertEqual(dict['here'], os.path.abspath('/'))
- self.assertEqual(dict['key'], 'val')
-
- def fileConfig(self, config_file, dict):
- return config_file, dict
+ loader = DummyLoader()
+ self._callFUT('/abc.ini', global_conf={'key': 'val'}, _loader=loader)
+ self.assertEqual(loader.uri.path, '/abc.ini')
+ self.assertEqual(len(loader.calls), 1)
+ self.assertEqual(loader.calls[0]['op'], 'logging')
+ self.assertEqual(loader.calls[0]['defaults'], {'key': 'val'})
class Test_bootstrap(unittest.TestCase):
def _callFUT(self, config_uri, request=None):
@@ -187,17 +150,6 @@ class DummyRegistry(object):
dummy_registry = DummyRegistry()
-class DummyLoadWSGI:
- def __init__(self, result):
- self.result = result
-
- def __call__(self, config_name, name=None, relative_to=None, **kw):
- self.config_name = config_name
- self.section_name = name
- self.relative_to = relative_to
- self.kw = kw
- return self.result
-
class DummyApp:
def __init__(self):
self.registry = dummy_registry
@@ -214,13 +166,3 @@ class DummyRequest:
def __init__(self, environ):
self.environ = environ
self.matchdict = {}
-
-class DummyConfigParser(object):
- def read(self, x):
- pass
-
- def has_section(self, name):
- return True
-
-class DummyConfigParserModule(object):
- ConfigParser = DummyConfigParser
diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py
index 65bfa5582..86d8b582a 100644
--- a/pyramid/tests/test_renderers.py
+++ b/pyramid/tests/test_renderers.py
@@ -203,6 +203,7 @@ class TestRendererHelper(unittest.TestCase):
self.assertEqual(helper.get_renderer(), factory.respond)
def test_render_view(self):
+ import pyramid.csrf
self._registerRendererFactory()
self._registerResponseFactory()
request = Dummy()
@@ -212,6 +213,9 @@ class TestRendererHelper(unittest.TestCase):
request = testing.DummyRequest()
response = 'response'
response = helper.render_view(request, response, view, context)
+ get_csrf = response.app_iter[1].pop('get_csrf_token')
+ self.assertEqual(get_csrf.args, (request, ))
+ self.assertEqual(get_csrf.func, pyramid.csrf.get_csrf_token)
self.assertEqual(response.app_iter[0], 'response')
self.assertEqual(response.app_iter[1],
{'renderer_info': helper,
@@ -242,12 +246,16 @@ class TestRendererHelper(unittest.TestCase):
self.assertEqual(reg.event.__class__.__name__, 'BeforeRender')
def test_render_system_values_is_None(self):
+ import pyramid.csrf
self._registerRendererFactory()
request = Dummy()
context = Dummy()
request.context = context
helper = self._makeOne('loo.foo')
result = helper.render('values', None, request=request)
+ get_csrf = result[1].pop('get_csrf_token')
+ self.assertEqual(get_csrf.args, (request, ))
+ self.assertEqual(get_csrf.func, pyramid.csrf.get_csrf_token)
system = {'request':request,
'context':context,
'renderer_name':'loo.foo',
diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py
index ad55882c9..53e3ce17a 100644
--- a/pyramid/tests/test_response.py
+++ b/pyramid/tests/test_response.py
@@ -136,9 +136,9 @@ class TestResponseAdapter(unittest.TestCase):
def tearDown(self):
self.config.end()
- def _makeOne(self, *types_or_ifaces):
+ def _makeOne(self, *types_or_ifaces, **kw):
from pyramid.response import response_adapter
- return response_adapter(*types_or_ifaces)
+ return response_adapter(*types_or_ifaces, **kw)
def test_register_single(self):
from zope.interface import Interface
@@ -172,7 +172,18 @@ class TestResponseAdapter(unittest.TestCase):
def foo(): pass
dec(foo)
self.assertEqual(dummy_venusian.attached,
- [(foo, dec.register, 'pyramid')])
+ [(foo, dec.register, 'pyramid', 1)])
+
+ def test___call___with_venusian_args(self):
+ from zope.interface import Interface
+ class IFoo(Interface): pass
+ dec = self._makeOne(IFoo, _category='foo', _depth=1)
+ dummy_venusian = DummyVenusian()
+ dec.venusian = dummy_venusian
+ def foo(): pass
+ dec(foo)
+ self.assertEqual(dummy_venusian.attached,
+ [(foo, dec.register, 'foo', 2)])
class TestGetResponseFactory(unittest.TestCase):
@@ -199,5 +210,5 @@ class DummyVenusian(object):
def __init__(self):
self.attached = []
- def attach(self, wrapped, fn, category=None):
- self.attached.append((wrapped, fn, category))
+ def attach(self, wrapped, fn, category=None, depth=None):
+ self.attached.append((wrapped, fn, category, depth))
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index 7aa42804c..6097018f0 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -641,22 +641,6 @@ class TestRouter(unittest.TestCase):
result = router(environ, start_response)
self.assertEqual(result, exception_response.app_iter)
- def test_call_pushes_and_pops_threadlocal_manager(self):
- from pyramid.interfaces import IViewClassifier
- context = DummyContext()
- self._registerTraverserFactory(context)
- response = DummyResponse()
- response.app_iter = ['Hello world']
- view = DummyView(response)
- environ = self._makeEnviron()
- self._registerView(view, '', IViewClassifier, None, None)
- router = self._makeOne()
- start_response = DummyStartResponse()
- router.threadlocal_manager = DummyThreadLocalManager()
- router(environ, start_response)
- self.assertEqual(len(router.threadlocal_manager.pushed), 1)
- self.assertEqual(len(router.threadlocal_manager.popped), 1)
-
def test_call_route_matches_and_has_factory(self):
from pyramid.interfaces import IViewClassifier
logger = self._registerLogger()
@@ -1271,6 +1255,88 @@ class TestRouter(unittest.TestCase):
start_response = DummyStartResponse()
self.assertRaises(PredicateMismatch, router, environ, start_response)
+ def test_custom_execution_policy(self):
+ from pyramid.interfaces import IExecutionPolicy
+ from pyramid.request import Request
+ from pyramid.response import Response
+ registry = self.config.registry
+ def dummy_policy(environ, router):
+ return Response(status=200, body=b'foo')
+ registry.registerUtility(dummy_policy, IExecutionPolicy)
+ router = self._makeOne()
+ resp = Request.blank('/').get_response(router)
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.body, b'foo')
+
+ def test_execution_policy_handles_exception(self):
+ from pyramid.interfaces import IViewClassifier
+ from pyramid.interfaces import IExceptionViewClassifier
+ from pyramid.interfaces import IRequest
+ class Exception1(Exception):
+ pass
+ class Exception2(Exception):
+ pass
+ req_iface = self._registerRouteRequest('foo')
+ self._connectRoute('foo', 'archives/:action/:article', None)
+ view = DummyView(DummyResponse(), raise_exception=Exception1)
+ self._registerView(view, '', IViewClassifier, req_iface, None)
+ exception_view1 = DummyView(DummyResponse(),
+ raise_exception=Exception2)
+ self._registerView(exception_view1, '', IExceptionViewClassifier,
+ IRequest, Exception1)
+ response = DummyResponse()
+ response.app_iter = ["Hello, world"]
+ exception_view2 = DummyView(response)
+ self._registerView(exception_view2, '', IExceptionViewClassifier,
+ IRequest, Exception2)
+ environ = self._makeEnviron(PATH_INFO='/archives/action1/article1')
+ start_response = DummyStartResponse()
+ router = self._makeOne()
+ result = router(environ, start_response)
+ self.assertEqual(result, ["Hello, world"])
+
+ def test_request_context_with_statement(self):
+ from pyramid.threadlocal import get_current_request
+ from pyramid.interfaces import IExecutionPolicy
+ from pyramid.request import Request
+ from pyramid.response import Response
+ registry = self.config.registry
+ result = []
+ def dummy_policy(environ, router):
+ with router.request_context(environ):
+ result.append(get_current_request())
+ result.append(get_current_request())
+ return Response(status=200, body=b'foo')
+ registry.registerUtility(dummy_policy, IExecutionPolicy)
+ router = self._makeOne()
+ resp = Request.blank('/test_path').get_response(router)
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.body, b'foo')
+ self.assertEqual(result[0].path_info, '/test_path')
+ self.assertEqual(result[1], None)
+
+ def test_request_context_manually(self):
+ from pyramid.threadlocal import get_current_request
+ from pyramid.interfaces import IExecutionPolicy
+ from pyramid.request import Request
+ from pyramid.response import Response
+ registry = self.config.registry
+ result = []
+ def dummy_policy(environ, router):
+ ctx = router.request_context(environ)
+ ctx.begin()
+ result.append(get_current_request())
+ ctx.end()
+ result.append(get_current_request())
+ return Response(status=200, body=b'foo')
+ registry.registerUtility(dummy_policy, IExecutionPolicy)
+ router = self._makeOne()
+ resp = Request.blank('/test_path').get_response(router)
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(resp.body, b'foo')
+ self.assertEqual(result[0].path_info, '/test_path')
+ self.assertEqual(result[1], None)
+
class DummyPredicate(object):
def __call__(self, info, request):
return True
@@ -1322,17 +1388,6 @@ class DummyResponse(object):
start_response(self.status, self.headerlist)
return self.app_iter
-class DummyThreadLocalManager:
- def __init__(self):
- self.pushed = []
- self.popped = []
-
- def push(self, val):
- self.pushed.append(val)
-
- def pop(self):
- self.popped.append(True)
-
class DummyAuthenticationPolicy:
pass
diff --git a/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/templates/mytemplate.pt_tmpl b/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/templates/mytemplate.pt_tmpl
index 856bc22e7..f4d98ec29 100644
--- a/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/templates/mytemplate.pt_tmpl
+++ b/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/templates/mytemplate.pt_tmpl
@@ -41,7 +41,7 @@
<h2>Pyramid links</h2>
<ul class="links">
<li>
- <a href="http://pylonsproject.org">Pylons Website</a>
+ <a href="https://pylonsproject.org">Pylons Website</a>
</li>
<li>
<a href="http://docs.pylonsproject.org/projects/pyramid/current/#narrative-documentation">Narrative Documentation</a>
diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py
index ced09d0b0..2d2b0549f 100644
--- a/pyramid/tests/test_scripts/dummy.py
+++ b/pyramid/tests/test_scripts/dummy.py
@@ -81,29 +81,6 @@ class DummyMultiView(object):
self.views = [(None, view, None) for view in views]
self.__request_attrs__ = attrs
-class DummyConfigParser(object):
- def __init__(self, result, defaults=None):
- self.result = result
- self.defaults = defaults
-
- def read(self, filename):
- self.filename = filename
-
- def items(self, section):
- self.section = section
- if self.result is None:
- from pyramid.compat import configparser
- raise configparser.NoSectionError(section)
- return self.result
-
-class DummyConfigParserFactory(object):
- items = None
-
- def __call__(self, defaults=None):
- self.defaults = defaults
- self.parser = DummyConfigParser(self.items, defaults)
- return self.parser
-
class DummyCloser(object):
def __call__(self):
self.called = True
@@ -162,3 +139,50 @@ class DummyPkgResources(object):
def iter_entry_points(self, name):
return self.entry_points
+
+
+class dummy_setup_logging(object):
+ def __call__(self, config_uri, global_conf):
+ self.config_uri = config_uri
+ self.defaults = global_conf
+
+
+class DummyLoader(object):
+ def __init__(self, settings=None, app_settings=None, app=None, server=None):
+ if not settings:
+ settings = {}
+ if not app_settings:
+ app_settings = {}
+ self.settings = settings
+ self.app_settings = app_settings
+ self.app = app
+ self.server = server
+ self.calls = []
+
+ def __call__(self, uri):
+ import plaster
+ self.uri = plaster.parse_uri(uri)
+ return self
+
+ def add_call(self, op, name, defaults):
+ self.calls.append({'op': op, 'name': name, 'defaults': defaults})
+
+ def get_settings(self, name=None, defaults=None):
+ self.add_call('settings', name, defaults)
+ return self.settings.get(name, {})
+
+ def get_wsgi_app(self, name=None, defaults=None):
+ self.add_call('app', name, defaults)
+ return self.app
+
+ def get_wsgi_app_settings(self, name=None, defaults=None):
+ self.add_call('app_settings', name, defaults)
+ return self.app_settings
+
+ def get_wsgi_server(self, name=None, defaults=None):
+ self.add_call('server', name, defaults)
+ return self.server
+
+ def setup_logging(self, defaults):
+ self.add_call('logging', None, defaults)
+ self.defaults = defaults
diff --git a/pyramid/tests/test_scripts/test_prequest.py b/pyramid/tests/test_scripts/test_prequest.py
index 45db0dbaf..75d5cc198 100644
--- a/pyramid/tests/test_scripts/test_prequest.py
+++ b/pyramid/tests/test_scripts/test_prequest.py
@@ -1,4 +1,5 @@
import unittest
+from pyramid.tests.test_scripts import dummy
class TestPRequestCommand(unittest.TestCase):
def _getTargetClass(self):
@@ -7,23 +8,17 @@ class TestPRequestCommand(unittest.TestCase):
def _makeOne(self, argv, headers=None):
cmd = self._getTargetClass()(argv)
- cmd.get_app = self.get_app
- self._headers = headers or []
- self._out = []
- cmd.out = self.out
- return cmd
-
- def get_app(self, spec, app_name=None, options=None):
- self._spec = spec
- self._app_name = app_name
- self._options = options or {}
def helloworld(environ, start_request):
self._environ = environ
self._path_info = environ['PATH_INFO']
- start_request('200 OK', self._headers)
+ start_request('200 OK', headers or [])
return [b'abc']
- return helloworld
+ self.loader = dummy.DummyLoader(app=helloworld)
+ self._out = []
+ cmd._get_config_loader = self.loader
+ cmd.out = self.out
+ return cmd
def out(self, msg):
self._out.append(msg)
@@ -38,8 +33,10 @@ class TestPRequestCommand(unittest.TestCase):
[('Content-Type', 'text/html; charset=UTF-8')])
command.run()
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
+ self.assertEqual(self.loader.uri.path, 'development.ini')
+ self.assertEqual(self.loader.calls[0]['op'], 'logging')
+ self.assertEqual(self.loader.calls[1]['op'], 'app')
+ self.assertEqual(self.loader.calls[1]['name'], None)
self.assertEqual(self._out, ['abc'])
def test_command_path_doesnt_start_with_slash(self):
@@ -47,8 +44,7 @@ class TestPRequestCommand(unittest.TestCase):
[('Content-Type', 'text/html; charset=UTF-8')])
command.run()
self.assertEqual(self._path_info, '/abc')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
+ self.assertEqual(self.loader.uri.path, 'development.ini')
self.assertEqual(self._out, ['abc'])
def test_command_has_bad_config_header(self):
@@ -67,8 +63,6 @@ class TestPRequestCommand(unittest.TestCase):
command.run()
self.assertEqual(self._environ['HTTP_NAME'], 'value')
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
def test_command_w_basic_auth(self):
@@ -81,8 +75,6 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._environ['HTTP_AUTHORIZATION'],
'Basic dXNlcjpwYXNzd29yZA==')
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
def test_command_has_content_type_header_var(self):
@@ -92,8 +84,6 @@ class TestPRequestCommand(unittest.TestCase):
command.run()
self.assertEqual(self._environ['CONTENT_TYPE'], 'app/foo')
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
def test_command_has_multiple_header_vars(self):
@@ -109,8 +99,6 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._environ['HTTP_NAME'], 'value')
self.assertEqual(self._environ['HTTP_NAME2'], 'value2')
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
def test_command_method_get(self):
@@ -119,8 +107,6 @@ class TestPRequestCommand(unittest.TestCase):
command.run()
self.assertEqual(self._environ['REQUEST_METHOD'], 'GET')
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
def test_command_method_post(self):
@@ -134,8 +120,6 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._environ['CONTENT_LENGTH'], '-1')
self.assertEqual(self._environ['wsgi.input'], stdin)
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
def test_command_method_put(self):
@@ -149,8 +133,6 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._environ['CONTENT_LENGTH'], '-1')
self.assertEqual(self._environ['wsgi.input'], stdin)
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
def test_command_method_patch(self):
@@ -164,8 +146,6 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._environ['CONTENT_LENGTH'], '-1')
self.assertEqual(self._environ['wsgi.input'], stdin)
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
def test_command_method_propfind(self):
@@ -178,8 +158,6 @@ class TestPRequestCommand(unittest.TestCase):
command.run()
self.assertEqual(self._environ['REQUEST_METHOD'], 'PROPFIND')
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
def test_command_method_options(self):
@@ -192,8 +170,6 @@ class TestPRequestCommand(unittest.TestCase):
command.run()
self.assertEqual(self._environ['REQUEST_METHOD'], 'OPTIONS')
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
def test_command_with_query_string(self):
@@ -202,8 +178,6 @@ class TestPRequestCommand(unittest.TestCase):
command.run()
self.assertEqual(self._environ['QUERY_STRING'], 'a=1&b=2&c')
self.assertEqual(self._path_info, '/abc')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
def test_command_display_headers(self):
@@ -212,8 +186,6 @@ class TestPRequestCommand(unittest.TestCase):
[('Content-Type', 'text/html; charset=UTF-8')])
command.run()
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(
self._out,
['200 OK', 'Content-Type: text/html; charset=UTF-8', 'abc'])
@@ -223,21 +195,13 @@ class TestPRequestCommand(unittest.TestCase):
headers=[('Content-Type', 'image/jpeg')])
command.run()
self.assertEqual(self._path_info, '/')
- self.assertEqual(self._spec, 'development.ini')
- self.assertEqual(self._app_name, None)
self.assertEqual(self._out, [b'abc'])
def test_command_method_configures_logging(self):
command = self._makeOne(['', 'development.ini', '/'])
- called_args = []
-
- def configure_logging(app_spec):
- called_args.append(app_spec)
-
- command.configure_logging = configure_logging
command.run()
- self.assertEqual(called_args, ['development.ini'])
+ self.assertEqual(self.loader.calls[0]['op'], 'logging')
class Test_main(unittest.TestCase):
diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py
index 74293a112..fab5e163e 100644
--- a/pyramid/tests/test_scripts/test_proutes.py
+++ b/pyramid/tests/test_scripts/test_proutes.py
@@ -19,7 +19,8 @@ class TestPRoutesCommand(unittest.TestCase):
def _makeOne(self):
cmd = self._getTargetClass()([])
- cmd.bootstrap = (dummy.DummyBootstrap(),)
+ cmd.bootstrap = dummy.DummyBootstrap()
+ cmd.get_config_loader = dummy.DummyLoader()
cmd.args.config_uri = '/foo/bar/myapp.ini#myapp'
return cmd
@@ -37,14 +38,15 @@ class TestPRoutesCommand(unittest.TestCase):
def test_good_args(self):
cmd = self._getTargetClass()([])
- cmd.bootstrap = (dummy.DummyBootstrap(),)
+ cmd.bootstrap = dummy.DummyBootstrap()
+ cmd.get_config_loader = dummy.DummyLoader()
cmd.args.config_uri = '/foo/bar/myapp.ini#myapp'
cmd.args.config_args = ('a=1',)
route = dummy.DummyRoute('a', '/a')
mapper = dummy.DummyMapper(route)
cmd._get_mapper = lambda *arg: mapper
registry = self._makeRegistry()
- cmd.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+ cmd.bootstrap = dummy.DummyBootstrap(registry=registry)
L = []
cmd.out = lambda msg: L.append(msg)
cmd.run()
@@ -52,7 +54,8 @@ class TestPRoutesCommand(unittest.TestCase):
def test_bad_args(self):
cmd = self._getTargetClass()([])
- cmd.bootstrap = (dummy.DummyBootstrap(),)
+ cmd.bootstrap = dummy.DummyBootstrap()
+ cmd.get_config_loader = dummy.DummyLoader()
cmd.args.config_uri = '/foo/bar/myapp.ini#myapp'
cmd.args.config_vars = ('a',)
route = dummy.DummyRoute('a', '/a')
@@ -86,7 +89,7 @@ class TestPRoutesCommand(unittest.TestCase):
mapper = dummy.DummyMapper(route)
command._get_mapper = lambda *arg: mapper
registry = self._makeRegistry()
- command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=registry)
L = []
command.out = L.append
@@ -103,7 +106,7 @@ class TestPRoutesCommand(unittest.TestCase):
L = []
command.out = L.append
registry = self._makeRegistry()
- command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -124,7 +127,7 @@ class TestPRoutesCommand(unittest.TestCase):
command._get_mapper = lambda *arg: mapper
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -150,7 +153,7 @@ class TestPRoutesCommand(unittest.TestCase):
command._get_mapper = lambda *arg: mapper
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -190,7 +193,7 @@ class TestPRoutesCommand(unittest.TestCase):
command._get_mapper = lambda *arg: mapper
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -218,7 +221,7 @@ class TestPRoutesCommand(unittest.TestCase):
command = self._makeOne()
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -252,7 +255,7 @@ class TestPRoutesCommand(unittest.TestCase):
command._get_mapper = lambda *arg: mapper
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -288,7 +291,7 @@ class TestPRoutesCommand(unittest.TestCase):
command._get_mapper = lambda *arg: mapper
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -327,7 +330,7 @@ class TestPRoutesCommand(unittest.TestCase):
command = self._makeOne()
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -354,7 +357,7 @@ class TestPRoutesCommand(unittest.TestCase):
command = self._makeOne()
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -382,7 +385,7 @@ class TestPRoutesCommand(unittest.TestCase):
command = self._makeOne()
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -410,7 +413,7 @@ class TestPRoutesCommand(unittest.TestCase):
command = self._makeOne()
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -436,7 +439,7 @@ class TestPRoutesCommand(unittest.TestCase):
command = self._makeOne()
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 5)
@@ -461,7 +464,7 @@ class TestPRoutesCommand(unittest.TestCase):
command = self._makeOne()
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -491,7 +494,7 @@ class TestPRoutesCommand(unittest.TestCase):
command = self._makeOne()
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config2.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config2.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -521,7 +524,7 @@ class TestPRoutesCommand(unittest.TestCase):
command = self._makeOne()
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -551,7 +554,7 @@ class TestPRoutesCommand(unittest.TestCase):
command = self._makeOne()
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -592,7 +595,7 @@ class TestPRoutesCommand(unittest.TestCase):
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -624,7 +627,7 @@ class TestPRoutesCommand(unittest.TestCase):
command.args.format = 'method,name'
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
@@ -654,7 +657,7 @@ class TestPRoutesCommand(unittest.TestCase):
command.args.format = 'predicates,name,pattern'
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
expected = (
"You provided invalid formats ['predicates'], "
"Available formats are ['name', 'pattern', 'view', 'method']"
@@ -682,10 +685,9 @@ class TestPRoutesCommand(unittest.TestCase):
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
- config_factory = dummy.DummyConfigParserFactory()
- command.ConfigParser = config_factory
- config_factory.items = [('format', 'method\nname')]
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
+ command.get_config_loader = dummy.DummyLoader(
+ {'proutes': {'format': 'method\nname'}})
result = command.run()
self.assertEqual(result, 0)
@@ -715,10 +717,9 @@ class TestPRoutesCommand(unittest.TestCase):
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
- config_factory = dummy.DummyConfigParserFactory()
- command.ConfigParser = config_factory
- config_factory.items = [('format', 'method name')]
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
+ command.get_config_loader = dummy.DummyLoader(
+ {'proutes': {'format': 'method name'}})
result = command.run()
self.assertEqual(result, 0)
@@ -748,10 +749,9 @@ class TestPRoutesCommand(unittest.TestCase):
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
- config_factory = dummy.DummyConfigParserFactory()
- command.ConfigParser = config_factory
- config_factory.items = [('format', 'method,name')]
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
+ command.get_config_loader = dummy.DummyLoader(
+ {'proutes': {'format': 'method,name'}})
result = command.run()
self.assertEqual(result, 0)
@@ -771,7 +771,7 @@ class TestPRoutesCommand(unittest.TestCase):
command = self._makeOne()
L = []
command.out = L.append
- command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+ command.bootstrap = dummy.DummyBootstrap(registry=config.registry)
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(len(L), 3)
diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py
index 18b0c84b6..485cf38cb 100644
--- a/pyramid/tests/test_scripts/test_pserve.py
+++ b/pyramid/tests/test_scripts/test_pserve.py
@@ -2,22 +2,18 @@ import os
import unittest
from pyramid.tests.test_scripts import dummy
+
here = os.path.abspath(os.path.dirname(__file__))
+
class TestPServeCommand(unittest.TestCase):
def setUp(self):
from pyramid.compat import NativeIO
self.out_ = NativeIO()
- self.config_factory = dummy.DummyConfigParserFactory()
def out(self, msg):
self.out_.write(msg)
- def _get_server(*args, **kwargs):
- def server(app):
- return ''
- return server
-
def _getTargetClass(self):
from pyramid.scripts.pserve import PServeCommand
return PServeCommand
@@ -27,7 +23,8 @@ class TestPServeCommand(unittest.TestCase):
effargs.extend(args)
cmd = self._getTargetClass()(effargs)
cmd.out = self.out
- cmd.ConfigParser = self.config_factory
+ self.loader = dummy.DummyLoader()
+ cmd._get_config_loader = self.loader
return cmd
def test_run_no_args(self):
@@ -36,46 +33,93 @@ class TestPServeCommand(unittest.TestCase):
self.assertEqual(result, 2)
self.assertEqual(self.out_.getvalue(), 'You must give a config file')
- def test_config_vars_no_command(self):
- inst = self._makeOne()
- inst.args.config_uri = 'foo'
- inst.args.config_vars = ['a=1', 'b=2']
- result = inst.get_config_vars()
- self.assertEqual(result, {'a': '1', 'b': '2'})
-
def test_parse_vars_good(self):
inst = self._makeOne('development.ini', 'a=1', 'b=2')
- inst.loadserver = self._get_server
-
app = dummy.DummyApp()
- def get_app(*args, **kwargs):
- app.global_conf = kwargs.get('global_conf', None)
- inst.loadapp = get_app
+
+ def get_app(name, global_conf):
+ app.name = name
+ app.global_conf = global_conf
+ return app
+ self.loader.get_wsgi_app = get_app
+ self.loader.server = lambda x: x
inst.run()
self.assertEqual(app.global_conf, {'a': '1', 'b': '2'})
def test_parse_vars_bad(self):
inst = self._makeOne('development.ini', 'a')
- inst.loadserver = self._get_server
self.assertRaises(ValueError, inst.run)
def test_config_file_finds_watch_files(self):
inst = self._makeOne('development.ini')
- self.config_factory.items = [(
- 'watch_files',
- 'foo\n/baz\npyramid.tests.test_scripts:*.py',
- )]
- inst.pserve_file_config('/base/path.ini', global_conf={'a': '1'})
- self.assertEqual(self.config_factory.defaults, {
+ loader = self.loader('/base/path.ini')
+ loader.settings = {'pserve': {
+ 'watch_files': 'foo\n/baz\npyramid.tests.test_scripts:*.py',
+ }}
+ inst.pserve_file_config(loader, global_conf={'a': '1'})
+ self.assertEqual(loader.calls[0]['defaults'], {
'a': '1',
- 'here': os.path.abspath('/base'),
})
- self.assertEqual(inst.watch_files, [
+ self.assertEqual(inst.watch_files, set([
os.path.abspath('/base/foo'),
os.path.abspath('/baz'),
os.path.abspath(os.path.join(here, '*.py')),
- ])
+ ]))
+
+ def test_config_file_finds_open_url(self):
+ inst = self._makeOne('development.ini')
+ loader = self.loader('/base/path.ini')
+ loader.settings = {'pserve': {
+ 'open_url': 'http://127.0.0.1:8080/',
+ }}
+ inst.pserve_file_config(loader, global_conf={'a': '1'})
+ self.assertEqual(loader.calls[0]['defaults'], {
+ 'a': '1',
+ })
+ self.assertEqual(inst.open_url, 'http://127.0.0.1:8080/')
+
+ def test_guess_server_url(self):
+ inst = self._makeOne('development.ini')
+ loader = self.loader('/base/path.ini')
+ loader.settings = {'server:foo': {
+ 'port': '8080',
+ }}
+ url = inst.guess_server_url(loader, 'foo', global_conf={'a': '1'})
+ self.assertEqual(loader.calls[0]['defaults'], {
+ 'a': '1',
+ })
+ self.assertEqual(url, 'http://127.0.0.1:8080')
+
+ def test_reload_call_hupper_with_correct_args(self):
+ from pyramid.scripts import pserve
+
+ class AttrDict(dict):
+ def __init__(self, *args, **kwargs):
+ super(AttrDict, self).__init__(*args, **kwargs)
+ self.__dict__ = self
+
+ def dummy_start_reloader(*args, **kwargs):
+ dummy_start_reloader.args = args
+ dummy_start_reloader.kwargs = kwargs
+
+ orig_hupper = pserve.hupper
+ try:
+ pserve.hupper = AttrDict(is_active=lambda: False,
+ start_reloader=dummy_start_reloader)
+
+ inst = self._makeOne('--reload', 'development.ini')
+ inst.run()
+ finally:
+ pserve.hupper = orig_hupper
+
+ self.assertEquals(dummy_start_reloader.args, ('pyramid.scripts.pserve.main',))
+ self.assertEquals(dummy_start_reloader.kwargs, {
+ 'reload_interval': 1,
+ 'verbose': 1,
+ 'worker_kwargs': {'argv': ['pserve', '--reload', 'development.ini'],
+ 'quiet': False}})
+
class Test_main(unittest.TestCase):
def _callFUT(self, argv):
diff --git a/pyramid/tests/test_scripts/test_pshell.py b/pyramid/tests/test_scripts/test_pshell.py
index 303964b2b..ca9eb7af2 100644
--- a/pyramid/tests/test_scripts/test_pshell.py
+++ b/pyramid/tests/test_scripts/test_pshell.py
@@ -8,16 +8,16 @@ class TestPShellCommand(unittest.TestCase):
from pyramid.scripts.pshell import PShellCommand
return PShellCommand
- def _makeOne(self, patch_bootstrap=True, patch_config=True,
+ def _makeOne(self, patch_bootstrap=True, patch_loader=True,
patch_args=True, patch_options=True):
cmd = self._getTargetClass()([])
if patch_bootstrap:
self.bootstrap = dummy.DummyBootstrap()
- cmd.bootstrap = (self.bootstrap,)
- if patch_config:
- self.config_factory = dummy.DummyConfigParserFactory()
- cmd.ConfigParser = self.config_factory
+ cmd.bootstrap = self.bootstrap
+ if patch_loader:
+ self.loader = dummy.DummyLoader()
+ cmd.get_config_loader = self.loader
if patch_args:
class Args(object): pass
self.args = Args()
@@ -46,9 +46,6 @@ class TestPShellCommand(unittest.TestCase):
command.default_runner = shell
command.run()
- self.assertTrue(self.config_factory.parser)
- self.assertEqual(self.config_factory.parser.filename,
- '/foo/bar/myapp.ini')
self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp')
self.assertEqual(shell.env, {
'app':self.bootstrap.app, 'root':self.bootstrap.root,
@@ -79,9 +76,6 @@ class TestPShellCommand(unittest.TestCase):
self.assertEqual(
out_calls, ['could not find a shell named "unknown_python_shell"']
)
- self.assertTrue(self.config_factory.parser)
- self.assertEqual(self.config_factory.parser.filename,
- '/foo/bar/myapp.ini')
self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp')
self.assertTrue(self.bootstrap.closer.called)
@@ -100,9 +94,6 @@ class TestPShellCommand(unittest.TestCase):
command.args.python_shell = 'ipython'
command.run()
- self.assertTrue(self.config_factory.parser)
- self.assertEqual(self.config_factory.parser.filename,
- '/foo/bar/myapp.ini')
self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp')
self.assertEqual(shell.env, {
'app':self.bootstrap.app, 'root':self.bootstrap.root,
@@ -199,12 +190,9 @@ class TestPShellCommand(unittest.TestCase):
command = self._makeOne()
model = dummy.Dummy()
user = dummy.Dummy()
- self.config_factory.items = [('m', model), ('User', user)]
+ self.loader.settings = {'pshell': {'m': model, 'User': user}}
shell = dummy.DummyShell()
command.run(shell)
- self.assertTrue(self.config_factory.parser)
- self.assertEqual(self.config_factory.parser.filename,
- '/foo/bar/myapp.ini')
self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp')
self.assertEqual(shell.env, {
'app':self.bootstrap.app, 'root':self.bootstrap.root,
@@ -223,12 +211,9 @@ class TestPShellCommand(unittest.TestCase):
env['a'] = 1
env['root'] = 'root override'
env['none'] = None
- self.config_factory.items = [('setup', setup)]
+ self.loader.settings = {'pshell': {'setup': setup}}
shell = dummy.DummyShell()
command.run(shell)
- self.assertTrue(self.config_factory.parser)
- self.assertEqual(self.config_factory.parser.filename,
- '/foo/bar/myapp.ini')
self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp')
self.assertEqual(shell.env, {
'app':self.bootstrap.app, 'root':'root override',
@@ -252,12 +237,9 @@ class TestPShellCommand(unittest.TestCase):
'python': dshell,
}
)
- self.config_factory.items = [
- ('default_shell', 'bpython python\nipython')]
+ self.loader.settings = {'pshell': {
+ 'default_shell': 'bpython python\nipython'}}
command.run()
- self.assertTrue(self.config_factory.parser)
- self.assertEqual(self.config_factory.parser.filename,
- '/foo/bar/myapp.ini')
self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp')
self.assertTrue(dshell.called)
@@ -268,12 +250,9 @@ class TestPShellCommand(unittest.TestCase):
env['a'] = 1
env['m'] = 'model override'
env['root'] = 'root override'
- self.config_factory.items = [('setup', setup), ('m', model)]
+ self.loader.settings = {'pshell': {'setup': setup, 'm': model}}
shell = dummy.DummyShell()
command.run(shell)
- self.assertTrue(self.config_factory.parser)
- self.assertEqual(self.config_factory.parser.filename,
- '/foo/bar/myapp.ini')
self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp')
self.assertEqual(shell.env, {
'app':self.bootstrap.app, 'root':'root override',
@@ -291,14 +270,10 @@ class TestPShellCommand(unittest.TestCase):
env['a'] = 1
env['root'] = 'root override'
model = dummy.Dummy()
- self.config_factory.items = [('setup', 'abc'),
- ('m', model)]
+ self.loader.settings = {'pshell': {'setup': 'abc', 'm': model}}
command.args.setup = setup
shell = dummy.DummyShell()
command.run(shell)
- self.assertTrue(self.config_factory.parser)
- self.assertEqual(self.config_factory.parser.filename,
- '/foo/bar/myapp.ini')
self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp')
self.assertEqual(shell.env, {
'app':self.bootstrap.app, 'root':'root override',
@@ -313,13 +288,11 @@ class TestPShellCommand(unittest.TestCase):
def test_command_custom_section_override(self):
command = self._makeOne()
dummy_ = dummy.Dummy()
- self.config_factory.items = [('app', dummy_), ('root', dummy_),
- ('registry', dummy_), ('request', dummy_)]
+ self.loader.settings = {'pshell': {
+ 'app': dummy_, 'root': dummy_, 'registry': dummy_,
+ 'request': dummy_}}
shell = dummy.DummyShell()
command.run(shell)
- self.assertTrue(self.config_factory.parser)
- self.assertEqual(self.config_factory.parser.filename,
- '/foo/bar/myapp.ini')
self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp')
self.assertEqual(shell.env, {
'app':dummy_, 'root':dummy_, 'registry':dummy_, 'request':dummy_,
diff --git a/pyramid/tests/test_scripts/test_ptweens.py b/pyramid/tests/test_scripts/test_ptweens.py
index f63069fed..6907b858d 100644
--- a/pyramid/tests/test_scripts/test_ptweens.py
+++ b/pyramid/tests/test_scripts/test_ptweens.py
@@ -8,7 +8,8 @@ class TestPTweensCommand(unittest.TestCase):
def _makeOne(self):
cmd = self._getTargetClass()([])
- cmd.bootstrap = (dummy.DummyBootstrap(),)
+ cmd.bootstrap = dummy.DummyBootstrap()
+ cmd.setup_logging = dummy.dummy_setup_logging()
cmd.args.config_uri = '/foo/bar/myapp.ini#myapp'
return cmd
diff --git a/pyramid/tests/test_scripts/test_pviews.py b/pyramid/tests/test_scripts/test_pviews.py
index 7bdab5804..6ec9defbd 100644
--- a/pyramid/tests/test_scripts/test_pviews.py
+++ b/pyramid/tests/test_scripts/test_pviews.py
@@ -8,7 +8,8 @@ class TestPViewsCommand(unittest.TestCase):
def _makeOne(self, registry=None):
cmd = self._getTargetClass()([])
- cmd.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+ cmd.bootstrap = dummy.DummyBootstrap(registry=registry)
+ cmd.setup_logging = dummy.dummy_setup_logging()
cmd.args.config_uri = '/foo/bar/myapp.ini#myapp'
return cmd
diff --git a/pyramid/tests/test_security.py b/pyramid/tests/test_security.py
index 6d75ac8e3..1da73ff73 100644
--- a/pyramid/tests/test_security.py
+++ b/pyramid/tests/test_security.py
@@ -16,12 +16,32 @@ class TestAllPermissionsList(unittest.TestCase):
def _makeOne(self):
return self._getTargetClass()()
- def test_it(self):
+ def test_equality_w_self(self):
thing = self._makeOne()
self.assertTrue(thing.__eq__(thing))
- self.assertEqual(thing.__iter__(), ())
+
+ def test_equality_w_other_instances_of_class(self):
+ thing = self._makeOne()
+ other = self._makeOne()
+ self.assertTrue(thing.__eq__(other))
+
+ def test_equality_miss(self):
+ thing = self._makeOne()
+ other = object()
+ self.assertFalse(thing.__eq__(other))
+
+ def test_contains_w_string(self):
+ thing = self._makeOne()
self.assertTrue('anything' in thing)
+ def test_contains_w_object(self):
+ thing = self._makeOne()
+ self.assertTrue(object() in thing)
+
+ def test_iterable(self):
+ thing = self._makeOne()
+ self.assertEqual(list(thing), [])
+
def test_singleton(self):
from pyramid.security import ALL_PERMISSIONS
self.assertEqual(ALL_PERMISSIONS.__class__, self._getTargetClass())
@@ -72,9 +92,11 @@ class TestACLAllowed(unittest.TestCase):
return klass(*arg, **kw)
def test_it(self):
+ from pyramid.security import Allowed
msg = ("ACLAllowed permission 'permission' via ACE 'ace' in ACL 'acl' "
"on context 'ctx' for principals 'principals'")
allowed = self._makeOne('ace', 'acl', 'permission', 'principals', 'ctx')
+ self.assertIsInstance(allowed, Allowed)
self.assertTrue(msg in allowed.msg)
self.assertEqual(allowed, True)
self.assertTrue(allowed)
@@ -92,9 +114,11 @@ class TestACLDenied(unittest.TestCase):
return klass(*arg, **kw)
def test_it(self):
+ from pyramid.security import Denied
msg = ("ACLDenied permission 'permission' via ACE 'ace' in ACL 'acl' "
"on context 'ctx' for principals 'principals'")
denied = self._makeOne('ace', 'acl', 'permission', 'principals', 'ctx')
+ self.assertIsInstance(denied, Denied)
self.assertTrue(msg in denied.msg)
self.assertEqual(denied, False)
self.assertFalse(denied)
diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py
index 3a308d08b..ade602799 100644
--- a/pyramid/tests/test_session.py
+++ b/pyramid/tests/test_session.py
@@ -659,144 +659,6 @@ class Test_signed_deserialize(unittest.TestCase):
result = self._callFUT(serialized, secret.decode('latin-1'))
self.assertEqual(result, '123')
-class Test_check_csrf_token(unittest.TestCase):
- def _callFUT(self, *args, **kwargs):
- from ..session import check_csrf_token
- return check_csrf_token(*args, **kwargs)
-
- def test_success_token(self):
- request = testing.DummyRequest()
- request.method = "POST"
- request.POST = {'csrf_token': request.session.get_csrf_token()}
- self.assertEqual(self._callFUT(request, token='csrf_token'), True)
-
- def test_success_header(self):
- request = testing.DummyRequest()
- request.headers['X-CSRF-Token'] = request.session.get_csrf_token()
- self.assertEqual(self._callFUT(request, header='X-CSRF-Token'), True)
-
- def test_success_default_token(self):
- request = testing.DummyRequest()
- request.method = "POST"
- request.POST = {'csrf_token': request.session.get_csrf_token()}
- self.assertEqual(self._callFUT(request), True)
-
- def test_success_default_header(self):
- request = testing.DummyRequest()
- request.headers['X-CSRF-Token'] = request.session.get_csrf_token()
- self.assertEqual(self._callFUT(request), True)
-
- def test_failure_raises(self):
- from pyramid.exceptions import BadCSRFToken
- request = testing.DummyRequest()
- self.assertRaises(BadCSRFToken, self._callFUT, request,
- 'csrf_token')
-
- def test_failure_no_raises(self):
- request = testing.DummyRequest()
- result = self._callFUT(request, 'csrf_token', raises=False)
- self.assertEqual(result, False)
-
- def test_token_differing_types(self):
- from pyramid.compat import text_
- request = testing.DummyRequest()
- request.method = "POST"
- request.session['_csrft_'] = text_('foo')
- request.POST = {'csrf_token': b'foo'}
- self.assertEqual(self._callFUT(request, token='csrf_token'), True)
-
-
-class Test_check_csrf_origin(unittest.TestCase):
-
- def _callFUT(self, *args, **kwargs):
- from ..session import check_csrf_origin
- return check_csrf_origin(*args, **kwargs)
-
- def test_success_with_http(self):
- request = testing.DummyRequest()
- request.scheme = "http"
- self.assertTrue(self._callFUT(request))
-
- def test_success_with_https_and_referrer(self):
- request = testing.DummyRequest()
- request.scheme = "https"
- request.host = "example.com"
- request.host_port = "443"
- request.referrer = "https://example.com/login/"
- request.registry.settings = {}
- self.assertTrue(self._callFUT(request))
-
- def test_success_with_https_and_origin(self):
- request = testing.DummyRequest()
- request.scheme = "https"
- request.host = "example.com"
- request.host_port = "443"
- request.headers = {"Origin": "https://example.com/"}
- request.referrer = "https://not-example.com/"
- request.registry.settings = {}
- self.assertTrue(self._callFUT(request))
-
- def test_success_with_additional_trusted_host(self):
- request = testing.DummyRequest()
- request.scheme = "https"
- request.host = "example.com"
- request.host_port = "443"
- request.referrer = "https://not-example.com/login/"
- request.registry.settings = {
- "pyramid.csrf_trusted_origins": ["not-example.com"],
- }
- self.assertTrue(self._callFUT(request))
-
- def test_success_with_nonstandard_port(self):
- request = testing.DummyRequest()
- request.scheme = "https"
- request.host = "example.com:8080"
- request.host_port = "8080"
- request.referrer = "https://example.com:8080/login/"
- request.registry.settings = {}
- self.assertTrue(self._callFUT(request))
-
- def test_fails_with_wrong_host(self):
- from pyramid.exceptions import BadCSRFOrigin
- request = testing.DummyRequest()
- request.scheme = "https"
- request.host = "example.com"
- request.host_port = "443"
- request.referrer = "https://not-example.com/login/"
- request.registry.settings = {}
- self.assertRaises(BadCSRFOrigin, self._callFUT, request)
- self.assertFalse(self._callFUT(request, raises=False))
-
- def test_fails_with_no_origin(self):
- from pyramid.exceptions import BadCSRFOrigin
- request = testing.DummyRequest()
- request.scheme = "https"
- request.referrer = None
- self.assertRaises(BadCSRFOrigin, self._callFUT, request)
- self.assertFalse(self._callFUT(request, raises=False))
-
- def test_fails_when_http_to_https(self):
- from pyramid.exceptions import BadCSRFOrigin
- request = testing.DummyRequest()
- request.scheme = "https"
- request.host = "example.com"
- request.host_port = "443"
- request.referrer = "http://example.com/evil/"
- request.registry.settings = {}
- self.assertRaises(BadCSRFOrigin, self._callFUT, request)
- self.assertFalse(self._callFUT(request, raises=False))
-
- def test_fails_with_nonstandard_port(self):
- from pyramid.exceptions import BadCSRFOrigin
- request = testing.DummyRequest()
- request.scheme = "https"
- request.host = "example.com:8080"
- request.host_port = "8080"
- request.referrer = "https://example.com/login/"
- request.registry.settings = {}
- self.assertRaises(BadCSRFOrigin, self._callFUT, request)
- self.assertFalse(self._callFUT(request, raises=False))
-
class DummySerializer(object):
def dumps(self, value):
diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py
index c8eada34c..2e74ad7cf 100644
--- a/pyramid/tests/test_tweens.py
+++ b/pyramid/tests/test_tweens.py
@@ -22,6 +22,8 @@ class Test_excview_tween_factory(unittest.TestCase):
request = DummyRequest()
result = tween(request)
self.assertTrue(result is dummy_response)
+ self.assertIsNone(request.exception)
+ self.assertIsNone(request.exc_info)
def test_it_catches_notfound(self):
from pyramid.request import Request
@@ -31,8 +33,11 @@ class Test_excview_tween_factory(unittest.TestCase):
raise HTTPNotFound
tween = self._makeOne(handler)
request = Request.blank('/')
+ request.registry = self.config.registry
result = tween(request)
self.assertEqual(result.status, '404 Not Found')
+ self.assertIsInstance(request.exception, HTTPNotFound)
+ self.assertEqual(request.exception, request.exc_info[1])
def test_it_catches_with_predicate(self):
from pyramid.request import Request
@@ -44,8 +49,11 @@ class Test_excview_tween_factory(unittest.TestCase):
raise ValueError
tween = self._makeOne(handler)
request = Request.blank('/')
+ request.registry = self.config.registry
result = tween(request)
self.assertTrue(b'foo' in result.body)
+ self.assertIsInstance(request.exception, ValueError)
+ self.assertEqual(request.exception, request.exc_info[1])
def test_it_reraises_on_mismatch(self):
from pyramid.request import Request
@@ -55,8 +63,11 @@ class Test_excview_tween_factory(unittest.TestCase):
raise ValueError
tween = self._makeOne(handler)
request = Request.blank('/')
+ request.registry = self.config.registry
request.method = 'POST'
self.assertRaises(ValueError, lambda: tween(request))
+ self.assertIsNone(request.exception)
+ self.assertIsNone(request.exc_info)
def test_it_reraises_on_no_match(self):
from pyramid.request import Request
@@ -64,10 +75,14 @@ class Test_excview_tween_factory(unittest.TestCase):
raise ValueError
tween = self._makeOne(handler)
request = Request.blank('/')
+ request.registry = self.config.registry
self.assertRaises(ValueError, lambda: tween(request))
+ self.assertIsNone(request.exception)
+ self.assertIsNone(request.exc_info)
class DummyRequest:
- pass
+ exception = None
+ exc_info = None
class DummyResponse:
pass
diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py
index ddf28e0b0..af2e5405c 100644
--- a/pyramid/tests/test_url.py
+++ b/pyramid/tests/test_url.py
@@ -115,6 +115,14 @@ class TestURLMethodsMixin(unittest.TestCase):
self.assertEqual(result,
'http://example.com:5432/context/a')
+ def test_resource_url_with_query_None(self):
+ request = self._makeOne()
+ self._registerResourceURL(request.registry)
+ context = DummyContext()
+ result = request.resource_url(context, 'a', query=None)
+ self.assertEqual(result,
+ 'http://example.com:5432/context/a')
+
def test_resource_url_anchor_is_after_root_when_no_elements(self):
request = self._makeOne()
self._registerResourceURL(request.registry)
@@ -157,6 +165,13 @@ class TestURLMethodsMixin(unittest.TestCase):
self.assertEqual(result,
'http://example.com:5432/context/#%20/%23?&+')
+ def test_resource_url_anchor_is_None(self):
+ request = self._makeOne()
+ self._registerResourceURL(request.registry)
+ context = DummyContext()
+ result = request.resource_url(context, anchor=None)
+ self.assertEqual(result, 'http://example.com:5432/context/')
+
def test_resource_url_no_IResourceURL_registered(self):
# falls back to ResourceURL
root = DummyContext()
@@ -421,6 +436,14 @@ class TestURLMethodsMixin(unittest.TestCase):
self.assertEqual(result,
'http://example.com:5432/1/2/3?a=1#foo')
+ def test_route_url_with_query_None(self):
+ from pyramid.interfaces import IRoutesMapper
+ request = self._makeOne()
+ mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3'))
+ request.registry.registerUtility(mapper, IRoutesMapper)
+ result = request.route_url('flub', a=1, b=2, c=3, _query=None)
+ self.assertEqual(result, 'http://example.com:5432/1/2/3')
+
def test_route_url_with_anchor_binary(self):
from pyramid.interfaces import IRoutesMapper
request = self._makeOne()
@@ -442,6 +465,15 @@ class TestURLMethodsMixin(unittest.TestCase):
self.assertEqual(result,
'http://example.com:5432/1/2/3#La%20Pe%C3%B1a')
+ def test_route_url_with_anchor_None(self):
+ from pyramid.interfaces import IRoutesMapper
+ request = self._makeOne()
+ mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3'))
+ request.registry.registerUtility(mapper, IRoutesMapper)
+ result = request.route_url('flub', _anchor=None)
+
+ self.assertEqual(result, 'http://example.com:5432/1/2/3')
+
def test_route_url_with_query(self):
from pyramid.interfaces import IRoutesMapper
request = self._makeOne()
diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py
index bbf6103f4..ab9de262e 100644
--- a/pyramid/tests/test_util.py
+++ b/pyramid/tests/test_util.py
@@ -293,6 +293,14 @@ class Test_InstancePropertyMixin(unittest.TestCase):
foo.set_property(lambda _: 2, name='x', reify=True)
self.assertEqual(1, foo.x)
+ def test_new_class_keeps_parent_module_name(self):
+ foo = self._makeOne()
+ self.assertEqual(foo.__module__, 'pyramid.tests.test_util')
+ self.assertEqual(foo.__class__.__module__, 'pyramid.tests.test_util')
+ foo.set_property(lambda _: 1, name='x', reify=True)
+ self.assertEqual(foo.__module__, 'pyramid.tests.test_util')
+ self.assertEqual(foo.__class__.__module__, 'pyramid.tests.test_util')
+
class Test_WeakOrderedSet(unittest.TestCase):
def _makeOne(self):
from pyramid.config import WeakOrderedSet
@@ -369,12 +377,16 @@ class Test_strings_differ(unittest.TestCase):
from pyramid.util import strings_differ
return strings_differ(*args, **kw)
- def test_it(self):
+ def test_it_bytes(self):
self.assertFalse(self._callFUT(b'foo', b'foo'))
self.assertTrue(self._callFUT(b'123', b'345'))
self.assertTrue(self._callFUT(b'1234', b'123'))
self.assertTrue(self._callFUT(b'123', b'1234'))
+ def test_it_native_str(self):
+ self.assertFalse(self._callFUT('123', '123'))
+ self.assertTrue(self._callFUT('123', '1234'))
+
def test_it_with_internal_comparator(self):
result = self._callFUT(b'foo', b'foo', compare_digest=None)
self.assertFalse(result)
diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py
index cab42cf48..0124ce632 100644
--- a/pyramid/tests/test_view.py
+++ b/pyramid/tests/test_view.py
@@ -91,6 +91,18 @@ class Test_notfound_view_config(BaseTest, unittest.TestCase):
self.assertEqual(settings[0]['attr'], 'view')
self.assertEqual(settings[0]['_info'], 'codeinfo')
+ def test_call_with_venusian_args(self):
+ decorator = self._makeOne(_depth=1, _category='foo')
+ venusian = DummyVenusian()
+ decorator.venusian = venusian
+ def foo(): pass
+ decorator(foo)
+ attachments = venusian.attachments
+ category = attachments[0][2]
+ depth = attachments[0][3]
+ self.assertEqual(depth, 2)
+ self.assertEqual(category, 'foo')
+
class Test_forbidden_view_config(BaseTest, unittest.TestCase):
def _makeOne(self, **kw):
from pyramid.view import forbidden_view_config
@@ -133,6 +145,18 @@ class Test_forbidden_view_config(BaseTest, unittest.TestCase):
self.assertEqual(settings[0]['attr'], 'view')
self.assertEqual(settings[0]['_info'], 'codeinfo')
+ def test_call_with_venusian_args(self):
+ decorator = self._makeOne(_depth=1, _category='foo')
+ venusian = DummyVenusian()
+ decorator.venusian = venusian
+ def foo(): pass
+ decorator(foo)
+ attachments = venusian.attachments
+ category = attachments[0][2]
+ depth = attachments[0][3]
+ self.assertEqual(depth, 2)
+ self.assertEqual(category, 'foo')
+
class Test_exception_view_config(BaseTest, unittest.TestCase):
def _makeOne(self, *args, **kw):
from pyramid.view import exception_view_config
@@ -184,6 +208,18 @@ class Test_exception_view_config(BaseTest, unittest.TestCase):
self.assertEqual(settings[0]['attr'], 'view')
self.assertEqual(settings[0]['_info'], 'codeinfo')
+ def test_call_with_venusian_args(self):
+ decorator = self._makeOne(_depth=1, _category='foo')
+ venusian = DummyVenusian()
+ decorator.venusian = venusian
+ def foo(): pass
+ decorator(foo)
+ attachments = venusian.attachments
+ category = attachments[0][2]
+ depth = attachments[0][3]
+ self.assertEqual(depth, 2)
+ self.assertEqual(category, 'foo')
+
class RenderViewToResponseTests(BaseTest, unittest.TestCase):
def _callFUT(self, *arg, **kw):
from pyramid.view import render_view_to_response
@@ -564,7 +600,29 @@ class TestViewConfigDecorator(unittest.TestCase):
decorator.venusian = venusian
def foo(): pass
decorator(foo)
- self.assertEqual(venusian.depth, 2)
+ attachments = venusian.attachments
+ depth = attachments[0][3]
+ self.assertEqual(depth, 2)
+
+ def test_call_withoutcategory(self):
+ decorator = self._makeOne()
+ venusian = DummyVenusian()
+ decorator.venusian = venusian
+ def foo(): pass
+ decorator(foo)
+ attachments = venusian.attachments
+ category = attachments[0][2]
+ self.assertEqual(category, 'pyramid')
+
+ def test_call_withcategory(self):
+ decorator = self._makeOne(_category='not_pyramid')
+ venusian = DummyVenusian()
+ decorator.venusian = venusian
+ def foo(): pass
+ decorator(foo)
+ attachments = venusian.attachments
+ category = attachments[0][2]
+ self.assertEqual(category, 'not_pyramid')
class Test_append_slash_notfound_view(BaseTest, unittest.TestCase):
def _callFUT(self, context, request):
@@ -778,11 +836,11 @@ class TestViewMethodsMixin(unittest.TestCase):
orig_response = request.response = DummyResponse(b'foo')
try:
raise RuntimeError
- except RuntimeError:
+ except RuntimeError as ex:
response = request.invoke_exception_view()
self.assertEqual(response.app_iter, [b'bar'])
- self.assertTrue(request.exception is orig_exc)
- self.assertTrue(request.exc_info is orig_exc_info)
+ self.assertTrue(request.exception is ex)
+ self.assertTrue(request.exc_info[1] is ex)
self.assertTrue(request.response is orig_response)
else: # pragma: no cover
self.fail()
@@ -790,6 +848,8 @@ class TestViewMethodsMixin(unittest.TestCase):
def test_it_supports_alternate_requests(self):
def exc_view(exc, request):
self.assertTrue(request is other_req)
+ from pyramid.threadlocal import get_current_request
+ self.assertTrue(get_current_request() is other_req)
return DummyResponse(b'foo')
self.config.add_view(exc_view, context=RuntimeError)
request = self._makeOne()
@@ -816,6 +876,23 @@ class TestViewMethodsMixin(unittest.TestCase):
else: # pragma: no cover
self.fail()
+ def test_it_raises_if_no_registry(self):
+ request = self._makeOne()
+ del request.registry
+ from pyramid.threadlocal import manager
+ manager.push({'registry': None, 'request': request})
+ try:
+ raise RuntimeError
+ except RuntimeError:
+ try:
+ request.invoke_exception_view()
+ except RuntimeError as e:
+ self.assertEqual(e.args[0], "Unable to retrieve registry")
+ else: # pragma: no cover
+ self.fail()
+ finally:
+ manager.pop()
+
def test_it_supports_alternate_exc_info(self):
def exc_view(exc, request):
self.assertTrue(request.exc_info is exc_info)
@@ -867,6 +944,18 @@ class TestViewMethodsMixin(unittest.TestCase):
else: # pragma: no cover
self.fail()
+ def test_it_reraises_if_not_found(self):
+ request = self._makeOne()
+ dummy_exc = RuntimeError()
+ try:
+ raise dummy_exc
+ except RuntimeError:
+ self.assertRaises(
+ RuntimeError,
+ lambda: request.invoke_exception_view(reraise=True))
+ else: # pragma: no cover
+ self.fail()
+
def test_it_raises_predicate_mismatch(self):
from pyramid.exceptions import PredicateMismatch
def exc_view(exc, request): pass
@@ -881,6 +970,21 @@ class TestViewMethodsMixin(unittest.TestCase):
else: # pragma: no cover
self.fail()
+ def test_it_reraises_after_predicate_mismatch(self):
+ def exc_view(exc, request): pass
+ self.config.add_view(exc_view, context=Exception, request_method='POST')
+ request = self._makeOne()
+ request.method = 'GET'
+ dummy_exc = RuntimeError()
+ try:
+ raise dummy_exc
+ except RuntimeError:
+ self.assertRaises(
+ RuntimeError,
+ lambda: request.invoke_exception_view(reraise=True))
+ else: # pragma: no cover
+ self.fail()
+
class ExceptionResponse(Exception):
status = '404 Not Found'
app_iter = ['Not Found']
@@ -934,8 +1038,7 @@ class DummyVenusian(object):
self.attachments = []
def attach(self, wrapped, callback, category=None, depth=1):
- self.attachments.append((wrapped, callback, category))
- self.depth = depth
+ self.attachments.append((wrapped, callback, category, depth))
return self.info
class DummyRegistry(object):
@@ -962,7 +1065,7 @@ class DummyVenusianContext(object):
def call_venusian(venusian, context=None):
if context is None:
context = DummyVenusianContext()
- for wrapped, callback, category in venusian.attachments:
+ for wrapped, callback, category, depth in venusian.attachments:
callback(context, None, None)
return context.config
diff --git a/pyramid/tests/test_viewderivers.py b/pyramid/tests/test_viewderivers.py
index 51d0bd367..6b81cc1e5 100644
--- a/pyramid/tests/test_viewderivers.py
+++ b/pyramid/tests/test_viewderivers.py
@@ -12,6 +12,7 @@ class TestDeriveView(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
+ self.config.set_default_csrf_options(require_csrf=False)
def tearDown(self):
self.config = None
diff --git a/pyramid/threadlocal.py b/pyramid/threadlocal.py
index 638f7b7b0..e8f825715 100644
--- a/pyramid/threadlocal.py
+++ b/pyramid/threadlocal.py
@@ -31,12 +31,13 @@ class ThreadLocalManager(threading.local):
self.stack[:] = []
def defaults():
- return {'request':None, 'registry':global_registry}
+ return {'request': None, 'registry': global_registry}
manager = ThreadLocalManager(default=defaults)
def get_current_request():
- """Return the currently active request or ``None`` if no request
+ """
+ Return the currently active request or ``None`` if no request
is currently active.
This function should be used *extremely sparingly*, usually only
@@ -44,11 +45,13 @@ def get_current_request():
``get_current_request`` outside a testing context because its
usage makes it possible to write code that can be neither easily
tested nor scripted.
+
"""
return manager.get()['request']
def get_current_registry(context=None): # context required by getSiteManager API
- """Return the currently active :term:`application registry` or the
+ """
+ Return the currently active :term:`application registry` or the
global application registry if no request is currently active.
This function should be used *extremely sparingly*, usually only
@@ -56,5 +59,25 @@ def get_current_registry(context=None): # context required by getSiteManager API
``get_current_registry`` outside a testing context because its
usage makes it possible to write code that can be neither easily
tested nor scripted.
+
"""
return manager.get()['registry']
+
+class RequestContext(object):
+ def __init__(self, request):
+ self.request = request
+
+ def begin(self):
+ request = self.request
+ registry = request.registry
+ manager.push({'registry': registry, 'request': request})
+ return request
+
+ def end(self):
+ manager.pop()
+
+ def __enter__(self):
+ return self.begin()
+
+ def __exit__(self, *args):
+ self.end()
diff --git a/pyramid/traversal.py b/pyramid/traversal.py
index 641445067..d8f4690fd 100644
--- a/pyramid/traversal.py
+++ b/pyramid/traversal.py
@@ -1,8 +1,6 @@
from zope.interface import implementer
from zope.interface.interfaces import IInterface
-from repoze.lru import lru_cache
-
from pyramid.interfaces import (
IResourceURL,
IRequestFactory,
@@ -20,6 +18,7 @@ from pyramid.compat import (
is_nonstr_iter,
decode_path_info,
unquote_bytes_to_wsgi,
+ lru_cache,
)
from pyramid.encode import url_quote
diff --git a/pyramid/tweens.py b/pyramid/tweens.py
index a842b1133..740b6961c 100644
--- a/pyramid/tweens.py
+++ b/pyramid/tweens.py
@@ -1,66 +1,44 @@
import sys
from pyramid.compat import reraise
-from pyramid.exceptions import PredicateMismatch
-from pyramid.interfaces import (
- IExceptionViewClassifier,
- IRequest,
- )
+from pyramid.httpexceptions import HTTPNotFound
-from zope.interface import providedBy
-from pyramid.view import _call_view
+def _error_handler(request, exc):
+ # NOTE: we do not need to delete exc_info because this function
+ # should never be in the call stack of the exception
+ exc_info = sys.exc_info()
+
+ try:
+ response = request.invoke_exception_view(exc_info)
+ except HTTPNotFound:
+ # re-raise the original exception as no exception views were
+ # able to handle the error
+ reraise(*exc_info)
+
+ return response
def excview_tween_factory(handler, registry):
""" A :term:`tween` factory which produces a tween that catches an
exception raised by downstream tweens (or the main Pyramid request
handler) and, if possible, converts it into a Response using an
- :term:`exception view`."""
+ :term:`exception view`.
+
+ .. versionchanged:: 1.9
+ The ``request.response`` will be remain unchanged even if the tween
+ handles an exception. Previously it was deleted after handling an
+ exception.
+
+ Also, ``request.exception`` and ``request.exc_info`` are only set if
+ the tween handles an exception and returns a response otherwise they
+ are left at their original values.
+
+ """
def excview_tween(request):
- attrs = request.__dict__
try:
response = handler(request)
except Exception as exc:
- # WARNING: do not assign the result of sys.exc_info() to a local
- # var here, doing so will cause a leak. We used to actually
- # explicitly delete both "exception" and "exc_info" from ``attrs``
- # in a ``finally:`` clause below, but now we do not because these
- # attributes are useful to upstream tweens. This actually still
- # apparently causes a reference cycle, but it is broken
- # successfully by the garbage collector (see
- # https://github.com/Pylons/pyramid/issues/1223).
- attrs['exc_info'] = sys.exc_info()
- attrs['exception'] = exc
- # clear old generated request.response, if any; it may
- # have been mutated by the view, and its state is not
- # sane (e.g. caching headers)
- if 'response' in attrs:
- del attrs['response']
- # we use .get instead of .__getitem__ below due to
- # https://github.com/Pylons/pyramid/issues/700
- request_iface = attrs.get('request_iface', IRequest)
- provides = providedBy(exc)
- try:
- response = _call_view(
- registry,
- request,
- exc,
- provides,
- '',
- view_classifier=IExceptionViewClassifier,
- request_iface=request_iface.combined
- )
-
- # if views matched but did not pass predicates, squash the error
- # and re-raise the original exception
- except PredicateMismatch:
- response = None
-
- # re-raise the original exception as no exception views were
- # able to handle the error
- if response is None:
- reraise(*attrs['exc_info'])
-
+ response = _error_handler(request, exc)
return response
return excview_tween
diff --git a/pyramid/url.py b/pyramid/url.py
index 9634f61da..d4b44ef23 100644
--- a/pyramid/url.py
+++ b/pyramid/url.py
@@ -2,8 +2,6 @@
import os
-from repoze.lru import lru_cache
-
from pyramid.interfaces import (
IResourceURL,
IRoutesMapper,
@@ -12,6 +10,7 @@ from pyramid.interfaces import (
from pyramid.compat import (
bytes_,
+ lru_cache,
string_types,
)
from pyramid.encode import (
@@ -31,45 +30,41 @@ from pyramid.traversal import (
QUERY_SAFE = "/?:@!$&'()*+,;=" # RFC 3986
ANCHOR_SAFE = QUERY_SAFE
-def parse_url_overrides(kw):
- """Parse special arguments passed when generating urls.
+def parse_url_overrides(request, kw):
+ """
+ Parse special arguments passed when generating urls.
+
+ The supplied dictionary is mutated when we pop arguments.
+ Returns a 3-tuple of the format:
+
+ ``(app_url, qs, anchor)``.
- The supplied dictionary is mutated, popping arguments as necessary.
- Returns a 6-tuple of the format ``(app_url, scheme, host, port,
- qs, anchor)``.
"""
- anchor = ''
- qs = ''
- app_url = None
- host = None
- scheme = None
- port = None
+ app_url = kw.pop('_app_url', None)
+ scheme = kw.pop('_scheme', None)
+ host = kw.pop('_host', None)
+ port = kw.pop('_port', None)
+ query = kw.pop('_query', '')
+ anchor = kw.pop('_anchor', '')
+
+ if app_url is None:
+ if (scheme is not None or host is not None or port is not None):
+ app_url = request._partial_application_url(scheme, host, port)
+ else:
+ app_url = request.application_url
- if '_query' in kw:
- query = kw.pop('_query')
+ qs = ''
+ if query:
if isinstance(query, string_types):
qs = '?' + url_quote(query, QUERY_SAFE)
- elif query:
+ else:
qs = '?' + urlencode(query, doseq=True)
- if '_anchor' in kw:
- anchor = kw.pop('_anchor')
- anchor = url_quote(anchor, ANCHOR_SAFE)
- anchor = '#' + anchor
+ frag = ''
+ if anchor:
+ frag = '#' + url_quote(anchor, ANCHOR_SAFE)
- if '_app_url' in kw:
- app_url = kw.pop('_app_url')
-
- if '_host' in kw:
- host = kw.pop('_host')
-
- if '_scheme' in kw:
- scheme = kw.pop('_scheme')
-
- if '_port' in kw:
- port = kw.pop('_port')
-
- return app_url, scheme, host, port, qs, anchor
+ return app_url, qs, frag
class URLMethodsMixin(object):
""" Request methods mixin for BaseRequest having to do with URL
@@ -85,6 +80,7 @@ class URLMethodsMixin(object):
passed, the ``port`` value is assumed to ``443``. Likewise, if
``scheme`` is passed as ``http`` and ``port`` is not passed, the
``port`` value is assumed to be ``80``.
+
"""
e = self.environ
if scheme is None:
@@ -191,10 +187,6 @@ class URLMethodsMixin(object):
as values, and a k=v pair will be placed into the query string for
each value.
- .. versionchanged:: 1.5
- Allow the ``_query`` option to be a string to enable alternative
- encodings.
-
If a keyword argument ``_anchor`` is present, its string
representation will be quoted per :rfc:`3986#section-3.5` and used as
a named anchor in the generated URL
@@ -208,10 +200,6 @@ class URLMethodsMixin(object):
``_anchor`` is passed as a Unicode object, it will be converted to
UTF-8 before being appended to the URL.
- .. versionchanged:: 1.5
- The ``_anchor`` option will be escaped instead of using
- its raw string representation.
-
If both ``_anchor`` and ``_query`` are specified, the anchor
element will always follow the query element,
e.g. ``http://example.com?foo=1#bar``.
@@ -252,6 +240,18 @@ class URLMethodsMixin(object):
If the route object which matches the ``route_name`` argument has
a :term:`pregenerator`, the ``*elements`` and ``**kw``
arguments passed to this function might be augmented or changed.
+
+ .. versionchanged:: 1.5
+ Allow the ``_query`` option to be a string to enable alternative
+ encodings.
+
+ The ``_anchor`` option will be escaped instead of using
+ its raw string representation.
+
+ .. versionchanged:: 1.9
+ If ``_query`` or ``_anchor`` are falsey (such as ``None`` or an
+ empty string) they will not be included in the generated url.
+
"""
try:
reg = self.registry
@@ -266,13 +266,7 @@ class URLMethodsMixin(object):
if route.pregenerator is not None:
elements, kw = route.pregenerator(self, elements, kw)
- app_url, scheme, host, port, qs, anchor = parse_url_overrides(kw)
-
- if app_url is None:
- if (scheme is not None or host is not None or port is not None):
- app_url = self._partial_application_url(scheme, host, port)
- else:
- app_url = self.application_url
+ app_url, qs, anchor = parse_url_overrides(self, kw)
path = route.generate(kw) # raises KeyError if generate fails
@@ -311,13 +305,13 @@ class URLMethodsMixin(object):
implemented in terms of :meth:`pyramid.request.Request.route_url`
in just this way. As a result, any ``_app_url`` passed within the
``**kw`` values to ``route_path`` will be ignored.
+
"""
kw['_app_url'] = self.script_name
return self.route_url(route_name, *elements, **kw)
def resource_url(self, resource, *elements, **kw):
"""
-
Generate a string representing the absolute URL of the
:term:`resource` object based on the ``wsgi.url_scheme``,
``HTTP_HOST`` or ``SERVER_NAME`` in the request, plus any
@@ -383,10 +377,6 @@ class URLMethodsMixin(object):
as values, and a k=v pair will be placed into the query string for
each value.
- .. versionchanged:: 1.5
- Allow the ``query`` option to be a string to enable alternative
- encodings.
-
If a keyword argument ``anchor`` is present, its string
representation will be used as a named anchor in the generated URL
(e.g. if ``anchor`` is passed as ``foo`` and the resource URL is
@@ -399,10 +389,6 @@ class URLMethodsMixin(object):
``anchor`` is passed as a Unicode object, it will be converted to
UTF-8 before being appended to the URL.
- .. versionchanged:: 1.5
- The ``anchor`` option will be escaped instead of using
- its raw string representation.
-
If both ``anchor`` and ``query`` are specified, the anchor element
will always follow the query element,
e.g. ``http://example.com?foo=1#bar``.
@@ -431,9 +417,6 @@ class URLMethodsMixin(object):
pass ``app_url=''``. Passing ``app_url=''`` when the resource path is
``/baz/bar`` will return ``/baz/bar``.
- .. versionadded:: 1.3
- ``app_url``
-
If ``app_url`` is passed and any of ``scheme``, ``port``, or ``host``
are also passed, ``app_url`` will take precedence and the values
passed for ``scheme``, ``host``, and/or ``port`` will be ignored.
@@ -445,9 +428,6 @@ class URLMethodsMixin(object):
.. seealso::
See also :ref:`overriding_resource_url_generation`.
-
- .. versionadded:: 1.5
- ``route_name``, ``route_kw``, and ``route_remainder_name``
If ``route_name`` is passed, this function will delegate its URL
production to the ``route_url`` function. Calling
@@ -521,6 +501,23 @@ class URLMethodsMixin(object):
For backwards compatibility purposes, this method is also
aliased as the ``model_url`` method of request.
+
+ .. versionchanged:: 1.3
+ Added the ``app_url`` keyword argument.
+
+ .. versionchanged:: 1.5
+ Allow the ``query`` option to be a string to enable alternative
+ encodings.
+
+ The ``anchor`` option will be escaped instead of using
+ its raw string representation.
+
+ Added the ``route_name``, ``route_kw``, and
+ ``route_remainder_name`` keyword arguments.
+
+ .. versionchanged:: 1.9
+ If ``query`` or ``anchor`` are falsey (such as ``None`` or an
+ empty string) they will not be included in the generated url.
"""
try:
reg = self.registry
@@ -533,13 +530,15 @@ class URLMethodsMixin(object):
virtual_path = getattr(url_adapter, 'virtual_path', None)
- app_url = None
- scheme = None
- host = None
- port = None
+ urlkw = {}
+ for name in (
+ 'app_url', 'scheme', 'host', 'port', 'query', 'anchor'
+ ):
+ val = kw.get(name, None)
+ if val is not None:
+ urlkw['_' + name] = val
if 'route_name' in kw:
- newkw = {}
route_name = kw['route_name']
remainder = getattr(url_adapter, 'virtual_path_tuple', None)
if remainder is None:
@@ -547,39 +546,16 @@ class URLMethodsMixin(object):
# virtual_path_tuple
remainder = tuple(url_adapter.virtual_path.split('/'))
remainder_name = kw.get('route_remainder_name', 'traverse')
- newkw[remainder_name] = remainder
-
- for name in (
- 'app_url', 'scheme', 'host', 'port', 'query', 'anchor'
- ):
- val = kw.get(name, None)
- if val is not None:
- newkw['_' + name] = val
-
+ urlkw[remainder_name] = remainder
+
if 'route_kw' in kw:
route_kw = kw.get('route_kw')
if route_kw is not None:
- newkw.update(route_kw)
-
- return self.route_url(route_name, *elements, **newkw)
+ urlkw.update(route_kw)
- if 'app_url' in kw:
- app_url = kw['app_url']
+ return self.route_url(route_name, *elements, **urlkw)
- if 'scheme' in kw:
- scheme = kw['scheme']
-
- if 'host' in kw:
- host = kw['host']
-
- if 'port' in kw:
- port = kw['port']
-
- if app_url is None:
- if scheme or host or port:
- app_url = self._partial_application_url(scheme, host, port)
- else:
- app_url = self.application_url
+ app_url, qs, anchor = parse_url_overrides(self, urlkw)
resource_url = None
local_url = getattr(resource, '__resource_url__', None)
@@ -600,21 +576,6 @@ class URLMethodsMixin(object):
# __resource_url__ function returned None
resource_url = app_url + virtual_path
- qs = ''
- anchor = ''
-
- if 'query' in kw:
- query = kw['query']
- if isinstance(query, string_types):
- qs = '?' + url_quote(query, QUERY_SAFE)
- elif query:
- qs = '?' + urlencode(query, doseq=True)
-
- if 'anchor' in kw:
- anchor = kw['anchor']
- anchor = url_quote(anchor, ANCHOR_SAFE)
- anchor = '#' + anchor
-
if elements:
suffix = _join_elements(elements)
else:
@@ -720,7 +681,7 @@ class URLMethodsMixin(object):
Calling ``request.static_path(apath)`` is the same as calling
``request.static_url(apath, _app_url=request.script_name)``.
:meth:`pyramid.request.Request.static_path` is, in fact, implemented
- in terms of `:meth:`pyramid.request.Request.static_url` in just this
+ in terms of :meth:`pyramid.request.Request.static_url` in just this
way. As a result, any ``_app_url`` passed within the ``**kw`` values
to ``static_path`` will be ignored.
"""
diff --git a/pyramid/util.py b/pyramid/util.py
index 3337d410d..09a3e530f 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -97,6 +97,9 @@ class InstancePropertyHelper(object):
attrs = dict(properties)
if attrs:
parent = target.__class__
+ # fix the module name so it appears to still be the parent
+ # e.g. pyramid.request instead of pyramid.util
+ attrs.setdefault('__module__', parent.__module__)
newcls = type(parent.__name__, (parent, object), attrs)
# We assign __provides__ and __implemented__ below to prevent a
# memory leak that results from from the usage of this instance's
@@ -231,17 +234,20 @@ class WeakOrderedSet(object):
self._order.remove(oid)
self._order.append(oid)
return
- ref = weakref.ref(item, lambda x: self.remove(item))
+ ref = weakref.ref(item, lambda x: self._remove_by_id(oid))
self._items[oid] = ref
self._order.append(oid)
- def remove(self, item):
+ def _remove_by_id(self, oid):
""" Remove an item from the set."""
- oid = id(item)
if oid in self._items:
del self._items[oid]
self._order.remove(oid)
+ def remove(self, item):
+ """ Remove an item from the set."""
+ self._remove_by_id(id(item))
+
def empty(self):
""" Clear all objects from the set."""
self._items = {}
@@ -578,7 +584,7 @@ def action_method(wrapped):
if last_frame.function == 'extract_stack': # pragma: no cover
f.pop()
info = ActionInfo(*f[-backframes])
- except: # pragma: no cover
+ except Exception: # pragma: no cover
info = ActionInfo(None, 0, '', '')
self._ainfo.append(info)
try:
diff --git a/pyramid/view.py b/pyramid/view.py
index 498bdde45..d1a12df32 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -16,6 +16,7 @@ from pyramid.interfaces import (
)
from pyramid.compat import decode_path_info
+from pyramid.compat import reraise as reraise_
from pyramid.exceptions import (
ConfigurationError,
@@ -28,7 +29,11 @@ from pyramid.httpexceptions import (
default_exceptionresponse_view,
)
-from pyramid.threadlocal import get_current_registry
+from pyramid.threadlocal import (
+ get_current_registry,
+ manager,
+ )
+
from pyramid.util import hide_attrs
_marker = object()
@@ -180,14 +185,22 @@ class view_config(object):
:meth:`pyramid.config.Configurator.add_view`. If any argument is left
out, its default will be the equivalent ``add_view`` default.
- An additional keyword argument named ``_depth`` is provided for people who
- wish to reuse this class from another decorator. The default value is
- ``0`` and should be specified relative to the ``view_config`` invocation.
- It will be passed in to the :term:`venusian` ``attach`` function as the
- depth of the callstack when Venusian checks if the decorator is being used
- in a class or module context. It's not often used, but it can be useful
- in this circumstance. See the ``attach`` function in Venusian for more
- information.
+ Two additional keyword arguments which will be passed to the
+ :term:`venusian` ``attach`` function are ``_depth`` and ``_category``.
+
+ ``_depth`` is provided for people who wish to reuse this class from another
+ decorator. The default value is ``0`` and should be specified relative to
+ the ``view_config`` invocation. It will be passed in to the
+ :term:`venusian` ``attach`` function as the depth of the callstack when
+ Venusian checks if the decorator is being used in a class or module
+ context. It's not often used, but it can be useful in this circumstance.
+
+ ``_category`` sets the decorator category name. It can be useful in
+ combination with the ``category`` argument of ``scan`` to control which
+ views should be processed.
+
+ See the :py:func:`venusian.attach` function in Venusian for more
+ information about the ``_depth`` and ``_category`` arguments.
.. seealso::
@@ -210,12 +223,13 @@ class view_config(object):
def __call__(self, wrapped):
settings = self.__dict__.copy()
depth = settings.pop('_depth', 0)
+ category = settings.pop('_category', 'pyramid')
def callback(context, name, ob):
config = context.config.with_package(info.module)
config.add_view(view=ob, **settings)
- info = self.venusian.attach(wrapped, callback, category='pyramid',
+ info = self.venusian.attach(wrapped, callback, category=category,
depth=depth + 1)
if info.scope == 'class':
@@ -382,10 +396,11 @@ class notfound_view_config(object):
being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will
be used` for the redirect response if a slash-appended route is found.
- .. versionchanged:: 1.6
-
See :ref:`changing_the_notfound_view` for detailed usage information.
+ .. versionchanged:: 1.9.1
+ Added the ``_depth`` and ``_category`` arguments.
+
"""
venusian = venusian
@@ -395,12 +410,15 @@ class notfound_view_config(object):
def __call__(self, wrapped):
settings = self.__dict__.copy()
+ depth = settings.pop('_depth', 0)
+ category = settings.pop('_category', 'pyramid')
def callback(context, name, ob):
config = context.config.with_package(info.module)
config.add_notfound_view(view=ob, **settings)
- info = self.venusian.attach(wrapped, callback, category='pyramid')
+ info = self.venusian.attach(wrapped, callback, category=category,
+ depth=depth + 1)
if info.scope == 'class':
# if the decorator was attached to a method in a class, or
@@ -442,6 +460,9 @@ class forbidden_view_config(object):
See :ref:`changing_the_forbidden_view` for detailed usage information.
+ .. versionchanged:: 1.9.1
+ Added the ``_depth`` and ``_category`` arguments.
+
"""
venusian = venusian
@@ -451,12 +472,15 @@ class forbidden_view_config(object):
def __call__(self, wrapped):
settings = self.__dict__.copy()
+ depth = settings.pop('_depth', 0)
+ category = settings.pop('_category', 'pyramid')
def callback(context, name, ob):
config = context.config.with_package(info.module)
config.add_forbidden_view(view=ob, **settings)
- info = self.venusian.attach(wrapped, callback, category='pyramid')
+ info = self.venusian.attach(wrapped, callback, category=category,
+ depth=depth + 1)
if info.scope == 'class':
# if the decorator was attached to a method in a class, or
@@ -498,6 +522,9 @@ class exception_view_config(object):
:meth:`pyramid.view.view_config`, and each predicate argument restricts
the set of circumstances under which this exception view will be invoked.
+ .. versionchanged:: 1.9.1
+ Added the ``_depth`` and ``_category`` arguments.
+
"""
venusian = venusian
@@ -511,12 +538,15 @@ class exception_view_config(object):
def __call__(self, wrapped):
settings = self.__dict__.copy()
+ depth = settings.pop('_depth', 0)
+ category = settings.pop('_category', 'pyramid')
def callback(context, name, ob):
config = context.config.with_package(info.module)
config.add_exception_view(view=ob, **settings)
- info = self.venusian.attach(wrapped, callback, category='pyramid')
+ info = self.venusian.attach(wrapped, callback, category=category,
+ depth=depth + 1)
if info.scope == 'class':
# if the decorator was attached to a method in a class, or
@@ -626,8 +656,9 @@ class ViewMethodsMixin(object):
self,
exc_info=None,
request=None,
- secure=True
- ):
+ secure=True,
+ reraise=False,
+ ):
""" Executes an exception view related to the request it's called upon.
The arguments it takes are these:
@@ -650,22 +681,40 @@ class ViewMethodsMixin(object):
does not have the appropriate permission, this should be ``True``.
Default: ``True``.
- If called with no arguments, it uses the global exception information
- returned by ``sys.exc_info()`` as ``exc_info``, the request
- object that this method is attached to as the ``request``, and
- ``True`` for ``secure``.
+ ``reraise``
+
+ A boolean indicating whether the original error should be reraised
+ if a :term:`response` object could not be created. If ``False``
+ then an :class:`pyramid.httpexceptions.HTTPNotFound`` exception
+ will be raised. Default: ``False``.
+
+ If a response is generated then ``request.exception`` and
+ ``request.exc_info`` will be left at the values used to render the
+ response. Otherwise the previous values for ``request.exception`` and
+ ``request.exc_info`` will be restored.
- This method returns a :term:`response` object or raises
- :class:`pyramid.httpexceptions.HTTPNotFound` if a matching view cannot
- be found."""
+ .. versionadded:: 1.7
+ .. versionchanged:: 1.9
+ The ``request.exception`` and ``request.exc_info`` properties will
+ reflect the exception used to render the response where previously
+ they were reset to the values prior to invoking the method.
+
+ Also added the ``reraise`` argument.
+
+ """
if request is None:
request = self
registry = getattr(request, 'registry', None)
if registry is None:
registry = get_current_registry()
+
+ if registry is None:
+ raise RuntimeError("Unable to retrieve registry")
+
if exc_info is None:
exc_info = sys.exc_info()
+
exc = exc_info[1]
attrs = request.__dict__
context_iface = providedBy(exc)
@@ -673,23 +722,40 @@ class ViewMethodsMixin(object):
# clear old generated request.response, if any; it may
# have been mutated by the view, and its state is not
# sane (e.g. caching headers)
- with hide_attrs(request, 'exception', 'exc_info', 'response'):
+ with hide_attrs(request, 'response', 'exc_info', 'exception'):
attrs['exception'] = exc
attrs['exc_info'] = exc_info
# we use .get instead of .__getitem__ below due to
# https://github.com/Pylons/pyramid/issues/700
request_iface = attrs.get('request_iface', IRequest)
- response = _call_view(
- registry,
- request,
- exc,
- context_iface,
- '',
- view_types=None,
- view_classifier=IExceptionViewClassifier,
- secure=secure,
- request_iface=request_iface.combined,
- )
- if response is None:
- raise HTTPNotFound
- return response
+
+ manager.push({'request': request, 'registry': registry})
+
+ try:
+ response = _call_view(
+ registry,
+ request,
+ exc,
+ context_iface,
+ '',
+ view_types=None,
+ view_classifier=IExceptionViewClassifier,
+ secure=secure,
+ request_iface=request_iface.combined,
+ )
+ except Exception:
+ if reraise:
+ reraise_(*exc_info)
+ raise
+ finally:
+ manager.pop()
+
+ if response is None:
+ if reraise:
+ reraise_(*exc_info)
+ raise HTTPNotFound
+
+ # successful response, overwrite exception/exc_info
+ attrs['exception'] = exc
+ attrs['exc_info'] = exc_info
+ return response
diff --git a/pyramid/viewderivers.py b/pyramid/viewderivers.py
index 4eb0ce704..d2869b162 100644
--- a/pyramid/viewderivers.py
+++ b/pyramid/viewderivers.py
@@ -6,7 +6,7 @@ from zope.interface import (
)
from pyramid.security import NO_PERMISSION_REQUIRED
-from pyramid.session import (
+from pyramid.csrf import (
check_csrf_origin,
check_csrf_token,
)
diff --git a/setup.cfg b/setup.cfg
index 9a241ddf5..3bf28ee15 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -13,6 +13,9 @@ docs = develop easy_install pyramid[docs]
[bdist_wheel]
universal = 1
+[metadata]
+license_file = LICENSE.txt
+
[flake8]
ignore =
# E121: continuation line under-indented for hanging indent
@@ -63,3 +66,13 @@ ignore =
W391
exclude = pyramid/tests/,pyramid/compat.py,pyramid/resource.py
show-source = True
+
+[check-manifest]
+ignore =
+ .gitignore
+ PKG-INFO
+ *.egg-info
+ *.egg-info/*
+ignore-default-rules = true
+ignore-bad-ideas =
+ pyramid/tests/pkgs/localeapp/*
diff --git a/setup.py b/setup.py
index d9fcec4c8..6fa833737 100644
--- a/setup.py
+++ b/setup.py
@@ -11,41 +11,25 @@
# FITNESS FOR A PARTICULAR PURPOSE
#
##############################################################################
-
-import os
-import sys
-import warnings
-
from setuptools import setup, find_packages
-py_version = sys.version_info[:2]
-
-if (3, 0) <= py_version < (3, 4):
- warnings.warn(
- 'On Python 3, Pyramid only supports Python 3.4 or better',
- UserWarning,
- )
-elif py_version < (2, 7):
- raise RuntimeError('On Python 2, Pyramid requires Python 2.7 or better')
+def readfile(name):
+ with open(name) as f:
+ return f.read()
-here = os.path.abspath(os.path.dirname(__file__))
-try:
- with open(os.path.join(here, 'README.rst')) as f:
- README = f.read()
- with open(os.path.join(here, 'CHANGES.txt')) as f:
- CHANGES = f.read()
-except IOError:
- README = CHANGES = ''
+README = readfile('README.rst')
+CHANGES = readfile('CHANGES.rst')
install_requires = [
'setuptools',
'WebOb >= 1.7.0rc2', # Response.has_body
- 'repoze.lru >= 0.4', # py3 compat
'zope.interface >= 3.8.0', # has zope.interface.registry
'zope.deprecation >= 3.5.0', # py3 compat
'venusian >= 1.0a3', # ``ignore``
'translationstring >= 0.4', # py3 compat
'PasteDeploy >= 1.5.0', # py3 compat
+ 'plaster',
+ 'plaster_pastedeploy',
'hupper',
]
@@ -67,11 +51,11 @@ docs_extras = [
testing_extras = tests_require + [
'nose',
'coverage',
- 'virtualenv', # for scaffolding tests
+ 'virtualenv', # for scaffolding tests
]
setup(name='pyramid',
- version='1.9.dev0',
+ version='1.10.dev0',
description='The Pyramid Web Framework, a Pylons project',
long_description=README + '\n\n' + CHANGES,
classifiers=[
@@ -98,8 +82,10 @@ setup(name='pyramid',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
+ python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*',
install_requires=install_requires,
extras_require={
+ ':python_version<"3.2"': ['repoze.lru >= 0.4'],
'testing': testing_extras,
'docs': docs_extras,
},
diff --git a/tox.ini b/tox.ini
index 242decfc4..4e1b4f728 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,9 +1,8 @@
[tox]
envlist =
- py27,py34,py35,py36,py37,pypy,
- docs,pep8,
- {py2,py3}-cover,coverage,
-skip_missing_interpreters = True
+ lint,
+ py27,py34,py35,py36,pypy,
+ docs,{py2,py3}-cover,coverage,
[testenv]
# Most of these are defaults but if you specify any you can't fall back
@@ -52,14 +51,20 @@ commands =
python pyramid/scaffolds/tests.py
deps = virtualenv
-[testenv:pep8]
+[testenv:lint]
+skip_install = True
basepython = python3.5
commands =
flake8 pyramid/
+ python setup.py check -r -s -m
+ check-manifest
deps =
flake8
+ readme_renderer
+ check-manifest
[testenv:docs]
+skip_install = True
basepython = python3.5
whitelist_externals = make
commands =
@@ -67,6 +72,7 @@ commands =
make -C docs doctest html epub BUILDDIR={envdir} "SPHINXOPTS=-W -E"
[testenv:pdf]
+skip_install = True
basepython = python3.5
whitelist_externals = make
commands =
@@ -93,6 +99,7 @@ setenv =
COVERAGE_FILE=.coverage.py3
[testenv:coverage]
+skip_install = True
basepython = python3.5
commands =
coverage erase