summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml10
-rw-r--r--CHANGES.txt235
-rw-r--r--CONTRIBUTORS.txt16
-rw-r--r--HACKING.txt6
-rw-r--r--HISTORY.txt305
-rw-r--r--README.rst22
-rw-r--r--RELEASING.txt22
-rw-r--r--TODO.txt10
-rw-r--r--appveyor.yml9
-rw-r--r--contributing.md6
-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.rst3
-rw-r--r--docs/api/csrf.rst23
-rw-r--r--docs/api/i18n.rst1
-rw-r--r--docs/api/interfaces.rst6
-rw-r--r--docs/api/paster.rst6
-rw-r--r--docs/api/session.rst4
-rw-r--r--docs/api/view.rst2
-rw-r--r--docs/conf.py23
-rw-r--r--docs/conventions.rst107
-rw-r--r--docs/designdefense.rst2
-rw-r--r--docs/glossary.rst48
-rw-r--r--docs/index.rst11
-rw-r--r--docs/latexindex.rst3
-rw-r--r--docs/narr/MyProject/CHANGES.txt4
-rw-r--r--docs/narr/MyProject/README.txt12
-rw-r--r--docs/narr/MyProject/setup.py49
-rw-r--r--docs/narr/assets.rst2
-rw-r--r--docs/narr/commandline.rst2
-rw-r--r--docs/narr/configuration.rst2
-rw-r--r--docs/narr/cookiecutters.rst22
-rw-r--r--docs/narr/extconfig.rst1
-rw-r--r--docs/narr/extending.rst2
-rw-r--r--docs/narr/hooks.rst28
-rw-r--r--docs/narr/install.rst60
-rw-r--r--docs/narr/introduction.rst43
-rw-r--r--docs/narr/logging.rst112
-rw-r--r--docs/narr/myproject/.coveragerc3
-rw-r--r--docs/narr/myproject/CHANGES.txt4
-rw-r--r--docs/narr/myproject/MANIFEST.in (renamed from docs/narr/MyProject/MANIFEST.in)2
-rw-r--r--docs/narr/myproject/README.txt29
-rw-r--r--docs/narr/myproject/development.ini (renamed from docs/narr/MyProject/development.ini)9
-rw-r--r--docs/narr/myproject/myproject/__init__.py (renamed from docs/narr/MyProject/myproject/__init__.py)2
-rw-r--r--docs/narr/myproject/myproject/static/pyramid-16x16.png (renamed from docs/narr/MyProject/myproject/static/pyramid-16x16.png)bin1319 -> 1319 bytes
-rw-r--r--docs/narr/myproject/myproject/static/pyramid.png (renamed from docs/narr/MyProject/myproject/static/pyramid.png)bin12901 -> 12901 bytes
-rw-r--r--docs/narr/myproject/myproject/static/theme.css (renamed from docs/narr/MyProject/myproject/static/theme.css)8
-rw-r--r--docs/narr/myproject/myproject/templates/layout.jinja2 (renamed from docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt)21
-rw-r--r--docs/narr/myproject/myproject/templates/mytemplate.jinja28
-rw-r--r--docs/narr/myproject/myproject/tests.py (renamed from docs/narr/MyProject/myproject/tests.py)0
-rw-r--r--docs/narr/myproject/myproject/views.py (renamed from docs/narr/MyProject/myproject/views.py)2
-rw-r--r--docs/narr/myproject/production.ini (renamed from docs/quick_tutorial/scaffolds/production.ini)13
-rw-r--r--docs/narr/myproject/pytest.ini3
-rw-r--r--docs/narr/myproject/setup.py51
-rw-r--r--docs/narr/paste.rst40
-rw-r--r--docs/narr/project.pngbin133242 -> 98989 bytes
-rw-r--r--docs/narr/project.rst406
-rw-r--r--docs/narr/scaffolding.rst4
-rw-r--r--docs/narr/security.rst216
-rw-r--r--docs/narr/sessions.rst183
-rw-r--r--docs/narr/startup.rst32
-rw-r--r--docs/narr/templates.rst4
-rw-r--r--docs/narr/testing.rst12
-rw-r--r--docs/narr/upgrading.rst2
-rw-r--r--docs/narr/vhosting.rst14
-rw-r--r--docs/narr/viewconfig.rst8
-rw-r--r--docs/narr/views.rst7
-rw-r--r--docs/narr/webob.rst4
-rw-r--r--docs/pscripts/pcreate.rst10
-rw-r--r--docs/pscripts/pdistreport.rst10
-rw-r--r--docs/pscripts/prequest.rst10
-rw-r--r--docs/pscripts/proutes.rst8
-rw-r--r--docs/pscripts/pserve.rst8
-rw-r--r--docs/pscripts/pshell.rst8
-rw-r--r--docs/pscripts/ptweens.rst8
-rw-r--r--docs/pscripts/pviews.rst8
-rw-r--r--docs/quick_tour.rst297
-rw-r--r--docs/quick_tour/logging/.coveragerc3
-rw-r--r--docs/quick_tour/logging/CHANGES.txt4
-rw-r--r--docs/quick_tour/logging/MANIFEST.in2
-rw-r--r--docs/quick_tour/logging/README.txt29
-rw-r--r--docs/quick_tour/logging/development.ini59
-rw-r--r--docs/quick_tour/logging/hello_world/__init__.py (renamed from docs/quick_tutorial/scaffolds/scaffolds/__init__.py)2
-rw-r--r--docs/quick_tour/logging/hello_world/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/quick_tour/logging/hello_world/static/pyramid.pngbin0 -> 12901 bytes
-rw-r--r--docs/quick_tour/logging/hello_world/static/theme.css154
-rw-r--r--docs/quick_tour/logging/hello_world/templates/layout.jinja2 (renamed from docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt)21
-rw-r--r--docs/quick_tour/logging/hello_world/templates/mytemplate.jinja28
-rw-r--r--docs/quick_tour/logging/hello_world/tests.py29
-rw-r--r--docs/quick_tour/logging/hello_world/views.py9
-rw-r--r--docs/quick_tour/logging/production.ini (renamed from docs/narr/MyProject/production.ini)15
-rw-r--r--docs/quick_tour/logging/pytest.ini3
-rw-r--r--docs/quick_tour/logging/setup.py51
-rw-r--r--docs/quick_tour/package/.coveragerc3
-rw-r--r--docs/quick_tour/package/CHANGES.txt2
-rw-r--r--docs/quick_tour/package/MANIFEST.in2
-rw-r--r--docs/quick_tour/package/README.txt27
-rw-r--r--docs/quick_tour/package/development.ini8
-rw-r--r--docs/quick_tour/package/hello_world/__init__.py24
-rw-r--r--docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.mobin460 -> 0 bytes
-rw-r--r--docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.po21
-rw-r--r--docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.mobin461 -> 0 bytes
-rw-r--r--docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.po21
-rw-r--r--docs/quick_tour/package/hello_world/locale/hello_world.pot21
-rw-r--r--docs/quick_tour/package/hello_world/resources.py8
-rw-r--r--docs/quick_tour/package/hello_world/static/favicon.icobin1406 -> 0 bytes
-rw-r--r--docs/quick_tour/package/hello_world/static/theme.css9
-rw-r--r--docs/quick_tour/package/hello_world/templates/layout.jinja2 (renamed from docs/narr/MyProject/myproject/templates/mytemplate.pt)21
-rw-r--r--docs/quick_tour/package/hello_world/templates/mytemplate.jinja280
-rw-r--r--docs/quick_tour/package/hello_world/tests.py27
-rw-r--r--docs/quick_tour/package/hello_world/views.py14
-rw-r--r--docs/quick_tour/package/message-extraction.ini3
-rw-r--r--docs/quick_tour/package/production.ini53
-rw-r--r--docs/quick_tour/package/pytest.ini3
-rw-r--r--docs/quick_tour/package/setup.cfg28
-rw-r--r--docs/quick_tour/package/setup.py61
-rw-r--r--docs/quick_tour/sessions/.coveragerc3
-rw-r--r--docs/quick_tour/sessions/CHANGES.txt4
-rw-r--r--docs/quick_tour/sessions/MANIFEST.in2
-rw-r--r--docs/quick_tour/sessions/README.txt29
-rw-r--r--docs/quick_tour/sessions/development.ini59
-rw-r--r--docs/quick_tour/sessions/hello_world/__init__.py14
-rw-r--r--docs/quick_tour/sessions/hello_world/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/quick_tour/sessions/hello_world/static/pyramid.pngbin0 -> 12901 bytes
-rw-r--r--docs/quick_tour/sessions/hello_world/static/theme.css154
-rw-r--r--docs/quick_tour/sessions/hello_world/templates/layout.jinja264
-rw-r--r--docs/quick_tour/sessions/hello_world/templates/mytemplate.jinja29
-rw-r--r--docs/quick_tour/sessions/hello_world/tests.py29
-rw-r--r--docs/quick_tour/sessions/hello_world/views.py14
-rw-r--r--docs/quick_tour/sessions/production.ini53
-rw-r--r--docs/quick_tour/sessions/pytest.ini3
-rw-r--r--docs/quick_tour/sessions/setup.py51
-rw-r--r--docs/quick_tour/sqla_demo/.coveragerc3
-rw-r--r--docs/quick_tour/sqla_demo/CHANGES.txt2
-rw-r--r--docs/quick_tour/sqla_demo/MANIFEST.in2
-rw-r--r--docs/quick_tour/sqla_demo/README.txt31
-rw-r--r--docs/quick_tour/sqla_demo/development.ini4
-rw-r--r--docs/quick_tour/sqla_demo/production.ini9
-rw-r--r--docs/quick_tour/sqla_demo/pytest.ini3
-rw-r--r--docs/quick_tour/sqla_demo/setup.cfg27
-rw-r--r--docs/quick_tour/sqla_demo/setup.py63
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/__init__.py5
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py74
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/models/meta.py33
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py5
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/routes.py3
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py18
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/templates/404.jinja28
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja28
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja24
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/tests.py16
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/views/default.py6
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/views/notfound.py7
-rw-r--r--docs/quick_tutorial/authentication/development.ini1
-rw-r--r--docs/quick_tutorial/authorization/development.ini1
-rw-r--r--docs/quick_tutorial/cookiecutters.rst87
-rw-r--r--docs/quick_tutorial/cookiecutters/.coveragerc3
-rw-r--r--docs/quick_tutorial/cookiecutters/CHANGES.txt4
-rw-r--r--docs/quick_tutorial/cookiecutters/MANIFEST.in2
-rw-r--r--docs/quick_tutorial/cookiecutters/README.txt29
-rw-r--r--docs/quick_tutorial/cookiecutters/cc_starter/__init__.py12
-rw-r--r--docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid.pngbin0 -> 12901 bytes
-rw-r--r--docs/quick_tutorial/cookiecutters/cc_starter/static/theme.css154
-rw-r--r--docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja264
-rw-r--r--docs/quick_tutorial/cookiecutters/cc_starter/templates/mytemplate.jinja28
-rw-r--r--docs/quick_tutorial/cookiecutters/cc_starter/tests.py29
-rw-r--r--docs/quick_tutorial/cookiecutters/cc_starter/views.py6
-rw-r--r--docs/quick_tutorial/cookiecutters/development.ini (renamed from docs/quick_tutorial/scaffolds/development.ini)15
-rw-r--r--docs/quick_tutorial/cookiecutters/production.ini53
-rw-r--r--docs/quick_tutorial/cookiecutters/pytest.ini3
-rw-r--r--docs/quick_tutorial/cookiecutters/setup.py51
-rw-r--r--docs/quick_tutorial/databases.rst2
-rw-r--r--docs/quick_tutorial/databases/development.ini1
-rw-r--r--docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt15
-rw-r--r--docs/quick_tutorial/debugtoolbar/development.ini1
-rw-r--r--docs/quick_tutorial/forms/development.ini1
-rw-r--r--docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt15
-rw-r--r--docs/quick_tutorial/functional_testing/development.ini1
-rw-r--r--docs/quick_tutorial/hello_world.rst4
-rw-r--r--docs/quick_tutorial/index.rst2
-rw-r--r--docs/quick_tutorial/ini.rst2
-rw-r--r--docs/quick_tutorial/ini/development.ini1
-rw-r--r--docs/quick_tutorial/jinja2/development.ini1
-rw-r--r--docs/quick_tutorial/json/development.ini1
-rw-r--r--docs/quick_tutorial/logging.rst2
-rw-r--r--docs/quick_tutorial/logging/development.ini1
-rw-r--r--docs/quick_tutorial/more_view_classes/development.ini1
-rw-r--r--docs/quick_tutorial/request_response.rst2
-rw-r--r--docs/quick_tutorial/request_response/development.ini1
-rw-r--r--docs/quick_tutorial/requirements.rst12
-rw-r--r--docs/quick_tutorial/retail_forms/development.ini1
-rw-r--r--docs/quick_tutorial/routing/development.ini1
-rw-r--r--docs/quick_tutorial/scaffolds.rst87
-rw-r--r--docs/quick_tutorial/scaffolds/CHANGES.txt4
-rw-r--r--docs/quick_tutorial/scaffolds/MANIFEST.in2
-rw-r--r--docs/quick_tutorial/scaffolds/README.txt1
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/favicon.icobin1406 -> 0 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.pngbin333 -> 0 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.pngbin203 -> 0 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css8
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.pngbin2797 -> 0 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css372
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.pngbin7044 -> 0 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.pngbin33055 -> 0 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gifbin49 -> 0 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt73
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/tests.py17
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/views.py6
-rw-r--r--docs/quick_tutorial/scaffolds/setup.py42
-rw-r--r--docs/quick_tutorial/sessions/development.ini1
-rw-r--r--docs/quick_tutorial/static_assets/development.ini1
-rw-r--r--docs/quick_tutorial/templating/development.ini1
-rw-r--r--docs/quick_tutorial/tutorial_approach.rst35
-rw-r--r--docs/quick_tutorial/unit_testing.rst2
-rw-r--r--docs/quick_tutorial/unit_testing/development.ini1
-rw-r--r--docs/quick_tutorial/view_classes/development.ini1
-rw-r--r--docs/quick_tutorial/views/development.ini1
-rw-r--r--docs/tutorials/modwsgi/index.rst84
-rw-r--r--docs/tutorials/wiki/authorization.rst88
-rw-r--r--docs/tutorials/wiki/background.rst2
-rw-r--r--docs/tutorials/wiki/basiclayout.rst28
-rw-r--r--docs/tutorials/wiki/definingmodels.rst6
-rw-r--r--docs/tutorials/wiki/definingviews.rst51
-rw-r--r--docs/tutorials/wiki/design.rst7
-rw-r--r--docs/tutorials/wiki/distributing.rst9
-rw-r--r--docs/tutorials/wiki/index.rst7
-rw-r--r--docs/tutorials/wiki/installation.rst270
-rw-r--r--docs/tutorials/wiki/src/authorization/.coveragerc3
-rw-r--r--docs/tutorials/wiki/src/authorization/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki/src/authorization/README.txt27
-rw-r--r--docs/tutorials/wiki/src/authorization/development.ini10
-rw-r--r--docs/tutorials/wiki/src/authorization/production.ini11
-rw-r--r--docs/tutorials/wiki/src/authorization/pytest.ini3
-rw-r--r--docs/tutorials/wiki/src/authorization/setup.py61
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/security.py17
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt1
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt1
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt1
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/tests.py2
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views.py6
-rw-r--r--docs/tutorials/wiki/src/basiclayout/.coveragerc3
-rw-r--r--docs/tutorials/wiki/src/basiclayout/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/README.txt27
-rw-r--r--docs/tutorials/wiki/src/basiclayout/development.ini10
-rw-r--r--docs/tutorials/wiki/src/basiclayout/production.ini11
-rw-r--r--docs/tutorials/wiki/src/basiclayout/pytest.ini3
-rw-r--r--docs/tutorials/wiki/src/basiclayout/setup.py60
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt10
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/tests.py2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/views.py2
-rw-r--r--docs/tutorials/wiki/src/installation/.coveragerc3
-rw-r--r--docs/tutorials/wiki/src/installation/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki/src/installation/README.txt27
-rw-r--r--docs/tutorials/wiki/src/installation/development.ini10
-rw-r--r--docs/tutorials/wiki/src/installation/production.ini11
-rw-r--r--docs/tutorials/wiki/src/installation/pytest.ini3
-rw-r--r--docs/tutorials/wiki/src/installation/setup.py60
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt10
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/tests.py2
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/views.py2
-rw-r--r--docs/tutorials/wiki/src/models/.coveragerc3
-rw-r--r--docs/tutorials/wiki/src/models/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki/src/models/README.txt27
-rw-r--r--docs/tutorials/wiki/src/models/development.ini10
-rw-r--r--docs/tutorials/wiki/src/models/production.ini11
-rw-r--r--docs/tutorials/wiki/src/models/pytest.ini3
-rw-r--r--docs/tutorials/wiki/src/models/setup.py60
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt10
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/tests.py2
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/views.py2
-rw-r--r--docs/tutorials/wiki/src/tests/.coveragerc3
-rw-r--r--docs/tutorials/wiki/src/tests/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki/src/tests/README.txt27
-rw-r--r--docs/tutorials/wiki/src/tests/development.ini10
-rw-r--r--docs/tutorials/wiki/src/tests/production.ini11
-rw-r--r--docs/tutorials/wiki/src/tests/pytest.ini3
-rw-r--r--docs/tutorials/wiki/src/tests/setup.py61
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/security.py17
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt1
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/login.pt1
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/view.pt1
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/tests.py11
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views.py6
-rw-r--r--docs/tutorials/wiki/src/views/.coveragerc3
-rw-r--r--docs/tutorials/wiki/src/views/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki/src/views/README.txt27
-rw-r--r--docs/tutorials/wiki/src/views/development.ini10
-rw-r--r--docs/tutorials/wiki/src/views/production.ini11
-rw-r--r--docs/tutorials/wiki/src/views/pytest.ini3
-rw-r--r--docs/tutorials/wiki/src/views/setup.py60
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/view.pt1
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/tests.py2
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views.py12
-rw-r--r--docs/tutorials/wiki/tests.rst18
-rw-r--r--docs/tutorials/wiki2/authentication.rst2
-rw-r--r--docs/tutorials/wiki2/basiclayout.rst14
-rw-r--r--docs/tutorials/wiki2/definingmodels.rst143
-rw-r--r--docs/tutorials/wiki2/definingviews.rst14
-rw-r--r--docs/tutorials/wiki2/design.rst33
-rw-r--r--docs/tutorials/wiki2/distributing.rst12
-rw-r--r--docs/tutorials/wiki2/index.rst7
-rw-r--r--docs/tutorials/wiki2/installation.rst340
-rw-r--r--docs/tutorials/wiki2/src/authentication/.coveragerc3
-rw-r--r--docs/tutorials/wiki2/src/authentication/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki2/src/authentication/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/authentication/README.txt31
-rw-r--r--docs/tutorials/wiki2/src/authentication/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/authentication/production.ini11
-rw-r--r--docs/tutorials/wiki2/src/authentication/pytest.ini3
-rw-r--r--docs/tutorials/wiki2/src/authentication/setup.py65
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/tests.py2
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/views/default.py6
-rw-r--r--docs/tutorials/wiki2/src/authorization/.coveragerc3
-rw-r--r--docs/tutorials/wiki2/src/authorization/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki2/src/authorization/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/authorization/README.txt31
-rw-r--r--docs/tutorials/wiki2/src/authorization/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/authorization/production.ini11
-rw-r--r--docs/tutorials/wiki2/src/authorization/pytest.ini3
-rw-r--r--docs/tutorials/wiki2/src/authorization/setup.py65
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views/default.py6
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/.coveragerc3
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/README.txt31
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/production.ini11
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/pytest.ini3
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/setup.py65
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja28
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja24
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py2
-rw-r--r--docs/tutorials/wiki2/src/installation/.coveragerc3
-rw-r--r--docs/tutorials/wiki2/src/installation/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki2/src/installation/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/installation/README.txt31
-rw-r--r--docs/tutorials/wiki2/src/installation/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/installation/production.ini11
-rw-r--r--docs/tutorials/wiki2/src/installation/pytest.ini3
-rw-r--r--docs/tutorials/wiki2/src/installation/setup.py65
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py2
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja28
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja24
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/tests.py2
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/views/default.py2
-rw-r--r--docs/tutorials/wiki2/src/models/.coveragerc3
-rw-r--r--docs/tutorials/wiki2/src/models/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki2/src/models/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/models/README.txt31
-rw-r--r--docs/tutorials/wiki2/src/models/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/models/production.ini11
-rw-r--r--docs/tutorials/wiki2/src/models/pytest.ini3
-rw-r--r--docs/tutorials/wiki2/src/models/setup.py65
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja28
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja24
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/tests.py2
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/views/default.py2
-rw-r--r--docs/tutorials/wiki2/src/tests/.coveragerc3
-rw-r--r--docs/tutorials/wiki2/src/tests/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki2/src/tests/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/tests/README.txt31
-rw-r--r--docs/tutorials/wiki2/src/tests/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/tests/production.ini11
-rw-r--r--docs/tutorials/wiki2/src/tests/pytest.ini3
-rw-r--r--docs/tutorials/wiki2/src/tests/setup.py65
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py1
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py12
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests/test_initdb.py16
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests/test_security.py23
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests/test_user_model.py67
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/default.py6
-rw-r--r--docs/tutorials/wiki2/src/views/.coveragerc3
-rw-r--r--docs/tutorials/wiki2/src/views/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki2/src/views/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/views/README.txt31
-rw-r--r--docs/tutorials/wiki2/src/views/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/views/production.ini11
-rw-r--r--docs/tutorials/wiki2/src/views/pytest.ini3
-rw-r--r--docs/tutorials/wiki2/src/views/setup.py65
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja22
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/tests.py2
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views/default.py6
-rw-r--r--docs/tutorials/wiki2/tests.rst43
-rw-r--r--docs/typographical-conventions.rst338
-rw-r--r--docs/whatsnew-1.7.rst2
-rw-r--r--docs/whatsnew-1.8.rst234
-rw-r--r--docs/whatsnew-1.9.rst61
-rw-r--r--pyramid/asset.py2
-rw-r--r--pyramid/authentication.py2
-rw-r--r--pyramid/config/__init__.py99
-rw-r--r--pyramid/config/assets.py8
-rw-r--r--pyramid/config/factories.py25
-rw-r--r--pyramid/config/i18n.py98
-rw-r--r--pyramid/config/predicates.py303
-rw-r--r--pyramid/config/routes.py10
-rw-r--r--pyramid/config/security.py38
-rw-r--r--pyramid/config/settings.py172
-rw-r--r--pyramid/config/tweens.py2
-rw-r--r--pyramid/config/util.py15
-rw-r--r--pyramid/config/views.py38
-rw-r--r--pyramid/csrf.py332
-rw-r--r--pyramid/httpexceptions.py4
-rw-r--r--pyramid/i18n.py12
-rw-r--r--pyramid/interfaces.py138
-rw-r--r--pyramid/paster.py59
-rw-r--r--pyramid/predicates.py300
-rw-r--r--pyramid/registry.py47
-rw-r--r--pyramid/renderers.py5
-rw-r--r--pyramid/response.py25
-rw-r--r--pyramid/router.py46
-rw-r--r--pyramid/scaffolds/__init__.py4
-rw-r--r--pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl2
-rw-r--r--pyramid/scaffolds/alchemy/development.ini_tmpl3
-rw-r--r--pyramid/scaffolds/alchemy/production.ini_tmpl3
-rw-r--r--pyramid/scaffolds/starter/+package+/__init__.py2
-rw-r--r--pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl (renamed from pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl)17
-rw-r--r--pyramid/scaffolds/starter/+package+/templates/mytemplate.jinja2_tmpl8
-rw-r--r--pyramid/scaffolds/starter/+package+/views.py_tmpl2
-rw-r--r--pyramid/scaffolds/starter/MANIFEST.in_tmpl2
-rw-r--r--pyramid/scaffolds/starter/development.ini_tmpl3
-rw-r--r--pyramid/scaffolds/starter/production.ini_tmpl3
-rw-r--r--pyramid/scaffolds/starter/setup.py_tmpl2
-rw-r--r--pyramid/scaffolds/template.py8
-rw-r--r--pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl2
-rw-r--r--pyramid/scaffolds/zodb/development.ini_tmpl3
-rw-r--r--pyramid/scaffolds/zodb/production.ini_tmpl3
-rw-r--r--pyramid/scripts/common.py27
-rw-r--r--pyramid/scripts/pcreate.py181
-rw-r--r--pyramid/scripts/pdistreport.py11
-rw-r--r--pyramid/scripts/prequest.py86
-rw-r--r--pyramid/scripts/proutes.py98
-rw-r--r--pyramid/scripts/pserve.py653
-rw-r--r--pyramid/scripts/pshell.py102
-rw-r--r--pyramid/scripts/ptweens.py35
-rw-r--r--pyramid/scripts/pviews.py46
-rw-r--r--pyramid/session.py169
-rw-r--r--pyramid/static.py17
-rw-r--r--pyramid/testing.py1
-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_i18n.py49
-rw-r--r--pyramid/tests/test_config/test_init.py76
-rw-r--r--pyramid/tests/test_config/test_settings.py58
-rw-r--r--pyramid/tests/test_config/test_util.py11
-rw-r--r--pyramid/tests/test_config/test_views.py7
-rw-r--r--pyramid/tests/test_csrf.py406
-rw-r--r--pyramid/tests/test_httpexceptions.py8
-rw-r--r--pyramid/tests/test_i18n.py30
-rw-r--r--pyramid/tests/test_integration.py30
-rw-r--r--pyramid/tests/test_paster.py190
-rw-r--r--pyramid/tests/test_predicates.py (renamed from pyramid/tests/test_config/test_predicates.py)22
-rw-r--r--pyramid/tests/test_registry.py29
-rw-r--r--pyramid/tests/test_renderers.py8
-rw-r--r--pyramid/tests/test_router.py13
-rw-r--r--pyramid/tests/test_scaffolds/fixture_scaffold/development.ini_tmpl3
-rw-r--r--pyramid/tests/test_scaffolds/fixture_scaffold/production.ini_tmpl3
-rw-r--r--pyramid/tests/test_scaffolds/test_template.py4
-rw-r--r--pyramid/tests/test_scripts/dummy.py68
-rw-r--r--pyramid/tests/test_scripts/test_pcreate.py10
-rw-r--r--pyramid/tests/test_scripts/test_prequest.py62
-rw-r--r--pyramid/tests/test_scripts/test_proutes.py92
-rw-r--r--pyramid/tests/test_scripts/test_pserve.py176
-rw-r--r--pyramid/tests/test_scripts/test_pshell.py77
-rw-r--r--pyramid/tests/test_scripts/test_ptweens.py5
-rw-r--r--pyramid/tests/test_scripts/test_pviews.py41
-rw-r--r--pyramid/tests/test_session.py138
-rw-r--r--pyramid/tests/test_static.py12
-rw-r--r--pyramid/tests/test_testing.py51
-rw-r--r--pyramid/tests/test_traversal.py238
-rw-r--r--pyramid/tests/test_tweens.py17
-rw-r--r--pyramid/tests/test_url.py22
-rw-r--r--pyramid/tests/test_urldispatch.py4
-rw-r--r--pyramid/tests/test_util.py6
-rw-r--r--pyramid/tests/test_view.py6
-rw-r--r--pyramid/tests/test_viewderivers.py1
-rw-r--r--pyramid/traversal.py116
-rw-r--r--pyramid/tweens.py76
-rw-r--r--pyramid/url.py161
-rw-r--r--pyramid/urldispatch.py11
-rw-r--r--pyramid/util.py24
-rw-r--r--pyramid/view.py26
-rw-r--r--pyramid/viewderivers.py2
-rwxr-xr-xscaffoldtests.sh3
-rw-r--r--setup.cfg51
-rw-r--r--setup.py28
-rw-r--r--tox.ini17
496 files changed, 8931 insertions, 6382 deletions
diff --git a/.travis.yml b/.travis.yml
index b46f677a6..ffc6caa72 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,10 +18,12 @@ matrix:
env: TOXENV=docs
- python: 3.5
env: TOXENV=pep8
- - python: nightly
+ - python: 3.6
env: TOXENV=py36
+ - python: nightly
+ env: TOXENV=py37
allow_failures:
- - env: TOXENV=py36
+ - env: TOXENV=py37
install:
- travis_retry pip install tox
@@ -29,6 +31,10 @@ install:
script:
- travis_retry tox
+cache:
+ directories:
+ - $HOME/.cache/pip
+
notifications:
email:
- pyramid-checkins@lists.repoze.org
diff --git a/CHANGES.txt b/CHANGES.txt
index 434557f89..51a1e457d 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,121 +1,146 @@
-unreleased
-==========
+1.9a2 (2017-05-09)
+==================
Backward Incompatibilities
--------------------------
- - Following the Pyramid deprecation period (1.6 -> 1.8),
- daemon support for pserve has been removed. This includes removing the
- daemon commands (start, stop, restart, status) as well as the following
- arguments:
- --daemon --pid-file --log-file --monitor-restart --status --user --group
- --stop-daemon
-
- To run your server as a daemon you should use a process manager instead of
- pserve.
-
- See https://github.com/Pylons/pyramid/pull/2615
-
-- ``pcreate`` is now interactive by default. You will be prompted if it
- a file already exists with different content. Previously if there were
- similar files it would silently skip them unless you specified
- ``--interactive`` or ``--overwrite``.
- See https://github.com/Pylons/pyramid/pull/2775
-
-Features
---------
-
-- pcreate learned about --package-name to allow you to create a new project in
- an existing folder with a different package name than the project name. See
- https://github.com/Pylons/pyramid/pull/2783
-
-- The `_get_credentials` private method of `BasicAuthAuthenticationPolicy`
- has been extracted into standalone function ``extract_http_basic_credentials`
- in `pyramid.authentication` module, this function extracts HTTP Basic
- credentials from a ``request`` object, and returns them as a named tuple.
- See https://github.com/Pylons/pyramid/pull/2662
-
-- Pyramid 1.4 silently dropped a feature of the configurator that has been
- restored. It's again possible for action discriminators to conflict across
- different action orders.
- See https://github.com/Pylons/pyramid/pull/2757
-
-- ``pyramid.paster.bootstrap`` and its sibling ``pyramid.scripting.prepare``
- can now be used as context managers to automatically invoke the ``closer``
- and pop threadlocals off of the stack to prevent memory leaks.
- See https://github.com/Pylons/pyramid/pull/2760
-
-- Added ``pyramid.config.Configurator.add_exception_view`` and the
- ``pyramid.view.exception_view_config`` decorator. It is now possible using
- these methods or via the new ``exception_only=True`` option to ``add_view``
- to add a view which will only be matched when handling an exception.
- Previously any exception views were also registered for a traversal
- context that inherited from the exception class which prevented any
- exception-only optimizations.
- See https://github.com/Pylons/pyramid/pull/2660
-
-- Added the ``exception_only`` boolean to
- ``pyramid.interfaces.IViewDeriverInfo`` which can be used by view derivers
- to determine if they are wrapping a view which only handles exceptions.
- This means that it is no longer necessary to perform request-time checks
- for ``request.exception`` to determine if the view is handling an exception
- - the pipeline can be optimized at config-time.
- See https://github.com/Pylons/pyramid/pull/2660
-
-- ``pserve`` should now work with ``gevent`` and other workers that need
- to monkeypatch the process, assuming the server and / or the app do so
- as soon as possible before importing the rest of pyramid.
- See https://github.com/Pylons/pyramid/pull/2797
+- ``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 <http://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
+ <http://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
---------
-- Fixed bug in `proutes` such that it now shows the correct view when a class
- and `attr` is involved.
- See: https://github.com/Pylons/pyramid/pull/2687
-
-- Fix a ``FutureWarning`` in Python 3.5 when using ``re.split`` on the
- ``format`` setting to the ``proutes`` script.
- See https://github.com/Pylons/pyramid/pull/2714
-
-- Fix a ``RuntimeWarning`` emitted by WebOb when using arbitrary objects
- as the ``userid`` in the ``AuthTktAuthenticationPolicy``. This is now caught
- by the policy and the object is serialized as a base64 string to avoid
- the cryptic warning. Since the userid will be read back as a string on
- subsequent requests a more useful warning is emitted encouraging you to
- use a primitive type instead.
- See https://github.com/Pylons/pyramid/pull/2715
-
-- Pyramid 1.6 introduced the ability for an action to invoke another action.
- There was a bug in the way that ``config.add_view`` would interact with
- custom view derivers introduced in Pyramid 1.7 because the view's
- discriminator cannot be computed until view derivers and view predicates
- have been created in earlier orders. Invoking an action from another action
- would trigger an unrolling of the pipeline and would compute discriminators
- before they were ready. The new behavior respects the ``order`` of the action
- and ensures the discriminators are not computed until dependent actions
- from previous orders have executed.
- See https://github.com/Pylons/pyramid/pull/2757
+- 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
------------
-Documentation Changes
----------------------
-- Add pyramid_nacl_session to session factories.
- See https://github.com/Pylons/pyramid/issues/2791
+- 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.
-- Update HACKING.txt from stale branch that was never merged to master.
- See https://github.com/Pylons/pyramid/pull/2782
+- 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``.
-- Updated Windows installation instructions and related bits.
- See https://github.com/Pylons/pyramid/issues/2661
+ Also, ``pyramid.session.check_csrf_token`` is now located at
+ ``pyramid.csrf.check_csrf_token``.
-- Fix an inconsistency in the documentation between view predicates and
- route predicates and highlight the differences in their APIs.
- See https://github.com/Pylons/pyramid/pull/2764
+ See https://github.com/Pylons/pyramid/pull/2854 and
+ https://github.com/Pylons/pyramid/pull/3019
+
+Documentation Changes
+---------------------
-- Clarify a possible misuse of the ``headers`` kwarg to subclasses of
- :class:`pyramid.httpexceptions.HTTPException` in which more appropriate
- kwargs from the parent class :class:`pyramid.response.Response` should be
- used instead. See https://github.com/Pylons/pyramid/pull/2750
+- Added the execution policy to the routing diagram in the Request Processing
+ chapter. See https://github.com/Pylons/pyramid/pull/2993
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index bb21337e2..2e49a193a 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -284,3 +284,19 @@ Contributors
- Jon Davidson, 2016/07/18
- Keith Yang, 2016/07/22
+
+- Hannah Krager, 2016/10/22
+
+- Moriyoshi Koizumi, 2016/11/20
+
+- 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
diff --git a/HACKING.txt b/HACKING.txt
index 953c386f9..bbebb5165 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -118,8 +118,8 @@ In order to add a feature to Pyramid:
- The feature must be documented in both the API and narrative documentation
(in ``docs/``).
-- The feature must work fully on the following CPython versions: 2.7, 3.4,
- and 3.5 on both UNIX and Windows.
+- The feature must work fully on the following CPython versions: 2.7, 3.4, 3.5,
+ and 3.6 on both UNIX and Windows.
- The feature must work on the latest version of PyPy.
@@ -216,7 +216,7 @@ Running Tests
Alternatively:
- $ tox -e{py27,py34,py35,pypy}-scaffolds,
+ $ tox -e{py27,py34,py35,pypy}-scaffolds
Test Coverage
diff --git a/HISTORY.txt b/HISTORY.txt
index 5de5b20bd..c69d9514e 100644
--- a/HISTORY.txt
+++ b/HISTORY.txt
@@ -1,3 +1,308 @@
+1.8 (2017-01-21)
+================
+
+- No major changes from 1.8b1.
+
+1.8b1 (2017-01-17)
+==================
+
+Features
+--------
+
+- Added an ``override`` option to ``config.add_translation_dirs`` to allow
+ later calls to place translation directories at a higher priority than
+ earlier calls. See https://github.com/Pylons/pyramid/pull/2902
+
+Documentation Changes
+---------------------
+
+- Improve registry documentation to discuss uses as a component registry
+ and as a dictionary. See https://github.com/Pylons/pyramid/pull/2893
+
+- Quick Tour, Quick Tutorial, and most other remaining documentation updated to
+ use cookiecutters instead of pcreate and scaffolds.
+ See https://github.com/Pylons/pyramid/pull/2888 and
+ https://github.com/Pylons/pyramid/pull/2889
+
+- Fix unittests in wiki2 to work without different dependencies between
+ py2 and py3. See https://github.com/Pylons/pyramid/pull/2899
+
+- Update Windows documentation to track newer Python 3 improvements to the
+ installer. See https://github.com/Pylons/pyramid/pull/2900
+
+- Updated the ``mod_wsgi`` tutorial to use cookiecutters and Apache 2.4+.
+ See https://github.com/Pylons/pyramid/pull/2901
+
+1.8a1 (2016-12-25)
+==================
+
+Backward Incompatibilities
+--------------------------
+
+- Support for the ``IContextURL`` interface that was deprecated in Pyramid 1.3
+ has been removed. See https://github.com/Pylons/pyramid/pull/2822
+
+- Following the Pyramid deprecation period (1.6 -> 1.8),
+ daemon support for pserve has been removed. This includes removing the
+ daemon commands (start, stop, restart, status) as well as the following
+ arguments: ``--daemon``, ``--pid-file``, ``--log-file``,
+ ``--monitor-restart``, ``--status``, ``--user``, ``--group``,
+ ``--stop-daemon``
+
+ To run your server as a daemon you should use a process manager instead of
+ pserve.
+
+ See https://github.com/Pylons/pyramid/pull/2615
+
+- ``pcreate`` is now interactive by default. You will be prompted if a file
+ already exists with different content. Previously if there were similar
+ files it would silently skip them unless you specified ``--interactive``
+ or ``--overwrite``.
+ See https://github.com/Pylons/pyramid/pull/2775
+
+- Removed undocumented argument ``cachebust_match`` from
+ ``pyramid.static.static_view``. This argument was shipped accidentally
+ in Pyramid 1.6. See https://github.com/Pylons/pyramid/pull/2681
+
+- Change static view to avoid setting the ``Content-Encoding`` response header
+ to an encoding guessed using Python's ``mimetypes`` module. This was causing
+ clients to decode the content of gzipped files when downloading them. The
+ client would end up with a ``foo.txt.gz`` file on disk that was already
+ decoded, thus should really be ``foo.txt``. Also, the ``Content-Encoding``
+ should only have been used if the client itself broadcast support for the
+ encoding via ``Accept-Encoding`` request headers.
+ See https://github.com/Pylons/pyramid/pull/2810
+
+- Settings are no longer accessible as attributes on the settings object
+ (e.g. ``request.registry.settings.foo``). This was deprecated in Pyramid 1.2.
+ See https://github.com/Pylons/pyramid/pull/2823
+
+Features
+--------
+
+- Python 3.6 compatibility.
+ https://github.com/Pylons/pyramid/issues/2835
+
+- ``pcreate`` learned about ``--package-name`` to allow you to create a new
+ project in an existing folder with a different package name than the project
+ name. See https://github.com/Pylons/pyramid/pull/2783
+
+- The ``_get_credentials`` private method of ``BasicAuthAuthenticationPolicy``
+ has been extracted into standalone function ``extract_http_basic_credentials``
+ in ``pyramid.authentication`` module, this function extracts HTTP Basic
+ credentials from a ``request`` object, and returns them as a named tuple.
+ See https://github.com/Pylons/pyramid/pull/2662
+
+- Pyramid 1.4 silently dropped a feature of the configurator that has been
+ restored. It's again possible for action discriminators to conflict across
+ different action orders.
+ See https://github.com/Pylons/pyramid/pull/2757
+
+- ``pyramid.paster.bootstrap`` and its sibling ``pyramid.scripting.prepare``
+ can now be used as context managers to automatically invoke the ``closer``
+ and pop threadlocals off of the stack to prevent memory leaks.
+ See https://github.com/Pylons/pyramid/pull/2760
+
+- Added ``pyramid.config.Configurator.add_exception_view`` and the
+ ``pyramid.view.exception_view_config`` decorator. It is now possible using
+ these methods or via the new ``exception_only=True`` option to ``add_view``
+ to add a view which will only be matched when handling an exception.
+ Previously any exception views were also registered for a traversal
+ context that inherited from the exception class which prevented any
+ exception-only optimizations.
+ See https://github.com/Pylons/pyramid/pull/2660
+
+- Added the ``exception_only`` boolean to
+ ``pyramid.interfaces.IViewDeriverInfo`` which can be used by view derivers
+ to determine if they are wrapping a view which only handles exceptions.
+ This means that it is no longer necessary to perform request-time checks
+ for ``request.exception`` to determine if the view is handling an exception
+ - the pipeline can be optimized at config-time.
+ See https://github.com/Pylons/pyramid/pull/2660
+
+- ``pserve`` should now work with ``gevent`` and other workers that need
+ to monkeypatch the process, assuming the server and / or the app do so
+ as soon as possible before importing the rest of pyramid.
+ See https://github.com/Pylons/pyramid/pull/2797
+
+- Pyramid no longer copies the settings object passed to the
+ ``pyramid.config.Configurator(settings=)``. The original ``dict`` is kept.
+ See https://github.com/Pylons/pyramid/pull/2823
+
+- The csrf trusted origins setting may now be a whitespace-separated list of
+ domains. Previously only a python list was allowed. Also, it can now be set
+ using the ``PYRAMID_CSRF_TRUSTED_ORIGINS`` environment variable similar to
+ other settings. See https://github.com/Pylons/pyramid/pull/2823
+
+- ``pserve --reload`` now uses the
+ `hupper <http://docs.pylonsproject.org/projects/hupper/en/latest/>`
+ library to monitor file changes. This comes with many improvements:
+
+ - If the `watchdog <http://pythonhosted.org/watchdog/>`_ package is
+ installed then monitoring will be done using inotify instead of
+ cpu and disk-intensive polling.
+
+ - The monitor is now a separate process that will not crash and starts up
+ before any of your code.
+
+ - The monitor will not restart the process after a crash until a file is
+ saved.
+
+ - The monitor works on windows.
+
+ - You can now trigger a reload manually from a pyramid view or any other
+ code via ``hupper.get_reloader().trigger_reload()``. Kind of neat.
+
+ - You can trigger a reload by issuing a ``SIGHUP`` to the monitor process.
+
+ See https://github.com/Pylons/pyramid/pull/2805
+
+- A new ``[pserve]`` section is supported in your config files with a
+ ``watch_files`` key that can configure ``pserve --reload`` to monitor custom
+ file paths. See https://github.com/Pylons/pyramid/pull/2827
+
+- Allow streaming responses to be made from subclasses of
+ ``pyramid.httpexceptions.HTTPException``. Previously the response would
+ be unrolled while testing for a body, making it impossible to stream
+ a response.
+ See https://github.com/Pylons/pyramid/pull/2863
+
+- Update starter, alchemy and zodb scaffolds to support IPv6 by using the
+ new ``listen`` directives in waitress.
+ See https://github.com/Pylons/pyramid/pull/2853
+
+- All p* scripts now use argparse instead of optparse. This improves their
+ ``--help`` output as well as enabling nicer documentation of their options.
+ See https://github.com/Pylons/pyramid/pull/2864
+
+- Any deferred configuration action registered via ``config.action`` may now
+ depend on threadlocal state, such as asset overrides, being active when
+ the action is executed.
+ See https://github.com/Pylons/pyramid/pull/2873
+
+- Asset specifications for directories passed to
+ ``config.add_translation_dirs`` now support overriding the entire asset
+ specification, including the folder name. Previously only the package name
+ was supported and the folder would always need to have the same name.
+ See https://github.com/Pylons/pyramid/pull/2873
+
+- ``config.begin()`` will propagate the current threadlocal request through
+ as long as the registry is the same. For example:
+
+ .. code-block:: python
+
+ request = Request.blank(...)
+ config.begin(request) # pushes a request
+ config.begin() # propagates the previous request through unchanged
+ assert get_current_request() is request
+
+ 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
+---------
+
+- Fixed bug in ``proutes`` such that it now shows the correct view when a
+ class and ``attr`` is involved.
+ See: https://github.com/Pylons/pyramid/pull/2687
+
+- Fix a ``FutureWarning`` in Python 3.5 when using ``re.split`` on the
+ ``format`` setting to the ``proutes`` script.
+ See https://github.com/Pylons/pyramid/pull/2714
+
+- Fix a ``RuntimeWarning`` emitted by WebOb when using arbitrary objects
+ as the ``userid`` in the ``AuthTktAuthenticationPolicy``. This is now caught
+ by the policy and the object is serialized as a base64 string to avoid
+ the cryptic warning. Since the userid will be read back as a string on
+ subsequent requests a more useful warning is emitted encouraging you to
+ use a primitive type instead.
+ See https://github.com/Pylons/pyramid/pull/2715
+
+- Pyramid 1.6 introduced the ability for an action to invoke another action.
+ There was a bug in the way that ``config.add_view`` would interact with
+ custom view derivers introduced in Pyramid 1.7 because the view's
+ discriminator cannot be computed until view derivers and view predicates
+ have been created in earlier orders. Invoking an action from another action
+ would trigger an unrolling of the pipeline and would compute discriminators
+ before they were ready. The new behavior respects the ``order`` of the action
+ and ensures the discriminators are not computed until dependent actions
+ from previous orders have executed.
+ See https://github.com/Pylons/pyramid/pull/2757
+
+- Fix bug in i18n where the default domain would always use the Germanic plural
+ style, even if a different plural function is defined in the relevant
+ messages file. See https://github.com/Pylons/pyramid/pull/2859
+
+- The ``config.override_asset`` method now occurs during
+ ``pyramid.config.PHASE1_CONFIG`` such that it is ordered to execute before
+ any calls to ``config.add_translation_dirs``.
+ See https://github.com/Pylons/pyramid/pull/2873
+
+Deprecations
+------------
+
+- The ``pcreate`` script and related scaffolds have been deprecated in favor
+ of the popular
+ `cookiecutter <https://cookiecutter.readthedocs.io/en/latest/>`_ project.
+
+ All of Pyramid's official scaffolds as well as the tutorials have been
+ ported to cookiecutters:
+
+ - `pyramid-cookiecutter-starter
+ <https://github.com/Pylons/pyramid-cookiecutter-starter>`_
+
+ - `pyramid-cookiecutter-alchemy
+ <https://github.com/Pylons/pyramid-cookiecutter-alchemy>`_
+
+ - `pyramid-cookiecutter-zodb
+ <https://github.com/Pylons/pyramid-cookiecutter-zodb>`_
+
+ See https://github.com/Pylons/pyramid/pull/2780
+
+Documentation Changes
+---------------------
+
+- Update Typographical Conventions.
+ https://github.com/Pylons/pyramid/pull/2838
+
+- Add `pyramid_nacl_session
+ <http://docs.pylonsproject.org/projects/pyramid-nacl-session/en/latest/>`_
+ to session factories. See https://github.com/Pylons/pyramid/issues/2791
+
+- Update ``HACKING.txt`` from stale branch that was never merged to master.
+ See https://github.com/Pylons/pyramid/pull/2782
+
+- Updated Windows installation instructions and related bits.
+ See https://github.com/Pylons/pyramid/issues/2661
+
+- Fix an inconsistency in the documentation between view predicates and
+ route predicates and highlight the differences in their APIs.
+ See https://github.com/Pylons/pyramid/pull/2764
+
+- Clarify a possible misuse of the ``headers`` kwarg to subclasses of
+ ``pyramid.httpexceptions.HTTPException`` in which more appropriate
+ kwargs from the parent class ``pyramid.response.Response`` should be
+ used instead. See https://github.com/Pylons/pyramid/pull/2750
+
+- The SQLAlchemy + URL Dispatch + Jinja2 (``wiki2``) and
+ ZODB + Traversal + Chameleon (``wiki``) tutorials have been updated to
+ utilize the new cookiecutters and drop support for the ``pcreate``
+ scaffolds.
+
+ See https://github.com/Pylons/pyramid/pull/2881 and
+ https://github.com/Pylons/pyramid/pull/2883.
+
+- Improve output of p* script descriptions for help.
+ See https://github.com/Pylons/pyramid/pull/2886
+
+- Quick Tour updated to use cookiecutters instead of pcreate and scaffolds.
+ See https://github.com/Pylons/pyramid/pull/2888
+
1.7 (2016-05-19)
================
diff --git a/README.rst b/README.rst
index 6ef75e899..5f42115df 100644
--- a/README.rst
+++ b/README.rst
@@ -1,16 +1,16 @@
Pyramid
=======
-.. image:: https://travis-ci.org/Pylons/pyramid.png?branch=master
+.. image:: https://travis-ci.org/Pylons/pyramid.png?branch=1.9-branch
:target: https://travis-ci.org/Pylons/pyramid
- :alt: Master Travis CI Status
+ :alt: master Travis CI Status
-.. image:: https://readthedocs.org/projects/pyramid/badge/?version=master
- :target: http://docs.pylonsproject.org/projects/pyramid/en/master/
+.. image:: https://readthedocs.org/projects/pyramid/badge/?version=1.9-branch
+ :target: http://docs.pylonsproject.org/projects/pyramid/en/1.9-branch/
:alt: Master Documentation Status
-.. image:: https://readthedocs.org/projects/pyramid/badge/?version=latest
- :target: http://docs.pylonsproject.org/projects/pyramid/en/latest/
+.. image:: https://readthedocs.org/projects/pyramid/badge/?version=1.9-branch
+ :target: http://docs.pylonsproject.org/projects/pyramid/en/1.9-branch/
:alt: Latest Documentation Status
.. image:: https://img.shields.io/badge/irc-freenode-blue.svg
@@ -31,14 +31,14 @@ 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://www.pylonsproject.org/>`_.
+Pyramid is a project of the `Pylons Project <http://pylonsproject.org/>`_.
Support and Documentation
-------------------------
diff --git a/RELEASING.txt b/RELEASING.txt
index 4690fbd37..58ebb2fb3 100644
--- a/RELEASING.txt
+++ b/RELEASING.txt
@@ -33,8 +33,8 @@ Prepare new release branch
- Run tests on Windows if feasible.
-- Make sure all scaffold tests pass (CPython 2.7, 3.4, and 3.5, and PyPy on
- UNIX; this doesn't work on Windows):
+- Make sure all scaffold tests pass (CPython 2.7, 3.4, 3.5, and 3.6, and PyPy
+ on UNIX; this doesn't work on Windows):
$ ./scaffoldtests.sh
@@ -63,10 +63,10 @@ Prepare new release branch
- Change setup.py version to the release version number.
-- Make sure PyPI long description renders (requires ``collective.dist``
- installed into your Python)::
+- Make sure PyPI long description renders (requires ``readme`` installed
+ into your Python)::
- $ python setup.py check -r
+ $ python setup.py check -r -s -m
- Create a release tag.
@@ -108,18 +108,14 @@ Update previous version (final releases only)
Marketing and communications
----------------------------
-- Edit Pylons/pylonshq/templates/home/home.mako.
-
-- Edit Pylons/pylonshq/templates/home/inside.rst for major releases only.
-
-- 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.
```
diff --git a/TODO.txt b/TODO.txt
index 797f8acef..a3ae9d8c0 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -114,15 +114,7 @@ Nice-to-Have
Future
------
-- 1.6: turn ``pyramid.settings.Settings`` into a function that returns the
- original dict (after ``__getattr__`` deprecation period, it was deprecated
- in 1.2).
-
-- 1.6: Remove IContextURL and TraversalContextURL.
-
-- 1.8: Remove set_request_property.
-- 1.8: Drop Python 3.3 support.
-
+- 1.9: Remove set_request_property.
- 1.9: Remove extra code enabling ``pyramid.security.remember(principal=...)``
and force use of ``userid``.
diff --git a/appveyor.yml b/appveyor.yml
index 1350507b2..abbbfea92 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,7 +1,16 @@
environment:
matrix:
+ - PYTHON: "C:\\Python36"
+ TOXENV: "py36"
- PYTHON: "C:\\Python35"
TOXENV: "py35"
+ - PYTHON: "C:\\Python27"
+ TOXENV: "py27"
+
+cache:
+ - '%LOCALAPPDATA%\pip\Cache'
+
+version: '{branch}.{build}'
install:
- "%PYTHON%\\python.exe -m pip install tox"
diff --git a/contributing.md b/contributing.md
index b5f17ae06..82f60e7b8 100644
--- a/contributing.md
+++ b/contributing.md
@@ -26,9 +26,11 @@ listed below.
* [master](https://github.com/Pylons/pyramid/) - The branch on which further
development takes place. The default branch on GitHub.
-* [1.7-branch](https://github.com/Pylons/pyramid/tree/1.7-branch) - The branch
+* [1.9-branch](https://github.com/Pylons/pyramid/tree/1.9-branch) - The branch
+ classified as "alpha".
+* [1.8-branch](https://github.com/Pylons/pyramid/tree/1.8-branch) - The branch
classified as "stable" or "latest".
-* [1.6-branch](https://github.com/Pylons/pyramid/tree/1.6-branch) - The oldest
+* [1.7-branch](https://github.com/Pylons/pyramid/tree/1.7-branch) - The oldest
actively maintained and stable branch.
Older branches are not actively maintained. In general, two stable branches and
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 ab3ff0fe1..a785b64ad 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -26,6 +26,7 @@
.. automethod:: add_view
.. automethod:: add_notfound_view
.. automethod:: add_forbidden_view
+ .. automethod:: add_exception_view
:methodcategory:`Adding an Event Subscriber`
@@ -36,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
@@ -69,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/i18n.rst b/docs/api/i18n.rst
index 3b9abbc1d..7a61246df 100644
--- a/docs/api/i18n.rst
+++ b/docs/api/i18n.rst
@@ -6,6 +6,7 @@
.. automodule:: pyramid.i18n
.. autoclass:: TranslationString
+ :noindex:
.. autofunction:: TranslationStringFactory
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/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/view.rst b/docs/api/view.rst
index d8e429552..e41212012 100644
--- a/docs/api/view.rst
+++ b/docs/api/view.rst
@@ -23,4 +23,6 @@
.. autoclass:: forbidden_view_config
:members:
+ .. autoclass:: exception_view_config
+ :members:
diff --git a/docs/conf.py b/docs/conf.py
index c3a7170fc..e63019c63 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -49,12 +49,13 @@ book = os.environ.get('BOOK')
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
+ 'repoze.sphinx.autointerface',
'sphinx.ext.autodoc',
'sphinx.ext.doctest',
- 'repoze.sphinx.autointerface',
- 'sphinx.ext.viewcode',
'sphinx.ext.intersphinx',
- 'sphinxcontrib.programoutput',
+ 'sphinx.ext.todo',
+ 'sphinx.ext.viewcode',
+ 'sphinxcontrib.autoprogram',
# enable pylons_sphinx_latesturl when this branch is no longer "latest"
# 'pylons_sphinx_latesturl',
]
@@ -63,11 +64,14 @@ extensions = [
intersphinx_mapping = {
'colander': ('http://docs.pylonsproject.org/projects/colander/en/latest', None),
'cookbook': ('http://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),
+ 'plaster': ('http://docs.pylonsproject.org/projects/plaster/en/latest/', None),
'pylonswebframework': ('http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None),
'python': ('https://docs.python.org/3', None),
- 'pytest': ('http://pytest.org/latest/', None),
+ 'pytest': ('https://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),
@@ -78,7 +82,8 @@ intersphinx_mapping = {
'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),
- 'zcomponent': ('http://zopecomponent.readthedocs.io/en/stable/', None),
+ 'zcomponent': ('http://zopecomponent.readthedocs.io/en/latest/', None),
+ 'zinterface': ('http://zopeinterface.readthedocs.io/en/latest/', None),
}
@@ -119,6 +124,9 @@ exclude_patterns = ['_themes/README.rst', ]
# unit titles (such as .. function::).
add_module_names = False
+# Add support for todo items
+todo_include_todos = True
+
# The name of the Pygments (syntax highlighting) style to use.
#pygments_style = book and 'bw' or 'tango'
if book:
@@ -139,6 +147,7 @@ if book:
# 'whatsnew-1.5': 'index',
# 'whatsnew-1.6': 'index',
# 'whatsnew-1.7': 'index',
+# 'whatsnew-1.8': 'index',
# 'tutorials/gae/index': 'index',
# 'api/chameleon_text': 'api',
# 'api/chameleon_zpt': 'api',
@@ -191,10 +200,10 @@ latex_documents = [
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
-latex_use_parts = True
+latex_toplevel_sectioning = "section"
# If false, no module index is generated.
-latex_use_modindex = False
+latex_domain_indices = False
## Say, for a moment that you have a twoside document that needs a 3cm
## inner margin to allow for binding and at least two centimetres the
diff --git a/docs/conventions.rst b/docs/conventions.rst
deleted file mode 100644
index de041da04..000000000
--- a/docs/conventions.rst
+++ /dev/null
@@ -1,107 +0,0 @@
-Typographical Conventions
-=========================
-
-Literals, filenames, and function arguments are presented using the
-following style:
-
- ``argument1``
-
-Warnings which represent limitations and need-to-know information
-related to a topic or concept are presented in the following style:
-
- .. warning::
-
- This is a warning.
-
-Notes which represent additional information related to a topic or
-concept are presented in the following style:
-
- .. note::
-
- This is a note.
-
-We present Python method names using the following style:
-
- :meth:`pyramid.config.Configurator.add_view`
-
-We present Python class names, module names, attributes, and global
-variables using the following style:
-
- :class:`pyramid.config.Configurator.registry`
-
-References to glossary terms are presented using the following style:
-
- :term:`Pylons`
-
-URLs are presented using the following style:
-
- `Pylons <http://www.pylonsproject.org>`_
-
-References to sections and chapters are presented using the following
-style:
-
- :ref:`traversal_chapter`
-
-Code and configuration file blocks are presented in the following style:
-
- .. code-block:: python
- :linenos:
-
- def foo(abc):
- pass
-
-Example blocks representing UNIX shell commands are prefixed with a ``$``
-character, e.g.:
-
- .. code-block:: bash
-
- $ $VENV/bin/py.test -q
-
-See :term:`venv` for the meaning of ``$VENV``.
-
-Example blocks representing Windows commands are prefixed with a drive letter
-with an optional directory name, e.g.:
-
- .. code-block:: doscon
-
- c:\examples> %VENV%\Scripts\py.test -q
-
-See :term:`venv` for the meaning of ``%VENV%``.
-
-When a command that should be typed on one line is too long to fit on a page,
-the backslash ``\`` is used to indicate that the following printed line should
-be part of the command:
-
- .. code-block:: bash
-
- $VENV/bin/py.test tutorial/tests.py --cov-report term-missing \
- --cov=tutorial -q
-
-A sidebar, which presents a concept tangentially related to content discussed
-on a page, is rendered like so:
-
-.. sidebar:: This is a sidebar
-
- Sidebar information.
-
-When multiple objects are imported from the same package, the following
-convention is used:
-
- .. code-block:: python
-
- from foo import (
- bar,
- baz,
- )
-
-It may look unusual, but it has advantages:
-
-* It allows one to swap out the higher-level package ``foo`` for something else
- that provides the similar API. An example would be swapping out one database
- for another (e.g., graduating from SQLite to PostgreSQL).
-
-* Looks more neat in cases where a large number of objects get imported from
- that package.
-
-* Adding or removing imported objects from the package is quicker and results
- in simpler diffs.
diff --git a/docs/designdefense.rst b/docs/designdefense.rst
index 5f65671bb..0a72ff27d 100644
--- a/docs/designdefense.rst
+++ b/docs/designdefense.rst
@@ -591,7 +591,7 @@ extensibility because it must be deployed in multiple locations.
Pyramid Is Too Big
------------------
-"The :app:`Pyramid` compressed tarball is larger than 2MB. It must beenormous!"
+"The :app:`Pyramid` compressed tarball is larger than 2MB. It must be enormous!"
No. We just ship it with docs, test code, and scaffolding. Here's a breakdown
of what's included in subdirectories of the package tree:
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 9b41b4359..2e5276554 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -366,6 +366,14 @@ Glossary
:term:`WSGI` components together declaratively within an ``.ini``
file. It was developed by Ian Bicking.
+ plaster
+ `plaster <http://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
attribute language template compiler which supports the :term:`ZPT`
@@ -568,7 +576,7 @@ Glossary
:ref:`adding_and_overriding_renderers` for more information.
mod_wsgi
- `mod_wsgi <https://code.google.com/archive/p/modwsgi>`_ is an Apache
+ `mod_wsgi <https://modwsgi.readthedocs.io>`_ 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.
@@ -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
@@ -992,7 +1005,7 @@ Glossary
pages rendered by your application, displaying request, routing, and
database information. :mod:`pyramid_debugtoolbar` is configured into
the ``development.ini`` of all applications which use a Pyramid
- :term:`scaffold`. For more information, see
+ :term:`cookiecutter`. For more information, see
http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest/.
scaffold
@@ -1000,6 +1013,10 @@ Glossary
application and helps users to quickly get started writing larger
applications. Scaffolds are usually used via the ``pcreate`` command.
+ .. deprecated:: 1.8
+
+ .. seealso:: See also :term:`cookiecutter`.
+
pyramid_exclog
A package which logs Pyramid application exception (error) information
to a standard Python logger. This add-on is most useful when
@@ -1058,8 +1075,8 @@ Glossary
:class:`pyramid.interfaces.IAssetDescriptor`.
Waitress
- A :term:`WSGI` server that runs on UNIX and Windows under Python 2.6+
- and Python 3.2+. Projects generated via Pyramid scaffolding use
+ 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
information.
@@ -1133,4 +1150,25 @@ Glossary
Python Packaging Authority
The `Python Packaging Authority (PyPA) <https://www.pypa.io/en/latest/>`_
is a working group that maintains many of the relevant projects in Python
- packaging. \ No newline at end of file
+ packaging.
+
+ cookiecutter
+ A command-line utility that creates projects from :ref:`cookiecutters <cookiecutter:readme>` (project templates), e.g., creating a Python package project from a Python package project template.
+
+ Pyramid cookiecutters include:
+
+ * `pyramid-cookiecutter-alchemy <https://github.com/Pylons/pyramid-cookiecutter-alchemy>`_
+ * `pyramid-cookiecutter-starter <https://github.com/Pylons/pyramid-cookiecutter-starter>`_
+ * `pyramid-cookiecutter-zodb <https://github.com/Pylons/pyramid-cookiecutter-zodb>`_
+
+ .. versionadded:: 1.8
+
+ .. seealso:: See also :term:`scaffold`.
+
+ 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`.
diff --git a/docs/index.rst b/docs/index.rst
index 02c35866a..7d3393548 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://www.pylonsproject.org/>`_.
+developed as part of the `Pylons Project <http://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:
@@ -145,6 +145,7 @@ Narrative documentation in chapter form explaining how to use :app:`Pyramid`.
narr/extending
narr/advconfig
narr/extconfig
+ narr/cookiecutters
narr/scaffolding
narr/upgrading
narr/threadlocals
@@ -184,6 +185,8 @@ Change History
.. toctree::
:maxdepth: 1
+ whatsnew-1.9
+ whatsnew-1.8
whatsnew-1.7
whatsnew-1.6
whatsnew-1.5
@@ -213,13 +216,13 @@ Copyright, Trademarks, and Attributions
copyright
-Typographical Conventions
-=========================
+Typographical Conventions and Style Guide
+=========================================
.. toctree::
:maxdepth: 1
- conventions
+ typographical-conventions
Index and Glossary
diff --git a/docs/latexindex.rst b/docs/latexindex.rst
index 05199d313..0a6b3400f 100644
--- a/docs/latexindex.rst
+++ b/docs/latexindex.rst
@@ -15,7 +15,7 @@ Front Matter
:maxdepth: 1
copyright
- conventions
+ typographical-conventions
authorintro
designdefense
@@ -78,6 +78,7 @@ Narrative Documentation
narr/extending
narr/advconfig
narr/extconfig
+ narr/cookiecutters
narr/scaffolding
narr/upgrading
narr/threadlocals
diff --git a/docs/narr/MyProject/CHANGES.txt b/docs/narr/MyProject/CHANGES.txt
deleted file mode 100644
index 35a34f332..000000000
--- a/docs/narr/MyProject/CHANGES.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-0.0
----
-
-- Initial version
diff --git a/docs/narr/MyProject/README.txt b/docs/narr/MyProject/README.txt
deleted file mode 100644
index 70759eba1..000000000
--- a/docs/narr/MyProject/README.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-MyProject README
-==================
-
-Getting Started
----------------
-
-- cd <directory containing this file>
-
-- $VENV/bin/pip install -e .
-
-- $VENV/bin/pserve development.ini
-
diff --git a/docs/narr/MyProject/setup.py b/docs/narr/MyProject/setup.py
deleted file mode 100644
index a911eff6d..000000000
--- a/docs/narr/MyProject/setup.py
+++ /dev/null
@@ -1,49 +0,0 @@
-import os
-
-from setuptools import setup, find_packages
-
-here = os.path.abspath(os.path.dirname(__file__))
-with open(os.path.join(here, 'README.txt')) as f:
- README = f.read()
-with open(os.path.join(here, 'CHANGES.txt')) as f:
- CHANGES = f.read()
-
-requires = [
- 'pyramid',
- 'pyramid_chameleon',
- 'pyramid_debugtoolbar',
- 'waitress',
- ]
-
-tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
- 'pytest-cov',
- ]
-
-setup(name='MyProject',
- version='0.0',
- description='MyProject',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web pyramid pylons',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = myproject:main
- """,
- )
diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst
index 58f547fc9..f5a2f9684 100644
--- a/docs/narr/assets.rst
+++ b/docs/narr/assets.rst
@@ -28,7 +28,7 @@ asset:
The use of assets is quite common in most web development projects. For
example, when you create a :app:`Pyramid` application using one of the
-available scaffolds, as described in :ref:`creating_a_project`, the directory
+available :term:`cookiecutter`\ s, as described in :ref:`creating_a_project`, the directory
representing the application contains a Python :term:`package`. Within that
Python package, there are directories full of files which are static assets.
For example, there's a ``static`` directory which contains ``.css``, ``.js``,
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index 242bc7ec7..98663cca6 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -839,7 +839,7 @@ In general, you can make your script into a console script by doing the
following:
- Use an existing distribution (such as one you've already created via
- ``pcreate``) or create a new distribution that possesses at least one package
+ ``cookiecutter``) or create a new distribution that possesses at least one package
or module. It should, within any module within the distribution, house a
callable (usually a function) that takes no arguments and which runs any of
the code you wish to run.
diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst
index cde166b21..ee54e3acd 100644
--- a/docs/narr/configuration.rst
+++ b/docs/narr/configuration.rst
@@ -54,7 +54,7 @@ configured imperatively:
server.serve_forever()
We won't talk much about what this application does yet. Just note that the
-"configuration' statements take place underneath the ``if __name__ ==
+configuration statements take place underneath the ``if __name__ ==
'__main__':`` stanza in the form of method calls on a :term:`Configurator`
object (e.g., ``config.add_view(...)``). These statements take place one after
the other, and are executed in order, so the full power of Python, including
diff --git a/docs/narr/cookiecutters.rst b/docs/narr/cookiecutters.rst
new file mode 100644
index 000000000..abedb25b9
--- /dev/null
+++ b/docs/narr/cookiecutters.rst
@@ -0,0 +1,22 @@
+.. _cookiecutters:
+
+Pyramid cookiecutters
+=====================
+
+.. versionadded:: 1.8
+
+A :term:`cookiecutter` is a command-line utility that creates projects from :ref:`cookiecutters <cookiecutter:readme>` (project templates), e.g., creating a Python package project from a Python package project template.
+
+Pyramid cookiecutters have replaced the now deprecated Pyramid scaffolds, and should be used going forward. Pyramid cookiecutters released under the Pylons Project include:
+
+* `pyramid-cookiecutter-alchemy <https://github.com/Pylons/pyramid-cookiecutter-alchemy>`_
+* `pyramid-cookiecutter-starter <https://github.com/Pylons/pyramid-cookiecutter-starter>`_
+* `pyramid-cookiecutter-zodb <https://github.com/Pylons/pyramid-cookiecutter-zodb>`_
+
+.. seealso::
+
+ See also `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ and `Cookiecutter Features <https://cookiecutter.readthedocs.io/en/latest/readme.html#features>`_. Development of cookiecutters is documented under `Learn the Basics of Cookiecutter by Creating a Cookiecutter <https://cookiecutter.readthedocs.io/en/latest/first_steps.html>`_.
+
+.. seealso::
+
+ See also :term:`scaffold`.
diff --git a/docs/narr/extconfig.rst b/docs/narr/extconfig.rst
index babfa0a98..4009ec1dc 100644
--- a/docs/narr/extconfig.rst
+++ b/docs/narr/extconfig.rst
@@ -260,6 +260,7 @@ Pre-defined Phases
- :meth:`pyramid.config.Configurator.add_subscriber_predicate`
- :meth:`pyramid.config.Configurator.add_view_predicate`
- :meth:`pyramid.config.Configurator.add_view_deriver`
+- :meth:`pyramid.config.Configurator.override_asset`
- :meth:`pyramid.config.Configurator.set_authorization_policy`
- :meth:`pyramid.config.Configurator.set_default_csrf_options`
- :meth:`pyramid.config.Configurator.set_default_permission`
diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst
index 9dc042024..bee30ec1a 100644
--- a/docs/narr/extending.rst
+++ b/docs/narr/extending.rst
@@ -190,7 +190,7 @@ The general pattern for extending an existing application looks something like
this:
- Create a new Python package. The easiest way to do this is to create a new
- :app:`Pyramid` application using the scaffold mechanism. See
+ :app:`Pyramid` application using a :term:`cookiecutter`. See
:ref:`creating_a_project` for more information.
- In the new package, create Python files containing views and other overridden
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index b22b31bf9..81c8e81d7 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -116,14 +116,6 @@ callable:
.. note::
- Both :meth:`pyramid.config.Configurator.add_notfound_view` and
- :class:`pyramid.view.notfound_view_config` are new as of Pyramid 1.3.
- Older Pyramid documentation instructed users to use ``add_view`` instead,
- with a ``context`` of ``HTTPNotFound``. This still works; the convenience
- method and decorator are just wrappers around this functionality.
-
-.. warning::
-
When a Not Found View callable accepts an argument list as described in
:ref:`request_and_context_view_definitions`, the ``context`` passed as the
first argument to the view callable will be the
@@ -131,6 +123,13 @@ callable:
available, the resource context will still be available as
``request.context``.
+.. warning::
+
+ The :term:`Not Found View` callables are only invoked when a
+ :exc:`~pyramid.httpexceptions.HTTPNotFound` exception is raised. If the
+ exception is returned from a view then it will be treated as a regular
+ response object and it will not trigger the custom view.
+
.. index::
single: forbidden view
@@ -161,7 +160,7 @@ forbidden view:
def main(globals, **settings):
config = Configurator()
- config.add_forbidden_view(forbidden_view)
+ config.add_forbidden_view(forbidden)
If instead you prefer to use decorators and a :term:`scan`, you can use the
:class:`pyramid.view.forbidden_view_config` decorator to mark a view callable
@@ -210,6 +209,13 @@ Here's some sample code that implements a minimal forbidden view:
whether the ``pyramid.debug_authorization`` environment setting is true or
false.
+.. warning::
+
+ The :term:`forbidden view` callables are only invoked when a
+ :exc:`~pyramid.httpexceptions.HTTPForbidden` exception is raised. If the
+ exception is returned from a view then it will be treated as a regular
+ response object and it will not trigger the custom view.
+
.. index::
single: request factory
@@ -744,7 +750,9 @@ The API that must be implemented by a class that provides
""" Accept the resource and request and set self.physical_path and
self.virtual_path """
self.virtual_path = some_function_of(resource, request)
+ self.virtual_path_tuple = some_function_of(resource, request)
self.physical_path = some_other_function_of(resource, request)
+ self.physical_path_tuple = some_function_of(resource, request)
The default context URL generator is available for perusal as the class
:class:`pyramid.traversal.ResourceURL` in the `traversal module
@@ -1561,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/install.rst b/docs/narr/install.rst
index 570cb2285..2a25ad84d 100644
--- a/docs/narr/install.rst
+++ b/docs/narr/install.rst
@@ -22,7 +22,7 @@ the following sections.
.. sidebar:: Python Versions
As of this writing, :app:`Pyramid` is tested against Python 2.7,
- Python 3.4, Python 3.5, PyPy.
+ Python 3.4, Python 3.5, Python 3.6, and PyPy.
:app:`Pyramid` is known to run on all popular UNIX-like systems such as Linux,
Mac OS X, and FreeBSD, as well as on Windows platforms. It is also known to
@@ -91,29 +91,22 @@ If your Windows system doesn't have a Python interpreter, you'll need to
install it by downloading a Python 3.x-series interpreter executable from
`python.org's download section <https://www.python.org/downloads/>`_ (the files
labeled "Windows Installer"). Once you've downloaded it, double click on the
-executable, and select appropriate options during the installation process. To
+executable and select appropriate options during the installation process. To
standardize this documentation, we used the GUI installer and selected the
following options:
- Screen 1: Install Python 3.x.x (32- or 64-bit)
- - Check "Install launcher for all users (recommended)"
- - Check "Add Python 3.x to PATH"
- - Click "Customize installation"
-- Screen 2: Optional Features
- - Check all options
- - Click "Next"
-- Screen 3: Advanced Options
- - Check all options
- - Customize install location: "C:\\Python3x", where "x" is the minor
- version of Python
- - Click "Next"
-
-You might also need to download and install the Python for Windows extensions.
+ - Check "Install launcher for all users (recommended)".
+ - Check "Add Python 3.x to PATH".
+ - Click "Install Now".
+- Screen 2: User Account Control
+ - Click "Yes".
.. seealso:: See the official Python documentation :ref:`Using Python on
Windows <python:using-on-windows>` for full details.
-.. seealso:: Download and install the `Python for Windows extensions
+.. seealso:: You might also need to download and install the `Python for
+ Windows extensions
<https://sourceforge.net/projects/pywin32/files/pywin32/>`_. Carefully read
the README.txt file at the end of the list of builds, and follow its
directions. Make sure you get the proper 32- or 64-bit build and Python
@@ -123,15 +116,26 @@ You might also need to download and install the Python for Windows extensions.
<https://docs.python.org/3/using/windows.html#launcher>`_ provides a command
``py`` that allows users to run any installed version of Python.
-.. warning::
+.. warning:: After you install Python on Windows, you might need to add the
+ directory where Python and other programs—such as pip, setuptools, and
+ cookiecutter—are installed to your environment's ``Path``. This will make it
+ possible to invoke them from a command prompt.
- After you install Python on Windows, you might need to add the
- ``c:\Python3x`` directory to your environment's ``Path``, where ``x`` is the
- minor version of installed Python, in order to make it possible to invoke
- Python from a command prompt by typing ``python``. To do so, right click
- ``My Computer``, select ``Properties`` --> ``Advanced Tab`` -->
- ``Environment Variables``, and add that directory to the end of the ``Path``
- environment variable.
+ To do so, search for "Environment Variables" on your computer (on Windows
+ 10, it is under ``System Properties`` --> ``Advanced``) and add that
+ directory to the ``Path`` environment variable, using the GUI to edit path
+ segments.
+
+ Example segments should look like
+ ``C:\Users\<username>\AppData\Local\Programs\Python3x-32``, where you have
+ your username instead of ``<username>``, and your version of Python and
+ whether it is 32- or 64-bit. Additionally ensure you have the path segment
+ ending with ``\Scripts``, i.e.,
+ ``C:\Users\<username>\AppData\Local\Programs\Python3x-32\Scripts``, and for
+ user-installed Python programs, ``%APPDATA%\Python\Python3x\Scripts``.
+
+ You may need to restart your command prompt session to load the environment
+ variables.
.. seealso:: See `Configuring Python (on Windows)
<https://docs.python.org/3/using/windows.html#configuring-python>`_ for
@@ -231,9 +235,9 @@ After installing Python as described previously in
.. code-block:: doscon
+ c:\> cd \
c:\> set VENV=c:\env
- # replace "x" with your minor version of Python 3
- c:\> c:\Python3x\python -m venv %VENV%
+ c:\> python -m venv %VENV%
c:\> cd %VENV%
You can either follow the use of the environment variable ``%VENV%``, or
@@ -260,5 +264,5 @@ What Gets Installed
When you install :app:`Pyramid`, various libraries such as WebOb, PasteDeploy,
and others are installed.
-Additionally, as chronicled in :ref:`project_narr`, scaffolds will be
-registered, which make it easy to start a new :app:`Pyramid` project.
+Additionally, as chronicled in :ref:`project_narr`, :term:`cookiecutter`\ s will be
+used, which make it easy to start a new :app:`Pyramid` project.
diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst
index adf955a97..5eda0fcf4 100644
--- a/docs/narr/introduction.rst
+++ b/docs/narr/introduction.rst
@@ -213,6 +213,9 @@ packages, SQL queries, logging statements and more.
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 Pyramid debug toolbar, build your project with a Pyramid
+:term:`cookiecutter`.
+
Example: :ref:`debug_toolbar`.
Debug with power
@@ -376,7 +379,7 @@ By configuring your view to use a renderer, you tell Pyramid to use the
behalf.
The string passed as ``renderer=`` above is an :term:`asset specification`.
-Asset specifications are omnipresent in Pyramid. They allow for more reliable
+Asset specifications are widely used in Pyramid. They allow for more reliable
customization. See :ref:`intro_asset_specs` for more information.
Example: :ref:`renderers_chapter`.
@@ -412,27 +415,19 @@ Example: :ref:`events_chapter` and :ref:`event_types`.
Build international applications
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Pyramid ships with features that allow you to write applications for
-international audiences. You can mark text in your source files and templates
-and build catalogs of messages to be translated. You can translate these
-catalogs into other languages. Users may then indicate their preference, and
-see your application in their language.
-
-Many systems which offer internationalization suffer from a common problem. A
-message in your code may have the same text as one in some other package.
-Messages can conflict with each-other, leading to translation errors. Pyramid
-solves this problem by using translation *domains*. Each application can have
-its own translation domain. Messages in one domain cannot conflict with
-messages in another. Problem solved.
+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.
Example: :ref:`i18n_chapter`.
Build efficient applications
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Views in dynamic web applications can be expensive or slow to build. Pyramid
-allows you to save the results of such a view by *caching* the rendered
-response. Indicate in configuration that you want a view to be cached::
+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::
@view_config(http_cache=3600) # 60 minutes
def myview(request): ...
@@ -446,7 +441,7 @@ documentation for more information.
Build fast applications
~~~~~~~~~~~~~~~~~~~~~~~
-The Pyramid core is fast. It has been engineered from the ground up for speed.
+The 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, Pyramid is the right choice for
you.
@@ -456,15 +451,11 @@ Example: http://blog.curiasolutions.com/pages/the-great-web-framework-shootout.h
Store session data
~~~~~~~~~~~~~~~~~~
-HTTP is a *stateless* protocol. No request can have knowledge of any other
-request. But it is often desireable to associate data with a particular user.
-Think of a shopping cart that remembers the items you have added to it even as
-you move through the shopping site finding other items to add.
-
-Pyramid allows you to use *sessions* to solve this problem. Many other
-frameworks also support sessions. But 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.
+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 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.
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
diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst
index c7b4b9d6f..9cc5b4ed8 100644
--- a/docs/narr/logging.rst
+++ b/docs/narr/logging.rst
@@ -9,14 +9,14 @@ to send log messages to loggers that you've configured.
.. warning::
- This chapter assumes you've used a :term:`scaffold` to create a project
+ This chapter assumes you've used a :term:`cookiecutter` to create a project
which contains ``development.ini`` and ``production.ini`` files which help
- configure logging. All of the scaffolds which ship with :app:`Pyramid` do
- this. If you're not using a scaffold, or if you've used a third-party
- scaffold which does not create these files, the configuration information in
+ configure logging. All of the Pyramid cookiecutters provided by the Pylons Project do
+ this. If you're not using a cookiecutter, or if you've used a third-party
+ 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
@@ -26,11 +26,11 @@ to send log messages to loggers that you've configured.
Logging Configuration
---------------------
-A :app:`Pyramid` project created from a :term:`scaffold` is configured to allow
+A :app:`Pyramid` project created from a :term:`cookiecutter` is configured to allow
you to send messages to :mod:`Python standard library logging package
<logging>` loggers from within your application. In particular, the
:term:`PasteDeploy` ``development.ini`` and ``production.ini`` files created
-when you use a scaffold include a basic configuration for the Python
+when you use a cookiecutter include a basic configuration for the Python
:mod:`logging` package.
PasteDeploy ``.ini`` files use the Python standard library :mod:`ConfigParser
@@ -43,94 +43,20 @@ from when you run ``pserve``.
The ``pserve`` command calls the :func:`pyramid.paster.setup_logging` function,
a thin wrapper around the :func:`logging.config.fileConfig` using the specified
``.ini`` file, if it contains a ``[loggers]`` section (all of the
-scaffold-generated ``.ini`` files do). ``setup_logging`` reads the logging
+cookiecutter-generated ``.ini`` files do). ``setup_logging`` reads the logging
configuration from the ini file upon which ``pserve`` was invoked.
Default logging configuration is provided in both the default
-``development.ini`` and the ``production.ini`` file. The logging configuration
+``development.ini`` and the ``production.ini`` files. If you use ``pyramid-cookiecutter-starter`` to generate a Pyramid project with the name of the package as ``hello_world``, then the logging configuration
in the ``development.ini`` file is as follows:
-.. code-block:: ini
- :linenos:
-
- # Begin logging configuration
-
- [loggers]
- keys = root, {{package_logger}}
-
- [handlers]
- keys = console
-
- [formatters]
- keys = generic
-
- [logger_root]
- level = INFO
- handlers = console
-
- [logger_{{package_logger}}]
- level = DEBUG
- handlers =
- qualname = {{package}}
-
- [handler_console]
- class = StreamHandler
- args = (sys.stderr,)
- level = NOTSET
- formatter = generic
-
- [formatter_generic]
- format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
- # End logging configuration
+.. literalinclude:: myproject/development.ini
+ :language: ini
+ :lineno-match:
+ :lines: 29-
The ``production.ini`` file uses the ``WARN`` level in its logger
-configuration, but it is otherwise identical.
-
-The name ``{{package_logger}}`` above will be replaced with the name of your
-project's :term:`package`, which is derived from the name you provide to your
-project. For instance, if you do:
-
-.. code-block:: text
- :linenos:
-
- pcreate -s starter MyApp
-
-The logging configuration will literally be:
-
-.. code-block:: ini
- :linenos:
-
- # Begin logging configuration
-
- [loggers]
- keys = root, myapp
-
- [handlers]
- keys = console
-
- [formatters]
- keys = generic
-
- [logger_root]
- level = INFO
- handlers = console
-
- [logger_myapp]
- level = DEBUG
- handlers =
- qualname = myapp
-
- [handler_console]
- class = StreamHandler
- args = (sys.stderr,)
- level = NOTSET
- formatter = generic
-
- [formatter_generic]
- format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
- # End logging configuration
+configuration instead of ``DEBUG``, but it is otherwise identical.
In this logging configuration:
@@ -149,7 +75,7 @@ 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 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 scaffold.
+logging location from any :app:`Pyramid` application generated via a cookiecutter.
:app:`Pyramid` and many other libraries (such as Beaker, SQLAlchemy, Paste) log
a number of messages to the root logger for debugging purposes. Switching the
@@ -162,9 +88,9 @@ root logger level to ``DEBUG`` reveals them:
level = DEBUG
handlers = console
-Some scaffolds configure additional loggers for additional subsystems they use
+Some cookiecutters configure additional loggers for additional subsystems they use
(such as SQLALchemy). Take a look at the ``production.ini`` and
-``development.ini`` files rendered when you create a project from a scaffold.
+``development.ini`` files rendered when you create a project from a cookiecutter.
Sending Logging Messages
------------------------
@@ -327,14 +253,14 @@ translogger and your application in it. For instance, change from this:
.. code-block:: ini
[app:main]
- use = egg:MyProject
+ use = egg:myproject
To this:
.. code-block:: ini
[app:mypyramidapp]
- use = egg:MyProject
+ use = egg:myproject
[filter:translogger]
use = egg:Paste#translogger
diff --git a/docs/narr/myproject/.coveragerc b/docs/narr/myproject/.coveragerc
new file mode 100644
index 000000000..f0c31d6d7
--- /dev/null
+++ b/docs/narr/myproject/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = myproject
+omit = myproject/test*
diff --git a/docs/narr/myproject/CHANGES.txt b/docs/narr/myproject/CHANGES.txt
new file mode 100644
index 000000000..14b902fd1
--- /dev/null
+++ b/docs/narr/myproject/CHANGES.txt
@@ -0,0 +1,4 @@
+0.0
+---
+
+- Initial version.
diff --git a/docs/narr/MyProject/MANIFEST.in b/docs/narr/myproject/MANIFEST.in
index fa1692163..1c24b8c0c 100644
--- a/docs/narr/MyProject/MANIFEST.in
+++ b/docs/narr/myproject/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include myproject *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include myproject *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/narr/myproject/README.txt b/docs/narr/myproject/README.txt
new file mode 100644
index 000000000..2ffc0acba
--- /dev/null
+++ b/docs/narr/myproject/README.txt
@@ -0,0 +1,29 @@
+MyProject
+=========
+
+Getting Started
+---------------
+
+- Change directory into your newly created project.
+
+ cd MyProject
+
+- Create a Python virtual environment.
+
+ python3 -m venv env
+
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/narr/MyProject/development.ini b/docs/narr/myproject/development.ini
index 94fece8ce..20a8a4868 100644
--- a/docs/narr/MyProject/development.ini
+++ b/docs/narr/myproject/development.ini
@@ -1,10 +1,10 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
-use = egg:MyProject
+use = egg:myproject
pyramid.reload_templates = true
pyramid.debug_authorization = false
@@ -24,12 +24,11 @@ pyramid.includes =
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/narr/MyProject/myproject/__init__.py b/docs/narr/myproject/myproject/__init__.py
index ad5ecbc6f..49dde36d4 100644
--- a/docs/narr/MyProject/myproject/__init__.py
+++ b/docs/narr/myproject/myproject/__init__.py
@@ -5,7 +5,7 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(settings=settings)
- config.include('pyramid_chameleon')
+ config.include('pyramid_jinja2')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.scan()
diff --git a/docs/narr/MyProject/myproject/static/pyramid-16x16.png b/docs/narr/myproject/myproject/static/pyramid-16x16.png
index 979203112..979203112 100644
--- a/docs/narr/MyProject/myproject/static/pyramid-16x16.png
+++ b/docs/narr/myproject/myproject/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/narr/MyProject/myproject/static/pyramid.png b/docs/narr/myproject/myproject/static/pyramid.png
index 4ab837be9..4ab837be9 100644
--- a/docs/narr/MyProject/myproject/static/pyramid.png
+++ b/docs/narr/myproject/myproject/static/pyramid.png
Binary files differ
diff --git a/docs/narr/MyProject/myproject/static/theme.css b/docs/narr/myproject/myproject/static/theme.css
index be50ad420..0f4b1a4d4 100644
--- a/docs/narr/MyProject/myproject/static/theme.css
+++ b/docs/narr/myproject/myproject/static/theme.css
@@ -72,10 +72,12 @@ p {
color: #f2b7bd;
font-weight: 400;
}
-.starter-template .links ul li a {
- color: #ffffff;
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
}
-.starter-template .links ul li a:hover {
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
text-decoration: underline;
}
.starter-template .links ul li .icon-muted {
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt b/docs/narr/myproject/myproject/templates/layout.jinja2
index f8cbe2e2c..bfac9e64e 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt
+++ b/docs/narr/myproject/myproject/templates/layout.jinja2
@@ -1,20 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('myproject:static/pyramid-16x16.png')}}">
- <title>ZODB Scaffold for The Pyramid Web Framework</title>
+ <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">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <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]>
@@ -29,22 +29,19 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('myproject:static/pyramid.png') }}" alt="pyramid web framework">
</div>
<div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
- </div>
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
</div>
</div>
<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><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-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>
</ul>
</div>
diff --git a/docs/narr/myproject/myproject/templates/mytemplate.jinja2 b/docs/narr/myproject/myproject/templates/mytemplate.jinja2
new file mode 100644
index 000000000..ce042215d
--- /dev/null
+++ b/docs/narr/myproject/myproject/templates/mytemplate.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% 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>
+</div>
+{% endblock content %}
diff --git a/docs/narr/MyProject/myproject/tests.py b/docs/narr/myproject/myproject/tests.py
index fd414cced..fd414cced 100644
--- a/docs/narr/MyProject/myproject/tests.py
+++ b/docs/narr/myproject/myproject/tests.py
diff --git a/docs/narr/MyProject/myproject/views.py b/docs/narr/myproject/myproject/views.py
index c383c5716..9e9ec4320 100644
--- a/docs/narr/MyProject/myproject/views.py
+++ b/docs/narr/myproject/myproject/views.py
@@ -1,6 +1,6 @@
from pyramid.view import view_config
-@view_config(route_name='home', renderer='templates/mytemplate.pt')
+@view_config(route_name='home', renderer='templates/mytemplate.jinja2')
def my_view(request):
return {'project': 'MyProject'}
diff --git a/docs/quick_tutorial/scaffolds/production.ini b/docs/narr/myproject/production.ini
index 1418e6bf6..13be488e7 100644
--- a/docs/quick_tutorial/scaffolds/production.ini
+++ b/docs/narr/myproject/production.ini
@@ -4,7 +4,7 @@
###
[app:main]
-use = egg:scaffolds
+use = egg:myproject
pyramid.reload_templates = false
pyramid.debug_authorization = false
@@ -18,8 +18,7 @@ pyramid.default_locale_name = en
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
@@ -27,7 +26,7 @@ port = 6543
###
[loggers]
-keys = root, scaffolds
+keys = root, myproject
[handlers]
keys = console
@@ -39,10 +38,10 @@ keys = generic
level = WARN
handlers = console
-[logger_scaffolds]
+[logger_myproject]
level = WARN
handlers =
-qualname = scaffolds
+qualname = myproject
[handler_console]
class = StreamHandler
@@ -51,4 +50,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/narr/myproject/pytest.ini b/docs/narr/myproject/pytest.ini
new file mode 100644
index 000000000..b1b5f4c38
--- /dev/null
+++ b/docs/narr/myproject/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = myproject
+python_files = *.py
diff --git a/docs/narr/myproject/setup.py b/docs/narr/myproject/setup.py
new file mode 100644
index 000000000..00e377349
--- /dev/null
+++ b/docs/narr/myproject/setup.py
@@ -0,0 +1,51 @@
+import os
+
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
+
+requires = [
+ 'pyramid',
+ 'pyramid_jinja2',
+ 'pyramid_debugtoolbar',
+ 'waitress',
+]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest',
+ 'pytest-cov',
+]
+
+setup(
+ name='myproject',
+ version='0.0',
+ description='MyProject',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = myproject:main',
+ ],
+ },
+)
diff --git a/docs/narr/paste.rst b/docs/narr/paste.rst
index 0a217e6e3..26cb1bfa5 100644
--- a/docs/narr/paste.rst
+++ b/docs/narr/paste.rst
@@ -3,7 +3,7 @@
PasteDeploy Configuration Files
===============================
-Packages generated via a :term:`scaffold` make use of a system created by Ian
+Packages generated via a :term:`cookiecutter` make use of a system created by Ian
Bicking named :term:`PasteDeploy`. PasteDeploy defines a way to declare
:term:`WSGI` application configuration in an ``.ini`` file.
@@ -14,7 +14,7 @@ runner ``pserve``, as well as other commands such as ``pviews``, ``pshell``,
PasteDeploy is not a particularly integral part of Pyramid. It's possible to
create a Pyramid application which does not use PasteDeploy at all. We show a
Pyramid application that doesn't use PasteDeploy in :ref:`firstapp_chapter`.
-However, all Pyramid scaffolds render PasteDeploy configuration files, to
+However, all Pyramid cookiecutters render PasteDeploy configuration files, to
provide new developers with a standardized way of setting deployment values,
and to provide new users with a standardized way of starting, stopping, and
debugging an application.
@@ -26,12 +26,7 @@ documentation, see http://pythonpaste.org/deploy/.
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:
@@ -40,25 +35,25 @@ Entry Points and PasteDeploy ``.ini`` Files
In the :ref:`project_narr` chapter, we breezed over the meaning of a
configuration line in the ``deployment.ini`` file. This was the ``use =
-egg:MyProject`` line in the ``[app:main]`` section. We breezed over it because
+egg:myproject`` line in the ``[app:main]`` section. We breezed over it because
it's pretty confusing and "too much information" for an introduction to the
system. We'll try to give it a bit of attention here. Let's see the config
file again:
-.. literalinclude:: MyProject/development.ini
+.. literalinclude:: myproject/development.ini
:language: ini
:linenos:
-The line in ``[app:main]`` above that says ``use = egg:MyProject`` is actually
-shorthand for a longer spelling: ``use = egg:MyProject#main``. The ``#main``
+The line in ``[app:main]`` above that says ``use = egg:myproject`` is actually
+shorthand for a longer spelling: ``use = egg:myproject#main``. The ``#main``
part is omitted for brevity, as ``#main`` is a default defined by PasteDeploy.
-``egg:MyProject#main`` is a string which has meaning to PasteDeploy. It points
+``egg:myproject#main`` is a string which has meaning to PasteDeploy. It points
at a :term:`setuptools` :term:`entry point` named ``main`` defined in the
-``MyProject`` project.
+``myproject`` project.
Take a look at the generated ``setup.py`` file for this project.
-.. literalinclude:: MyProject/setup.py
+.. literalinclude:: myproject/setup.py
:language: python
:linenos:
@@ -66,21 +61,21 @@ Note that ``entry_points`` is assigned a string which looks a lot like an
``.ini`` file. This string representation of an ``.ini`` file has a section
named ``[paste.app_factory]``. Within this section, there is a key named
``main`` (the entry point name) which has a value ``myproject:main``. The
-*key* ``main`` is what our ``egg:MyProject#main`` value of the ``use`` section
+*key* ``main`` is what our ``egg:myproject#main`` value of the ``use`` section
in our config file is pointing at, although it is actually shortened to
-``egg:MyProject`` there. The value represents a :term:`dotted Python name`
+``egg:myproject`` there. The value represents a :term:`dotted Python name`
path, which refers to a callable in our ``myproject`` package's ``__init__.py``
module.
-The ``egg:`` prefix in ``egg:MyProject`` indicates that this is an entry point
+The ``egg:`` prefix in ``egg:myproject`` indicates that this is an entry point
*URI* specifier, where the "scheme" is "egg". An "egg" is created when you run
``setup.py install`` or ``setup.py develop`` within your project.
In English, this entry point can thus be referred to as a "PasteDeploy
-application factory in the ``MyProject`` project which has the entry point
+application factory in the ``myproject`` project which has the entry point
named ``main`` where the entry point refers to a ``main`` function in the
``mypackage`` module". Indeed, if you open up the ``__init__.py`` module
-generated within any scaffold-generated package, you'll see a ``main``
+generated within any cookiecutter-generated package, you'll see a ``main``
function. This is the function called by :term:`PasteDeploy` when the
``pserve`` command is invoked against our application. It accepts a global
configuration object and *returns* an instance of our application.
@@ -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.png b/docs/narr/project.png
index e1afd97d4..0e17e57ab 100644
--- a/docs/narr/project.png
+++ b/docs/narr/project.png
Binary files differ
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 71bd176f6..a150afc6b 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -5,94 +5,132 @@ Creating a :app:`Pyramid` Project
As we saw in :ref:`firstapp_chapter`, it's possible to create a :app:`Pyramid`
application completely manually. However, it's usually more convenient to use
-a :term:`scaffold` to generate a basic :app:`Pyramid` :term:`project`.
+a :term:`cookiecutter` to generate a basic :app:`Pyramid` :term:`project`.
A project is a directory that contains at least one Python :term:`package`.
-You'll use a scaffold to create a project, and you'll create your application
+You'll use a cookiecutter to create a project, and you'll create your application
logic within a package that lives inside the project. Even if your application
is extremely simple, it is useful to place code that drives the application
within a package, because (1) a package is more easily extended with new code,
and (2) an application that lives inside a package can also be distributed more
easily than one which does not live within a package.
-:app:`Pyramid` comes with a variety of scaffolds that you can use to generate a
-project. Each scaffold makes different configuration assumptions about what
+The Pylons Project provides several :app:`Pyramid` cookiecutters that you can use to generate a
+project. Each cookiecutter makes different configuration assumptions about what
type of application you're trying to construct.
-These scaffolds are rendered using the ``pcreate`` command that is installed as
-part of Pyramid.
+These cookiecutters are rendered using the ``cookiecutter`` command that you may install.
+
+.. seealso::
+
+ See also `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_.
+
.. index::
- single: scaffolds
- single: starter scaffold
- single: zodb scaffold
- single: alchemy scaffold
+ single: cookiecutters
+ single: pyramid-cookiecutter-starter
+ single: pyramid-cookiecutter-zodb
+ single: pyramid-cookiecutter-alchemy
-.. _additional_paster_scaffolds:
+.. _additional_cookiecutters:
-Scaffolds Included with :app:`Pyramid`
---------------------------------------
+:app:`Pyramid` cookiecutters
+----------------------------
+
+Pyramid cookiecutters released under the Pylons Project differ from each other on a number of axes:
+
+- the persistence mechanism they offer (no persistence mechanism, :term:`SQLAlchemy` with SQLite, or :term:`ZODB`)
-The convenience scaffolds included with :app:`Pyramid` differ from each other
-on a number of axes:
+- the mechanism they use to map URLs to code (:term:`URL dispatch` or :term:`traversal`)
-- the persistence mechanism they offer (no persistence mechanism, :term:`ZODB`,
- or :term:`SQLAlchemy`)
+- templating libraries (:term:`Jinja2`, :term:`Chameleon`, or :term:`Mako`)
-- the mechanism they use to map URLs to code (:term:`traversal` or :term:`URL
- dispatch`)
+* `pyramid-cookiecutter-starter <https://github.com/Pylons/pyramid-cookiecutter-starter>`_
+* `pyramid-cookiecutter-alchemy <https://github.com/Pylons/pyramid-cookiecutter-alchemy>`_
+* `pyramid-cookiecutter-zodb <https://github.com/Pylons/pyramid-cookiecutter-zodb>`_
-The included scaffolds are these:
+These cookiecutters include:
-``starter``
- URL mapping via :term:`URL dispatch` and no persistence mechanism
+``pyramid-cookiecutter-starter``
+ :term:`URL dispatch` for routing and either :term:`Jinja2`, :term:`Chameleon`, or :term:`Mako` for templating
-``zodb``
- URL mapping via :term:`traversal` and persistence via :term:`ZODB`
+``pyramid-cookiecutter-alchemy``
+ SQLite for persistent storage, :term:`SQLAlchemy` for an ORM, :term:`URL dispatch` for routing, and :term:`Jinja2` for templating.
-``alchemy``
- URL mapping via :term:`URL dispatch` and persistence via :term:`SQLAlchemy`
+``pyramid-cookiecutter-zodb``
+ :term:`ZODB` for persistent storage, :term:`traversal` for routing, and :term:`Chameleon` for templating
.. index::
single: creating a project
single: project
- single: pcreate
+ single: cookiecutter
.. _creating_a_project:
Creating the Project
--------------------
-.. seealso:: See also the output of :ref:`pcreate --help <pcreate_script>`.
-
In :ref:`installing_chapter`, you created a virtual Python environment via the
-``venv`` command. To start a :app:`Pyramid` :term:`project`, use the
-``pcreate`` command installed within the virtual environment. We'll choose the
-``starter`` scaffold for this purpose. When we invoke ``pcreate``, it will
-create a directory that represents our project.
+``venv`` command. We called the virtual environment directory
+``env`` and set an environment variable ``VENV`` to its path.
+
+We assume that you :ref:`previously installed cookiecutter <cookiecutters>`, following its installation instructions.
+
+We'll choose ``pyramid-cookiecutter-starter`` to start the project. When we invoke ``cookiecutter``, it will create a directory that represents our project.
-In :ref:`installing_chapter` we called the virtual environment directory
-``env``. The following commands assume that our current working directory is
-the ``env`` directory.
+We assume our current working directory is the value of ``VENV``.
+
+On all platforms, generate a project using cookiecutter.
+
+.. code-block:: bash
-The below example uses the ``pcreate`` command to create a project with the
-``starter`` scaffold.
+ $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter
+
+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
+
+We then run through the following commands.
On UNIX:
.. code-block:: bash
- $ $VENV/bin/pcreate -s starter MyProject
+ # Reset our environment variable for a new virtual environment.
+ $ export VENV=~/env/myproject/env
+ # Change directory into your newly created project.
+ $ cd myproject
+ # Create a new virtual environment...
+ $ python3 -m venv $VENV
+ # ...where we upgrade packaging tools.
+ $ env/bin/pip install --upgrade pip setuptools
Or on Windows:
.. code-block:: doscon
- c:\> %VENV%\Scripts\pcreate -s starter MyProject
-
-As a result of invoking the ``pcreate`` command, a directory named
-``MyProject`` is created. That directory is a :term:`project` directory. The
+ # Reset our environment variable for a new virtual environment.
+ c:\> set VENV=c:\env\myproject\env
+ # Change directory into your newly created project.
+ c:\> cd myproject
+ # Create a new virtual environment...
+ c:\myproject> python -m venv %VENV%
+ # ...where we upgrade packaging tools.
+ c:\myproject> %VENV%\Scripts\pip install --upgrade pip setuptools
+
+As a result of invoking the ``cookiecutter`` command, a directory named
+``myproject`` is created. That directory is a :term:`project` directory. The
``setup.py`` file in that directory can be used to distribute your application,
or install your application for deployment or development.
@@ -107,15 +145,15 @@ debugger (to prevent inappropriate access and disclosure), and turns off a
number of debugging settings. You can use this file to put your application
into production.
-The ``MyProject`` project directory contains an additional subdirectory named
+The ``myproject`` project directory contains an additional subdirectory named
``myproject`` (note the case difference) representing a Python :term:`package`
which holds very simple :app:`Pyramid` sample code. This is where you'll edit
your application's Python code and templates.
-We created this project within an ``env`` virtual environment directory.
+We created this project in a directory next to its virtual environment directory.
However, note that this is not mandatory. The project directory can go more or
less anywhere on your filesystem. You don't need to put it in a special "web
-server" directory, and you don't need to put it within a virtual environment
+server" directory. You could put it within a virtual environment
directory. The author uses Linux mainly, and tends to put project directories
which he creates within his ``~/projects`` directory. On Windows, it's a good
idea to put project directories within a directory that contains no space
@@ -125,7 +163,7 @@ projects in ``C:\projects``.
.. warning::
- You'll need to avoid using ``pcreate`` to create a project with the same
+ You'll need to avoid using ``cookiecutter`` to create a project with the same
name as a Python standard library component. In particular, this means you
should avoid using the names ``site`` or ``test``, both of which conflict
with Python standard library packages. You should also avoid using the name
@@ -144,7 +182,7 @@ newly created project directory and use the Python interpreter from the
invoke the command ``pip install -e .``, which installs the project in
development mode (``-e`` is for "editable") into the current directory (``.``).
-The file named ``setup.py`` will be in the root of the pcreate-generated
+The file named ``setup.py`` will be in the root of the cookiecutter-generated
project directory. The ``python`` you're invoking should be the one that lives
in the ``bin`` (or ``Scripts`` on Windows) directory of your virtual Python
environment. Your terminal's current working directory *must* be the newly
@@ -154,25 +192,24 @@ On UNIX:
.. code-block:: bash
- $ cd MyProject
- $ $VENV/bin/pip install -e .
+ $ $VENV/bin/pip install -e .
Or on Windows:
.. code-block:: doscon
- c:\> cd MyProject
- c:\> %VENV%\Scripts\pip install -e .
+ c:\env\myproject> %VENV%\Scripts\pip install -e .
Elided output from a run of this command on UNIX is shown below:
.. code-block:: bash
- $ cd MyProject
- $ $VENV/bin/pip install -e .
- ...
- Successfully installed Chameleon-2.24 Mako-1.0.4 MyProject \
- pyramid-chameleon-0.3 pyramid-debugtoolbar-2.4.2 pyramid-mako-1.0.2
+ Running setup.py develop for myproject
+ Successfully installed Jinja2-2.8 Mako-1.0.6 MarkupSafe-0.23 \
+ PasteDeploy-1.5.2 Pygments-2.1.3 WebOb-1.7.0 myproject pyramid-1.7.3 \
+ pyramid-debugtoolbar-3.0.5 pyramid-jinja2-2.7 pyramid-mako-1.0.2 \
+ repoze.lru-0.6 translationstring-1.3 venusian-1.0 waitress-1.0.1 \
+ zope.deprecation-4.2.0 zope.interface-4.3.3
This will install a :term:`distribution` representing your project into the
virtual environment interpreter's library set so it can be found by ``import``
@@ -199,7 +236,7 @@ On Windows:
.. code-block:: doscon
- c:\> %VENV%\Scripts\pip install -e ".[testing]"
+ c:\env\myproject> %VENV%\Scripts\pip install -e ".[testing]"
Once the testing requirements are installed, then you can run the tests using
the ``py.test`` command that was just installed in the ``bin`` directory of
@@ -215,7 +252,7 @@ On Windows:
.. code-block:: doscon
- c:\> %VENV%\Scripts\py.test -q
+ c:\env\myproject> %VENV%\Scripts\py.test -q
Here's sample output from a test run on UNIX:
@@ -225,9 +262,7 @@ Here's sample output from a test run on UNIX:
..
2 passed in 0.47 seconds
-The tests themselves are found in the ``tests.py`` module in your ``pcreate``
-generated project. Within a project generated by the ``starter`` scaffold,
-only two sample tests exist.
+The tests themselves are found in the ``tests.py`` module in your ``cookiecutter``-generated project. Within a project generated by the ``pyramid-cookiecutter-starter`` cookiecutter, only two sample tests exist.
.. note::
@@ -242,7 +277,7 @@ to ``py.test``:
$ $VENV/bin/py.test --cov -q
-Scaffolds include configuration defaults for ``py.test`` and test coverage.
+Cookiecutters include configuration defaults for ``py.test`` and test coverage.
These configuration files are ``pytest.ini`` and ``.coveragerc``, located at
the root of your package. Without these defaults, we would need to specify the
path to the module on which we want to run tests and coverage.
@@ -280,31 +315,31 @@ On UNIX:
On Windows:
-.. code-block:: text
+.. code-block:: doscon
- c:\> %VENV%\Scripts\pserve development.ini
+ c:\env\myproject> %VENV%\Scripts\pserve development.ini
Here's sample output from a run of ``pserve`` on UNIX:
.. code-block:: bash
$ $VENV/bin/pserve development.ini
- Starting server in PID 16208.
- serving on http://127.0.0.1:6543
+ Starting server in PID 77171.
+ Serving on http://localhost:6543
+ Serving on http://localhost:6543
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 ``host`` value in the
-``[server:main]`` section, changing it from ``127.0.0.1`` to ``0.0.0.0``. For
-example:
+``development.ini`` file, and replace the ``listen`` value in the
+``[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
[server:main]
use = egg:waitress#main
- host = 0.0.0.0
- port = 6543
+ listen = *:6543
Now when you use ``pserve`` to start the application, it will respond to
requests on *all* IP addresses possessed by your system, not just requests to
@@ -316,19 +351,20 @@ the case, if you use a browser running on the same system as Pyramid, it will
be able to access the application via ``http://127.0.0.1:6543/`` as well as via
``http://192.168.1.50:6543/``. However, *other people* on other computers on
the same network will also be able to visit your Pyramid application in their
-browser by visiting ``http://192.168.1.50:6543/``.
+browser by visiting ``http://192.168.1.50:6543/``. The same holds true if you use
+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
-``port = 6543`` line in the ``development.ini`` file's ``[server:main]``
-section to ``port = 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).
The default server used to run your Pyramid application when a project is
-created from a scaffold is named :term:`Waitress`. This server is what prints
-the ``serving on...`` line when you run ``pserve``. It's a good idea to use
+created from a cookiecutter is named :term:`Waitress`. This server is what prints
+the ``Serving on...`` line when you run ``pserve``. It's a good idea to use
this server during development because it's very simple. It can also be used
for light production. Setting your application up under a different server is
not advised until you've done some development work under the default server,
@@ -365,7 +401,8 @@ For example, on UNIX:
$ $VENV/bin/pserve development.ini --reload
Starting subprocess with file monitor
Starting server in PID 16601.
- serving on http://127.0.0.1:6543
+ Serving on http://localhost:6543
+ Serving on http://localhost:6543
Now if you make a change to any of your project's ``.py`` files or ``.ini``
files, you'll see the server restart automatically:
@@ -375,12 +412,13 @@ files, you'll see the server restart automatically:
development.ini changed; reloading...
-------------------- Restarting --------------------
Starting server in PID 16602.
- serving on http://127.0.0.1:6543
+ Serving on http://localhost:6543
+ Serving on http://localhost:6543
Changes to template files (such as ``.pt`` or ``.mak`` files) won't cause the
server to restart. Changes to template files don't require a server restart as
long as the ``pyramid.reload_templates`` setting in the ``development.ini``
-file is ``true``. Changes made to template files when this setting is true
+file is ``true``. Changes made to template files when this setting is ``true``
will take effect immediately without a server restart.
.. index::
@@ -395,8 +433,8 @@ browser like what is displayed in the following image:
.. image:: project.png
-This is the page shown by default when you visit an unmodified ``pcreate``
-generated ``starter`` application in a browser.
+This is the page shown by default when you visit an unmodified ``cookiecutter``
+generated ``pyramid-cookiecutter-starter`` application in a browser.
.. index::
single: debug toolbar
@@ -477,48 +515,57 @@ this:
The Project Structure
---------------------
-The ``starter`` scaffold generated a :term:`project` (named ``MyProject``),
+The ``pyramid-cookiecutter-starter`` cookiecutter generated a :term:`project` (named ``myproject``),
which contains a Python :term:`package`. The package is *also* named
-``myproject``, but it's lowercased; the scaffold generates a project which
-contains a package that shares its name except for case.
+``myproject``; the cookiecutter generates a project which
+contains a package that shares its name.
-All :app:`Pyramid` ``pcreate``-generated projects share a similar structure.
-The ``MyProject`` project we've generated has the following directory structure:
+All :app:`Pyramid` ``cookiecutter``-generated projects share a similar structure.
+The ``myproject`` project we've generated has the following directory structure:
.. code-block:: text
- MyProject/
- |-- CHANGES.txt
- |-- development.ini
- |-- MANIFEST.in
- |-- myproject
- | |-- __init__.py
- | |-- static
- | | |-- pyramid-16x16.png
- | | |-- pyramid.png
- | | |-- theme.css
- | | `-- theme.min.css
- | |-- templates
- | | `-- mytemplate.pt
- | |-- tests.py
- | `-- views.py
- |-- production.ini
- |-- README.txt
- `-- setup.py
-
-The ``MyProject`` :term:`Project`
+ myproject/
+ ├── .coveragerc
+ ├── CHANGES.txt
+ ├── MANIFEST.in
+ ├── myproject
+ │   ├── __init__.py
+ │   ├── static
+ │   │   ├── pyramid-16x16.png
+ │   │   ├── pyramid.png
+ │   │   └── theme.css
+ │   ├── templates
+ │   │   ├── layout.jinja2
+ │   │   └── mytemplate.jinja2
+ │   ├── tests.py
+ │   └── views.py
+ ├── README.txt
+ ├── development.ini
+ ├── production.ini
+ ├── pytest.ini
+ └── setup.py
+
+
+The ``myproject`` :term:`Project`
---------------------------------
-The ``MyProject`` :term:`project` directory is the distribution and deployment
+The ``myproject`` :term:`project` directory is the distribution and deployment
wrapper for your application. It contains both the ``myproject``
:term:`package` representing your application as well as files used to
describe, run, and test your application.
+#. ``.coveragerc`` configures coverage when running tests.
+
#. ``CHANGES.txt`` describes the changes you've made to the application. It is
- conventionally written in :term:`ReStructuredText` format.
+ conventionally written in :term:`reStructuredText` format.
+
+#. ``MANIFEST.in`` is a :term:`distutils` "manifest" file, naming which files
+ should be included in a source distribution of the package when ``python
+ setup.py sdist`` is run.
#. ``README.txt`` describes the application in general. It is conventionally
- written in :term:`ReStructuredText` format.
+ written in :term:`reStructuredText` format.
#. ``development.ini`` is a :term:`PasteDeploy` configuration file that can be
used to execute your application during development.
@@ -526,9 +573,7 @@ describe, run, and test your application.
#. ``production.ini`` is a :term:`PasteDeploy` configuration file that can be
used to execute your application in a production configuration.
-#. ``MANIFEST.in`` is a :term:`distutils` "manifest" file, naming which files
- should be included in a source distribution of the package when ``python
- setup.py sdist`` is run.
+#. ``pytest.ini`` is a configuration file for running tests.
#. ``setup.py`` is the file you'll use to test and distribute your application.
It is a standard :term:`setuptools` ``setup.py`` file.
@@ -537,7 +582,7 @@ describe, run, and test your application.
single: PasteDeploy
single: ini file
-.. _MyProject_ini:
+.. _myproject_ini:
``development.ini``
~~~~~~~~~~~~~~~~~~~
@@ -548,7 +593,7 @@ as the deployment settings provided to that application.
The generated ``development.ini`` file looks like so:
-.. literalinclude:: MyProject/development.ini
+.. literalinclude:: myproject/development.ini
:language: ini
:linenos:
@@ -557,15 +602,15 @@ This file contains several sections including ``[app:main]``,
The ``[app:main]`` section represents configuration for your :app:`Pyramid`
application. The ``use`` setting is the only setting required to be present in
-the ``[app:main]`` section. Its default value, ``egg:MyProject``, indicates
-that our MyProject project contains the application that should be served.
+the ``[app:main]`` section. Its default value, ``egg:myproject``, indicates
+that our myproject project contains the application that should be served.
Other settings added to this section are passed as keyword arguments to the
function named ``main`` in our package's ``__init__.py`` module. You can
provide startup-time configuration parameters to your application by adding
more settings to this section.
.. seealso:: See :ref:`pastedeploy_entry_points` for more information about the
- meaning of the ``use = egg:MyProject`` value in this section.
+ meaning of the ``use = egg:myproject`` value in this section.
The ``pyramid.reload_templates`` setting in the ``[app:main]`` section is a
:app:`Pyramid`-specific setting which is passed into the framework. If it
@@ -599,7 +644,7 @@ The ``[server:main]`` section of the configuration file configures a WSGI
server which listens on TCP port 6543. It is configured to listen on localhost
only (``127.0.0.1``).
-.. _MyProject_ini_logging:
+.. _myproject_ini_logging:
The sections after ``# logging configuration`` represent Python's standard
library :mod:`logging` module configuration for your application. These
@@ -660,7 +705,7 @@ directory didn't contain a ``MANIFEST.in`` file that told the ``sdist``
machinery to include ``*.pt`` files, the ``myproject/templates/mytemplate.pt``
file would not be included in the generated tarball.
-Projects generated by Pyramid scaffolds include a default ``MANIFEST.in`` file.
+Projects generated by Pyramid cookiecutters include a default ``MANIFEST.in`` file.
The ``MANIFEST.in`` file contains declarations which tell it to include files
like ``*.pt``, ``*.css`` and ``*.js`` in the generated tarball. If you include
files with extensions other than the files named in the project's
@@ -700,7 +745,7 @@ testing, as well as distributing your application.
Our generated ``setup.py`` looks like this:
-.. literalinclude:: MyProject/setup.py
+.. literalinclude:: myproject/setup.py
:language: python
:linenos:
@@ -743,7 +788,7 @@ you can try this command now:
$ $VENV/bin/python setup.py sdist
This will create a tarball of your application in a ``dist`` subdirectory named
-``MyProject-0.0.tar.gz``. You can send this tarball to other people who want
+``myproject-0.0.tar.gz``. You can send this tarball to other people who want
to install and use your application.
.. index::
@@ -752,7 +797,7 @@ to install and use your application.
The ``myproject`` :term:`Package`
---------------------------------
-The ``myproject`` :term:`package` lives inside the ``MyProject``
+The ``myproject`` :term:`package` lives inside the ``myproject``
:term:`project`. It contains:
#. An ``__init__.py`` file signifies that this is a Python :term:`package`. It
@@ -760,14 +805,14 @@ The ``myproject`` :term:`package` lives inside the ``MyProject``
``main`` function which is used as a entry point for commands such as
``pserve``, ``pshell``, ``pviews``, and others.
-#. A ``templates`` directory, which contains :term:`Chameleon` (or other types
+#. A ``templates`` directory, which contains :term:`Jinja2` (or other types
of) templates.
#. A ``tests.py`` module, which contains unit test code for the application.
#. A ``views.py`` module, which contains view code for the application.
-These are purely conventions established by the scaffold. :app:`Pyramid`
+These are purely conventions established by the cookiecutter. :app:`Pyramid`
doesn't insist that you name things in any particular way. However, it's
generally a good idea to follow Pyramid standards for naming, so that other
Pyramid developers can get up to speed quickly on your code when you need help.
@@ -785,7 +830,7 @@ advertises an entry point for use by our :term:`PasteDeploy` ``.ini`` file.
This is the file named ``__init__.py``. The presence of an ``__init__.py``
also informs Python that the directory which contains it is a *package*.
-.. literalinclude:: MyProject/myproject/__init__.py
+.. literalinclude:: myproject/myproject/__init__.py
:language: python
:linenos:
@@ -800,8 +845,8 @@ also informs Python that the directory which contains it is a *package*.
Line 7 creates an instance of a :term:`Configurator`.
- Line 8 adds support for Chameleon templating bindings, allowing us to
- specify renderers with the ``.pt`` extension.
+ Line 8 adds support for Jinja2 templating bindings, allowing us to
+ specify renderers with the ``.jinja2`` extension.
Line 9 registers a static view, which will serve up the files from the
``myproject:static`` :term:`asset specification` (the ``static`` directory
@@ -827,7 +872,7 @@ callables*. A :term:`view callable` is the main tool of a :app:`Pyramid` web
application developer; it is a bit of code which accepts a :term:`request` and
which returns a :term:`response`.
-.. literalinclude:: MyProject/myproject/views.py
+.. literalinclude:: myproject/myproject/views.py
:language: python
:linenos:
@@ -844,8 +889,8 @@ result of the view callable. This particular view declaration points at
specifies the ``mytemplate.pt`` file within the ``templates`` directory of the
``myproject`` package. The asset specification could have also been specified
as ``myproject:templates/mytemplate.pt``; the leading package name and colon is
-optional. The template file pointed to is a :term:`Chameleon` ZPT template
-file (``templates/my_template.pt``).
+optional. The template file pointed to is a :term:`Jinja2` template
+file (``templates/my_template.jinja2``).
This view callable function is handed a single piece of information: the
:term:`request`. The *request* is an instance of the :term:`WebOb` ``Request``
@@ -858,9 +903,9 @@ the HTML in a :term:`response`.
.. note:: Dictionaries provide values to :term:`template`\s.
-.. note:: When the application is run with the scaffold's :ref:`default
- development.ini <MyProject_ini>` configuration, :ref:`logging is set up
- <MyProject_ini_logging>` to aid debugging. If an exception is raised,
+.. note:: When the application is run with the cookiecutter's :ref:`default
+ development.ini <myproject_ini>` configuration, :ref:`logging is set up
+ <myproject_ini_logging>` to aid debugging. If an exception is raised,
uncaught tracebacks are displayed after the startup messages on :ref:`the
console running the server <running_the_project_application>`. Also
``print()`` statements may be inserted into the application for debugging to
@@ -869,7 +914,7 @@ the HTML in a :term:`response`.
.. note:: ``development.ini`` has a setting that controls how templates are
reloaded, ``pyramid.reload_templates``.
- - When set to ``True`` (as in the scaffold ``development.ini``), changed
+ - When set to ``True`` (as in the cookiecutter ``development.ini``), changed
templates automatically reload without a server restart. This is
convenient while developing, but slows template rendering speed.
@@ -900,31 +945,46 @@ the HTML in a :term:`response`.
``static``
~~~~~~~~~~
-This directory contains static assets which support the ``mytemplate.pt``
+This directory contains static assets which support the ``layout.jinja2``
template. It includes CSS and images.
-``templates/mytemplate.pt``
+
+``templates/layout.jinja2``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-This is the single :term:`Chameleon` template that exists in the project. Its
-contents are too long to show here, but it displays a default page when
-rendered. It is referenced by the call to ``@view_config`` as the ``renderer``
+This is the base layout content. It contains a single marker for content block. Other templates inherit its content, providing layout for the web application. Its contents are too long to show here, but here is an excerpt:
+
+.. literalinclude:: myproject/myproject/templates/layout.jinja2
+ :language: jinja
+ :lines: 34-38
+ :lineno-match:
+
+
+``templates/mytemplate.jinja2``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is the content :term:`Jinja2` template that exists in the project. It is referenced by the call to ``@view_config`` as the ``renderer``
of the ``my_view`` view callable in the ``views.py`` file. See
-:ref:`views_which_use_a_renderer` for more information about renderers.
+:ref:`views_which_use_a_renderer` for more information about renderers. It inherits ("extends") the HTML provided by ``layout.jinja2``, replacing the content block with its own content.
+
+.. literalinclude:: myproject/myproject/templates/mytemplate.jinja2
+ :language: jinja
+ :linenos:
Templates are accessed and used by view configurations and sometimes by view
functions themselves. See :ref:`templates_used_directly` and
:ref:`templates_used_as_renderers`.
+
.. index::
single: tests.py
``tests.py``
~~~~~~~~~~~~
-The ``tests.py`` module includes unit tests for your application.
+The ``tests.py`` module includes tests for your application.
-.. literalinclude:: MyProject/myproject/tests.py
+.. literalinclude:: myproject/myproject/tests.py
:language: python
:linenos:
@@ -946,16 +1006,16 @@ Modifying Package Structure
---------------------------
It is best practice for your application's code layout to not stray too much
-from accepted Pyramid scaffold defaults. If you refrain from changing things
+from accepted Pyramid cookiecutter defaults. If you refrain from changing things
very much, other Pyramid coders will be able to more quickly understand your
-application. However, the code layout choices made for you by a scaffold are
+application. However, the code layout choices made for you by a cookiecutter are
in no way magical or required. Despite the choices made for you by any
-scaffold, you can decide to lay your code out any way you see fit.
+cookiecutter, you can decide to lay your code out any way you see fit.
For example, the configuration method named
:meth:`~pyramid.config.Configurator.add_view` requires you to pass a
:term:`dotted Python name` or a direct object reference as the class or
-function to be used as a view. By default, the ``starter`` scaffold would have
+function to be used as a view. By default, the ``starter`` cookiecutter would have
you add view functions to the ``views.py`` module in your package. However, you
might be more comfortable creating a ``views`` *directory*, and adding a single
file for each view.
@@ -997,7 +1057,7 @@ Pyramid application via ``pserve``. This can be a useful debugging tool. See
What Is This ``pserve`` Thing
-----------------------------
-The code generated by a :app:`Pyramid` scaffold assumes that you will be using
+The code generated by a :app:`Pyramid` cookiecutter assumes that you will be using
the ``pserve`` command to start your application while you do development.
``pserve`` is a command that reads a :term:`PasteDeploy` ``.ini`` file (e.g.,
``development.ini``), and configures a server to serve a :app:`Pyramid`
@@ -1007,7 +1067,7 @@ application based on the data in the file.
application. As we saw in :ref:`firstapp_chapter`, ``pserve`` needn't be
invoked at all to run a :app:`Pyramid` application. The use of ``pserve`` to
run a :app:`Pyramid` application is purely conventional based on the output of
-its scaffolding. But we strongly recommend using ``pserve`` while developing
+its cookiecutter. But we strongly recommend using ``pserve`` while developing
your application because many other convenience introspection commands (such as
``pviews``, ``prequest``, ``proutes``, and others) are also implemented in
terms of configuration availability of this ``.ini`` file format. It also
@@ -1019,7 +1079,7 @@ restarting of the server when code changes.
Using an Alternate WSGI Server
------------------------------
-Pyramid scaffolds generate projects which use the :term:`Waitress` WSGI server.
+Pyramid cookiecutters generate projects which use the :term:`Waitress` WSGI server.
Waitress is a server that is suited for development and light production
usage. It's not the fastest nor the most featureful WSGI server. Instead, its
main feature is that it works on all platforms that Pyramid needs to run on,
@@ -1036,12 +1096,54 @@ configuration on a local system that you have complete control over; it will
provide the best development experience.
One popular production alternative to the default Waitress server is
-:term:`mod_wsgi`. You can use mod_wsgi to serve your :app:`Pyramid` application
+:term:`mod_wsgi`. You can use ``mod_wsgi`` to serve your :app:`Pyramid` application
using the Apache web server rather than any "pure-Python" server like Waitress.
It is fast and featureful. See :ref:`modwsgi_tutorial` for details.
Another good production alternative is :term:`Green Unicorn` (aka
``gunicorn``). It's faster than Waitress and slightly easier to configure than
-mod_wsgi, although it depends, in its default configuration, on having a
+``mod_wsgi``, although it depends, in its default configuration, on having a
buffering HTTP proxy in front of it. It does not, as of this writing, work on
Windows.
+
+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
+to enable this behavior. When your code crashes, ``hupper`` will wait for
+another change or the ``SIGHUP`` signal before restarting again.
+
+inotify support
+~~~~~~~~~~~~~~~
+
+By default ``hupper`` will poll the filesystem for changes to all Python
+code. This can be pretty inefficient in larger projects. To be nicer to your
+hard drive, you should install the
+`watchdog <http://pythonhosted.org/watchdog/>`_ package in development.
+``hupper`` will automatically use ``watchdog`` to more efficiently poll the
+filesystem.
+
+Monitoring Custom Files
+~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, ``pserve --reload`` will monitor all imported Python code
+(everything in ``sys.modules``) as well as the config file passed to
+``pserve`` (e.g., ``development.ini``). You can instruct ``pserve`` to watch
+other files for changes as well by defining a ``[pserve]`` section in your
+configuration file. For example, let's say your application loads the
+``favicon.ico`` file at startup and stores it in memory to efficiently
+serve it many times. When you change it, you want ``pserve`` to restart:
+
+.. code-block:: ini
+
+ [pserve]
+ watch_files =
+ myapp/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
+has some basic support for globbing. Acceptable glob patterns depend on the
+version of Python being used.
diff --git a/docs/narr/scaffolding.rst b/docs/narr/scaffolding.rst
index 164ceb3bf..27239d34e 100644
--- a/docs/narr/scaffolding.rst
+++ b/docs/narr/scaffolding.rst
@@ -3,6 +3,10 @@
Creating Pyramid Scaffolds
==========================
+.. deprecated:: 1.8
+
+ Scaffolds and the ``pcreate`` script used to generate :app:`Pyramid` projects from scaffolds have been deprecated. Use :ref:`cookiecutters` instead.
+
You can extend Pyramid by creating a :term:`scaffold` template. A scaffold
template is useful if you'd like to distribute a customizable configuration of
Pyramid to other users. Once you've created a scaffold, and someone has
diff --git a/docs/narr/security.rst b/docs/narr/security.rst
index 77e7fd707..3a6bfa5e5 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 get_csrf_token
+ token = new_csrf_token()
+
+.. 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..7e2469d54 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)
@@ -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 3e168eaea..5e7c7c871 100644
--- a/docs/narr/startup.rst
+++ b/docs/narr/startup.rst
@@ -10,11 +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://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
@@ -37,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
@@ -49,7 +57,7 @@ Here's a high-level time-ordered overview of what happens when you press
application or a pipeline, you're using a "composite" (e.g.,
``[composite:main]``), refer to the documentation for that particular
composite to understand how to make it refer to your :app:`Pyramid`
- application. In most cases, a Pyramid application built from a scaffold
+ application. In most cases, a Pyramid application built from a cookiecutter
will have a single ``[app:main]`` section in it, and this will be the
application served.
@@ -69,7 +77,7 @@ Here's a high-level time-ordered overview of what happens when you press
:app:`Pyramid` :term:`router` instance. Here's the contents of an example
``__init__.py`` module:
- .. literalinclude:: MyProject/myproject/__init__.py
+ .. literalinclude:: myproject/myproject/__init__.py
:language: python
:linenos:
@@ -85,12 +93,12 @@ Here's a high-level time-ordered overview of what happens when you press
Our generated ``development.ini`` file looks like so:
- .. literalinclude:: MyProject/development.ini
+ .. literalinclude:: myproject/development.ini
:language: ini
:linenos:
In this case, the ``myproject.__init__:main`` function referred to by the
- entry point URI ``egg:MyProject`` (see :ref:`MyProject_ini` for more
+ entry point URI ``egg:myproject`` (see :ref:`myproject_ini` for more
information about entry point URIs, and how they relate to callables) will
receive the key/value pairs ``{pyramid.reload_templates = true,
pyramid.debug_authorization = false, pyramid.debug_notfound = false,
@@ -130,10 +138,10 @@ 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 (``host =
- 127.0.0.1``), on port number 6543 (``port = 6543``). The server code itself
- is what prints ``serving on http://127.0.0.1:6543``. The server serves the
- application, and the application is running, waiting to receive requests.
+ egg:waitress#main``), and it will listen on all interfaces on port 6543
+ 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::
Logging configuration is described in the :ref:`logging_chapter` chapter.
diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst
index 6b3b5fcce..4eadbd2f0 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``.
diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst
index 354a462d4..406383bbd 100644
--- a/docs/narr/testing.rst
+++ b/docs/narr/testing.rst
@@ -370,11 +370,11 @@ coverage reports.
Regardless of which testing :term:`package` you use, be sure to add a
``tests_require`` dependency on that package to your application's ``setup.py``
-file. Using the project ``MyProject`` generated by the starter scaffold as
+file. Using the project ``myproject`` generated by the starter cookiecutter as
described in :doc:`project`, we would insert the following code immediately
-following the ``requires`` block in the file ``MyProject/setup.py``.
+following the ``requires`` block in the file ``myproject/setup.py``.
-.. literalinclude:: MyProject/setup.py
+.. literalinclude:: myproject/setup.py
:language: python
:linenos:
:lines: 11-22
@@ -383,7 +383,7 @@ following the ``requires`` block in the file ``MyProject/setup.py``.
Remember to change the dependency.
-.. literalinclude:: MyProject/setup.py
+.. literalinclude:: myproject/setup.py
:language: python
:linenos:
:lines: 40-44
@@ -401,14 +401,14 @@ In your ``MyPackage`` project, your :term:`package` is named ``myproject``
which contains a ``views`` module, which in turn contains a :term:`view`
function ``my_view`` that returns an HTML body when the root URL is invoked:
- .. literalinclude:: MyProject/myproject/views.py
+ .. literalinclude:: myproject/myproject/views.py
:linenos:
:language: python
The following example functional test demonstrates invoking the above
:term:`view`:
- .. literalinclude:: MyProject/myproject/tests.py
+ .. literalinclude:: myproject/myproject/tests.py
:linenos:
:pyobject: FunctionalTests
:language: python
diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst
index 4e434c3c6..e0482d5a2 100644
--- a/docs/narr/upgrading.rst
+++ b/docs/narr/upgrading.rst
@@ -208,7 +208,7 @@ On Windows, you need to issue two commands:
.. code-block:: doscon
c:\> set PYTHONWARNINGS=default
- c:\> Scripts/pserve.exe development.ini
+ c:\> Scripts\pserve development.ini
At this point, it's ensured that deprecation warnings will be printed to the
console whenever a codepath is hit that generates one. You can then click
diff --git a/docs/narr/vhosting.rst b/docs/narr/vhosting.rst
index 0edf03353..e4cee9882 100644
--- a/docs/narr/vhosting.rst
+++ b/docs/narr/vhosting.rst
@@ -26,20 +26,20 @@ Hosting an Application Under a URL Prefix
``http://example.com/``).
If you use a "pure Python" environment, this functionality can be provided by
-Paste's `urlmap <http://pythonpaste.org/modules/urlmap.html>`_ "composite" WSGI
-application. Alternatively, you can use :term:`mod_wsgi` to serve your
+`rutter <http://rutter.readthedocs.io/en/latest/>`_, forming a "composite"
+WSGI application. Alternatively, you can use :term:`mod_wsgi` to serve your
application, which handles this virtual hosting translation for you "under the
hood".
-If you use the ``urlmap`` composite application "in front" of a :app:`Pyramid`
+If you use the ``rutter`` composite application "in front" of a :app:`Pyramid`
application or if you use :term:`mod_wsgi` to serve up a :app:`Pyramid`
application, nothing special needs to be done within the application for URLs
-to be generated that contain a prefix. :mod:`paste.urlmap` and :term:`mod_wsgi`
+to be generated that contain a prefix. Rutter and :term:`mod_wsgi`
manipulate the :term:`WSGI` environment in such a way that the ``PATH_INFO``
and ``SCRIPT_NAME`` variables are correct for some given prefix.
Here's an example of a PasteDeploy configuration snippet that includes a
-``urlmap`` composite.
+``rutter`` composite.
.. code-block:: ini
:linenos:
@@ -48,13 +48,13 @@ Here's an example of a PasteDeploy configuration snippet that includes a
use = egg:mypyramidapp
[composite:main]
- use = egg:Paste#urlmap
+ use = egg:rutter#urlmap
/pyramidapp = mypyramidapp
This "roots" the :app:`Pyramid` application at the prefix ``/pyramidapp`` and
serves up the composite as the "main" application in the file.
-.. note:: If you're using an Apache server to proxy to a Paste ``urlmap``
+.. note:: If you're using an Apache server to proxy to a ``urlmap``
composite, you may have to use the `ProxyPreserveHost
<http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxypreservehost>`_
directive to pass the original ``HTTP_HOST`` header along to the
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index 7cb8e0306..3b683ff79 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -252,7 +252,7 @@ Non-Predicate Arguments
def myview(request):
...
- Is similar to doing::
+ Is similar to decorating the view callable directly::
@view_config(...)
@decorator2
@@ -260,8 +260,10 @@ Non-Predicate Arguments
def myview(request):
...
- All view callables in the decorator chain must return a response object
- implementing :class:`pyramid.interfaces.IResponse` or raise an exception:
+ An important distinction is that each decorator will receive a response
+ object implementing :class:`pyramid.interfaces.IResponse` instead of the
+ raw value returned from the view callable. All decorators in the chain must
+ return a response object or raise an exception:
.. code-block:: python
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index ab139ea19..e8a07202e 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -52,7 +52,7 @@ of exceptions from within the body of a view callable.
Defining a View Callable as a Function
--------------------------------------
-One of the easiest way to define a view callable is to create a function that
+One of the easiest ways to define a view callable is to create a function that
accepts a single argument named ``request``, and which returns a
:term:`Response` object. For example, this is a "hello world" view callable
implemented as a function:
@@ -246,7 +246,7 @@ within view code, the result of the :term:`Not Found View` will be returned to
the user agent which performed the request.
If :exc:`~pyramid.httpexceptions.HTTPForbidden` is raised by Pyramid itself
-within view code, the result of the :term:`Forbidden View` will be returned to
+or within view code, the result of the :term:`Forbidden View` will be returned to
the user agent which performed the request.
.. index::
@@ -523,8 +523,7 @@ Alternate View Callable Argument/Calling Conventions
----------------------------------------------------
Usually view callables are defined to accept only a single argument:
-``request``. However, view callables may alternately be defined as classes,
-functions, or any callable that accept *two* positional arguments: a
+``request``. However, a view callable may alternately be defined as any class, function, or callable that accepts *two* positional arguments: a
:term:`context` resource as the first argument and a :term:`request` as the
second argument.
diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst
index ce1586834..578fdeb51 100644
--- a/docs/narr/webob.rst
+++ b/docs/narr/webob.rst
@@ -345,8 +345,8 @@ against your ``mypackage`` package during application initialization.
.. note::
This is only an example. In particular, it is not necessary to cause
- ``DBSession.remove`` to be called in an application generated from any
- :app:`Pyramid` scaffold, because these all use the ``pyramid_tm`` package.
+ ``DBSession.remove`` to be called in an application generated from a
+ :app:`Pyramid` cookiecutter, because these all use the ``pyramid_tm`` package.
The cleanup done by ``DBSession.remove`` is unnecessary when ``pyramid_tm``
:term:`middleware` is configured into the application.
diff --git a/docs/pscripts/pcreate.rst b/docs/pscripts/pcreate.rst
index b5ec3f4e2..c48a70acd 100644
--- a/docs/pscripts/pcreate.rst
+++ b/docs/pscripts/pcreate.rst
@@ -1,13 +1,9 @@
.. index::
- single: pcreate; --help
+ single: pcreate; --help
.. _pcreate_script:
-``pcreate``
------------
-
-.. program-output:: pcreate --help
- :prompt:
- :shell:
+.. autoprogram:: pyramid.scripts.pcreate:PCreateCommand.parser
+ :prog: pcreate
.. seealso:: :ref:`creating_a_project`
diff --git a/docs/pscripts/pdistreport.rst b/docs/pscripts/pdistreport.rst
index 1c53fb6e9..b3c9dffd8 100644
--- a/docs/pscripts/pdistreport.rst
+++ b/docs/pscripts/pdistreport.rst
@@ -1,13 +1,9 @@
.. index::
- single: pdistreport; --help
+ single: pdistreport; --help
.. _pdistreport_script:
-``pdistreport``
----------------
-
-.. program-output:: pdistreport --help
- :prompt:
- :shell:
+.. autoprogram:: pyramid.scripts.pdistreport:get_parser()
+ :prog: pdistreport
.. seealso:: :ref:`showing_distributions`
diff --git a/docs/pscripts/prequest.rst b/docs/pscripts/prequest.rst
index a15827767..64ed01739 100644
--- a/docs/pscripts/prequest.rst
+++ b/docs/pscripts/prequest.rst
@@ -1,13 +1,9 @@
.. index::
- single: prequest; --help
+ single: prequest; --help
.. _prequest_script:
-``prequest``
-------------
-
-.. program-output:: prequest --help
- :prompt:
- :shell:
+.. autoprogram:: pyramid.scripts.prequest:PRequestCommand.parser
+ :prog: prequest
.. seealso:: :ref:`invoking_a_request`
diff --git a/docs/pscripts/proutes.rst b/docs/pscripts/proutes.rst
index 09ed013e1..ee7c209e3 100644
--- a/docs/pscripts/proutes.rst
+++ b/docs/pscripts/proutes.rst
@@ -3,11 +3,7 @@
.. _proutes_script:
-``proutes``
------------
-
-.. program-output:: proutes --help
- :prompt:
- :shell:
+.. autoprogram:: pyramid.scripts.proutes:PRoutesCommand.parser
+ :prog: proutes
.. seealso:: :ref:`displaying_application_routes`
diff --git a/docs/pscripts/pserve.rst b/docs/pscripts/pserve.rst
index d33d4a484..991976aea 100644
--- a/docs/pscripts/pserve.rst
+++ b/docs/pscripts/pserve.rst
@@ -3,11 +3,7 @@
.. _pserve_script:
-``pserve``
-----------
-
-.. program-output:: pserve --help
- :prompt:
- :shell:
+.. autoprogram:: pyramid.scripts.pserve:PServeCommand.parser
+ :prog: pserve
.. seealso:: :ref:`running_the_project_application`
diff --git a/docs/pscripts/pshell.rst b/docs/pscripts/pshell.rst
index cfd84d4f8..4e2ce74cb 100644
--- a/docs/pscripts/pshell.rst
+++ b/docs/pscripts/pshell.rst
@@ -3,11 +3,7 @@
.. _pshell_script:
-``pshell``
-----------
-
-.. program-output:: pshell --help
- :prompt:
- :shell:
+.. autoprogram:: pyramid.scripts.pshell:PShellCommand.parser
+ :prog: pshell
.. seealso:: :ref:`interactive_shell`
diff --git a/docs/pscripts/ptweens.rst b/docs/pscripts/ptweens.rst
index 02e23e49a..f586e1467 100644
--- a/docs/pscripts/ptweens.rst
+++ b/docs/pscripts/ptweens.rst
@@ -3,11 +3,7 @@
.. _ptweens_script:
-``ptweens``
------------
-
-.. program-output:: ptweens --help
- :prompt:
- :shell:
+.. autoprogram:: pyramid.scripts.ptweens:PTweensCommand.parser
+ :prog: ptweens
.. seealso:: :ref:`displaying_tweens`
diff --git a/docs/pscripts/pviews.rst b/docs/pscripts/pviews.rst
index b4de5c054..da4660779 100644
--- a/docs/pscripts/pviews.rst
+++ b/docs/pscripts/pviews.rst
@@ -3,11 +3,7 @@
.. _pviews_script:
-``pviews``
-----------
-
-.. program-output:: pviews --help
- :prompt:
- :shell:
+.. autoprogram:: pyramid.scripts.pviews:PViewsCommand.parser
+ :prog: pviews
.. seealso:: :ref:`displaying_matching_views`
diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst
index 39b4cafb3..1265012ab 100644
--- a/docs/quick_tour.rst
+++ b/docs/quick_tour.rst
@@ -26,7 +26,7 @@ To save a little bit of typing and to be certain that we use the modules,
scripts, and packages installed in our virtual environment, we'll set an
environment variable, too.
-As an example, for Python 3.5+ on Linux:
+As an example, for Python 3.6+ on Linux:
.. parsed-literal::
@@ -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
@@ -116,7 +116,7 @@ part of a web application, web developers need a robust, mature set of software
for web requests.
Pyramid has always fit nicely into the existing world of Python web development
-(virtual environments, packaging, scaffolding, one of the first to embrace
+(virtual environments, packaging, cookiecutters, one of the first to embrace
Python 3, etc.). Pyramid turned to the well-regarded :term:`WebOb` Python
library for request and response handling. In our example above, Pyramid hands
``hello_world`` a ``request`` that is :ref:`based on WebOb <webob_chapter>`.
@@ -491,40 +491,55 @@ more to offer:
:ref:`class_as_view`.
-Quick project startup with scaffolds
-====================================
+Quick project startup with cookiecutters
+========================================
So far we have done all of our *Quick Tour* as a single Python file. No Python
packages, no structure. Most Pyramid projects, though, aren't developed this
way.
-To ease the process of getting started, Pyramid provides *scaffolds* that
-generate sample projects from templates in Pyramid and Pyramid add-ons.
-Pyramid's ``pcreate`` command can list the available scaffolds:
+To ease the process of getting started, the Pylons Project provides :term:`cookiecutter`\ s that generate sample Pyramid projects from project templates. These cookiecutters will install Pyramid and its dependencies as well.
+
+First you'll need to install cookiecutter.
.. code-block:: bash
- $ pcreate --list
- Available scaffolds:
- alchemy: Pyramid project using SQLAlchemy, SQLite, URL dispatch, and Jinja2
- pyramid_jinja2_starter: Pyramid Jinja2 starter project
- starter: Pyramid starter project using URL dispatch and Chameleon
- zodb: Pyramid project using ZODB, traversal, and Chameleon
+ $ $VENV/bin/pip install cookiecutter
-The ``pyramid_jinja2`` add-on gave us a scaffold that we can use. From the
-parent directory of where we want our Python package to be generated, let's use
-that scaffold to make our project:
+Let's use the cookiecutter ``pyramid-cookiecutter-starter`` to create a starter Pyramid project in the current directory, entering values at the prompts as shown below for the following command.
.. code-block:: bash
- $ pcreate --scaffold pyramid_jinja2_starter hello_world
+ $ $VENV/bin/cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter
-We next use the normal Python command to set up our package for development:
+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]: 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.
.. code-block:: bash
+ # Change directory into your newly created project.
$ cd hello_world
- $ $VENV/bin/pip install -e .
+ # Create a new virtual environment...
+ $ python3 -m venv env
+ # ...where we upgrade packaging tools...
+ $ env/bin/pip install --upgrade pip setuptools
+ # ...and into which we install our project and its testing requirements.
+ $ env/bin/pip install -e ".[testing]"
+ # Reset our environment variable for a new virtual environment.
+ $ export VENV=~/hello_world/env
We are moving in the direction of a full-featured Pyramid project, with a
proper setup for Python standards (packaging) and Pyramid configuration. This
@@ -537,14 +552,14 @@ includes a new way of running your application:
Let's look at ``pserve`` and configuration in more depth.
.. seealso:: See also:
- :ref:`Quick Tutorial Scaffolds <qtut_scaffolds>`,
+ :ref:`Quick Tutorial Cookiecutters <qtut_cookiecutters>`,
:ref:`project_narr`, and
- :doc:`../narr/scaffolding`
+ :doc:`../narr/cookiecutters`
Application running with ``pserve``
===================================
-Prior to scaffolds, our project mixed a number of operational details into our
+Prior to the cookiecutter, our project mixed a number of operational details into our
code. Why should my main code care which HTTP server I want and what port
number to run on?
@@ -574,7 +589,7 @@ Configuration with ``.ini`` files
Earlier in *Quick Tour* we first met Pyramid's configuration system. At that
point we did all configuration in Python code. For example, the port number
-chosen for our HTTP server was right there in Python code. Our scaffold has
+chosen for our HTTP server was right there in Python code. Our cookiecutter has
moved this decision and more into the ``development.ini`` file:
.. literalinclude:: quick_tour/package/development.ini
@@ -591,11 +606,6 @@ sections:
We have a few decisions made for us in this configuration:
-#. *Choice of web server:* ``use = egg:hello_world`` tells ``pserve`` to
- use the ``waitress`` server.
-
-#. *Port number:* ``port = 6543`` tells ``waitress`` to listen on port 6543.
-
#. *WSGI app:* What package has our WSGI application in it?
``use = egg:hello_world`` in the app section tells the configuration what
application to load.
@@ -605,7 +615,12 @@ We have a few decisions made for us in this configuration:
``pyramid.reload_templates = true`` sets this policy, which might be
different in production.
-Additionally the ``development.ini`` generated by this scaffold wired up
+#. *Choice of web server:* ``use = egg:waitress#main`` tells ``pserve`` to
+ use the ``waitress`` server.
+
+#. *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
every request that comes in, as well as traceback information.
@@ -626,38 +641,35 @@ and earlier we showed ``--reload`` for application reloading.
available in your browser. Adding it to your project illustrates several points
about configuration.
-The scaffold ``pyramid_jinja2_starter`` is already configured to include the
+The cookiecutter ``pyramid-cookiecutter-starter`` already configured our package to include the
add-on ``pyramid_debugtoolbar`` in its ``setup.py``:
.. literalinclude:: quick_tour/package/setup.py
:language: python
- :linenos:
- :lineno-start: 11
+ :lineno-match:
:lines: 11-16
+ :emphasize-lines: 4
It was installed when you previously ran:
.. code-block:: bash
- $ $VENV/bin/pip install -e .
+ $ $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 ``pyramid_jinja2``
-add-on already took care of this for us in its ``__init__.py``:
+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
- :linenos:
- :lineno-start: 16
- :lines: 19
+ :lineno-match:
+ :lines: 8
And it uses the ``pyramid.includes`` facility in our ``development.ini``:
.. literalinclude:: quick_tour/package/development.ini
:language: ini
- :linenos:
- :lineno-start: 15
- :lines: 15-16
+ :lineno-match:
+ :lines: 14-15
You'll now see a Pyramid logo on the right side of your browser window, which
when clicked opens a new window that provides introspective access to debugging
@@ -677,42 +689,23 @@ Yikes! We got this far and we haven't yet discussed tests. This is particularly
egregious, as Pyramid has had a deep commitment to full test coverage since
before its release.
-Our ``pyramid_jinja2_starter`` scaffold generated a ``tests.py`` module with
-one unit test in it. To run it, let's install the handy ``pytest`` test runner
-by editing ``setup.py``. While we're at it, we'll throw in the ``pytest-cov``
-tool which yells at us for code that isn't tested. Insert and edit the
-following lines as shown:
+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:
-.. code-block:: python
- :linenos:
- :lineno-start: 11
- :emphasize-lines: 8-12
-
- requires = [
- 'pyramid',
- 'pyramid_jinja2',
- 'pyramid_debugtoolbar',
- 'waitress',
- ]
-
- tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
- 'pytest-cov',
- ]
-
-.. code-block:: python
- :linenos:
- :lineno-start: 34
- :emphasize-lines: 2-4
+.. literalinclude:: quick_tour/package/setup.py
+ :language: python
+ :lineno-match:
+ :lines: 18-22
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
+.. literalinclude:: quick_tour/package/setup.py
+ :language: python
+ :lineno-match:
+ :lines: 42-44
-We changed ``setup.py`` which means we need to rerun ``$VENV/bin/pip install -e
-".[testing]"``. We can now run all our tests:
+We already installed the test requirements when we ran the command ``$VENV/bin/pip install -e ".[testing]"``. We can now run all our tests:
.. code-block:: bash
@@ -723,34 +716,33 @@ This yields the following output.
.. code-block:: text
=========================== test session starts ===========================
- platform darwin -- Python 3.5.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
- rootdir: /Users/stevepiercy/projects/hack-on-pyramid/hello_world, inifile:
- plugins: cov-2.2.1
- collected 1 items
-
- hello_world/tests.py .
- ------------- coverage: platform darwin, python 3.5.0-final-0 -------------
- Name Stmts Miss Cover Missing
- --------------------------------------------------------
- hello_world/__init__.py 11 8 27% 11-23
- hello_world/resources.py 5 1 80% 8
- hello_world/tests.py 14 0 100%
- hello_world/views.py 4 0 100%
- --------------------------------------------------------
- TOTAL 34 9 74%
-
- ========================= 1 passed in 0.22 seconds =========================
-
-Our unit test passed, although its coverage is incomplete. What did our test
-look like?
+ platform darwin -- Python 3.6.0, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
+ rootdir: /Users/stevepiercy/hello_world, inifile: pytest.ini
+ plugins: cov-2.4.0
+ collected 2 items
+
+ hello_world/tests.py ..
+
+ ------------- coverage: platform darwin, python 3.6.0-final-0 -------------
+ Name Stmts Miss Cover Missing
+ -----------------------------------------------------------------------
+ hello_world/__init__.py 8 0 100%
+ hello_world/views.py 3 0 100%
+ -----------------------------------------------------------------------
+ TOTAL 11 0 100%
+
+
+ ========================= 2 passed in 1.37 seconds =========================
+
+Our tests passed, and its coverage is complete. What did our test look like?
.. literalinclude:: quick_tour/package/hello_world/tests.py
:language: python
:linenos:
Pyramid supplies helpers for test writing, which we use in the test setup and
-teardown. Our one test imports the view, makes a dummy request, and sees if the
-view returns what we expected.
+teardown. Our first test imports the view, makes a dummy request, and sees if the
+view returns what we expected. Our second test verifies that the response body from a request to the web root contains what we expected.
.. seealso:: See also:
:ref:`Quick Tutorial Unit Testing <qtut_unit_testing>`, :ref:`Quick
@@ -764,38 +756,34 @@ It's important to know what is going on inside our web application. In
development we might need to collect some output. In production we might need
to detect situations when other people use the site. We need *logging*.
-Fortunately Pyramid uses the normal Python approach to logging. The scaffold
-generated in your ``development.ini`` has a number of lines that configure the
+Fortunately Pyramid uses the normal Python approach to logging. The ``development.ini`` file for your project has a number of lines that configure the
logging for you to some reasonable defaults. You then see messages sent by
Pyramid (for example, when a new request comes in).
Maybe you would like to log messages in your code? In your Python module,
-import and set up the logging:
+import and set up the logging in your ``views.py``:
-.. literalinclude:: quick_tour/package/hello_world/views.py
+.. literalinclude:: quick_tour/logging/hello_world/views.py
:language: python
- :linenos:
- :lineno-start: 3
+ :lineno-match:
:lines: 3-4
You can now, in your code, log messages:
-.. literalinclude:: quick_tour/package/hello_world/views.py
+.. literalinclude:: quick_tour/logging/hello_world/views.py
:language: python
- :linenos:
- :lineno-start: 9
- :lines: 9-10
+ :lineno-match:
+ :lines: 7-8
:emphasize-lines: 2
-This will log ``Some Message`` at a ``debug`` log level to the
+This will log ``Some Message`` at a ``DEBUG`` log level to the
application-configured logger in your ``development.ini``. What controls that?
These emphasized sections in the configuration file:
-.. literalinclude:: quick_tour/package/development.ini
+.. literalinclude:: quick_tour/logging/development.ini
:language: ini
- :linenos:
- :lineno-start: 36
- :lines: 36-52
+ :lineno-match:
+ :lines: 34-50
:emphasize-lines: 1-2,14-17
Our application, a package named ``hello_world``, is set up as a logger and
@@ -804,7 +792,7 @@ http://localhost:6543, your console will now show:
.. code-block:: text
- 2016-01-18 13:55:55,040 DEBUG [hello_world.views:10][waitress] Some Message
+ 2016-12-25 03:03:57,059 DEBUG [hello_world.views:8][waitress] Some Message
.. seealso:: See also:
:ref:`Quick Tutorial Logging <qtut_logging>` and :ref:`logging_chapter`.
@@ -822,11 +810,10 @@ your own custom sessioning engine. Let's take a look at the :doc:`built-in
sessioning support <../narr/sessions>`. In our ``__init__.py`` we first import
the kind of sessioning we want:
-.. literalinclude:: quick_tour/package/hello_world/__init__.py
+.. literalinclude:: quick_tour/sessions/hello_world/__init__.py
:language: python
- :linenos:
- :lineno-start: 2
- :lines: 2-3
+ :lineno-match:
+ :lines: 1-2
:emphasize-lines: 2
.. warning::
@@ -837,31 +824,28 @@ the kind of sessioning we want:
Now make a "factory" and pass it to the :term:`configurator`'s
``session_factory`` argument:
-.. literalinclude:: quick_tour/package/hello_world/__init__.py
+.. literalinclude:: quick_tour/sessions/hello_world/__init__.py
:language: python
- :linenos:
- :lineno-start: 13
- :lines: 13-17
- :emphasize-lines: 3-5
+ :lineno-match:
+ :lines: 10-13
+ :emphasize-lines: 2-3
Pyramid's :term:`request` object now has a ``session`` attribute that we can
use in our view code in ``views.py``:
-.. literalinclude:: quick_tour/package/hello_world/views.py
+.. literalinclude:: quick_tour/sessions/hello_world/views.py
:language: python
- :linenos:
- :lineno-start: 9
- :lines: 9-15
+ :lineno-match:
+ :lines: 7-
:emphasize-lines: 3-7
-We need to update our Jinja2 template to show counter increment in the session:
+We need to update our Jinja2 template ``templates/mytemplate.jinja2`` to show counter increment in the session:
-.. literalinclude:: quick_tour/package/hello_world/templates/mytemplate.jinja2
+.. literalinclude:: quick_tour/sessions/hello_world/templates/mytemplate.jinja2
:language: jinja
- :linenos:
- :lineno-start: 40
- :lines: 40-42
- :emphasize-lines: 3
+ :lineno-match:
+ :lines: 4-8
+ :emphasize-lines: 4
.. seealso:: See also:
:ref:`Quick Tutorial Sessions <qtut_sessions>`, :ref:`sessions_chapter`,
@@ -877,13 +861,36 @@ databases frequently mean an "ORM" (object-relational mapper.) In Python, ORM
usually leads to the mega-quality *SQLAlchemy*, a Python package that greatly
eases working with databases.
-Pyramid and SQLAlchemy are great friends. That friendship includes a scaffold!
+Pyramid and SQLAlchemy are great friends. That friendship includes a cookiecutter!
+
+.. code-block:: bash
+
+ $ cd ~
+ $ env/bin/cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy
+
+If prompted for the first item, accept the default ``yes`` by hitting return.
+
+.. 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.
.. code-block:: bash
- $ $VENV/bin/pcreate --scaffold alchemy sqla_demo
- $ cd sqla_demo
- $ $VENV/bin/pip install -e .
+ # Change directory into your newly created project.
+ $ cd sqla_demo
+ # Create a new virtual environment...
+ $ python3 -m venv env
+ # ...where we upgrade packaging tools...
+ $ env/bin/pip install --upgrade pip setuptools
+ # ...and into which we install our project and its testing requirements.
+ $ env/bin/pip install -e ".[testing]"
+ # Reset our environment variable for a new virtual environment.
+ $ export VENV=~/sqla_demo/env
We now have a working sample SQLAlchemy application with all dependencies
installed. The sample project provides a console script to initialize a SQLite
@@ -895,21 +902,21 @@ database with tables. Let's run it, then start the application:
$ $VENV/bin/pserve development.ini
The ORM eases the mapping of database structures into a programming language.
-SQLAlchemy uses "models" for this mapping. The scaffold generated a sample
+SQLAlchemy uses "models" for this mapping. The cookiecutter generated a sample
model:
.. literalinclude:: quick_tour/sqla_demo/sqla_demo/models/mymodel.py
:language: python
- :start-after: Start Sphinx Include
- :end-before: End Sphinx Include
+ :lineno-match:
+ :pyobject: MyModel
View code, which mediates the logic between web requests and the rest of the
system, can then easily get at the data thanks to SQLAlchemy:
.. literalinclude:: quick_tour/sqla_demo/sqla_demo/views/default.py
:language: python
- :start-after: Start Sphinx Include
- :end-before: End Sphinx Include
+ :lineno-match:
+ :lines: 13
.. seealso:: See also:
:ref:`Quick Tutorial Databases <qtut_databases>`, `SQLAlchemy
@@ -968,14 +975,10 @@ Deform and Colander provide a very flexible combination for forms, widgets,
schemas, and validation. Recent versions of Deform also include a :ref:`retail
mode <deform:retail>` for gaining Deform features on custom forms.
-Also the ``deform_bootstrap`` Pyramid add-on restyles the stock Deform widgets
-using attractive CSS from Twitter Bootstrap and more powerful widgets from
-Chosen.
+Deform uses attractive CSS from Twitter Bootstrap and more powerful select, checkbox, and date and time widgets.
.. seealso:: See also:
- :ref:`Quick Tutorial Forms <qtut_forms>`, :ref:`Deform <deform:overview>`,
- :ref:`Colander <colander:overview>`, and `deform_bootstrap
- <https://pypi.python.org/pypi/deform_bootstrap>`_.
+ :ref:`Quick Tutorial Forms <qtut_forms>`, :ref:`Deform <deform:overview>`, and :ref:`Colander <colander:overview>`.
Conclusion
==========
diff --git a/docs/quick_tour/logging/.coveragerc b/docs/quick_tour/logging/.coveragerc
new file mode 100644
index 000000000..128e26410
--- /dev/null
+++ b/docs/quick_tour/logging/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = hello_world
+omit = hello_world/test*
diff --git a/docs/quick_tour/logging/CHANGES.txt b/docs/quick_tour/logging/CHANGES.txt
new file mode 100644
index 000000000..14b902fd1
--- /dev/null
+++ b/docs/quick_tour/logging/CHANGES.txt
@@ -0,0 +1,4 @@
+0.0
+---
+
+- Initial version.
diff --git a/docs/quick_tour/logging/MANIFEST.in b/docs/quick_tour/logging/MANIFEST.in
new file mode 100644
index 000000000..a75da6dad
--- /dev/null
+++ b/docs/quick_tour/logging/MANIFEST.in
@@ -0,0 +1,2 @@
+include *.txt *.ini *.cfg *.rst
+recursive-include hello_world *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/quick_tour/logging/README.txt b/docs/quick_tour/logging/README.txt
new file mode 100644
index 000000000..ff70a1354
--- /dev/null
+++ b/docs/quick_tour/logging/README.txt
@@ -0,0 +1,29 @@
+hello_world
+===========
+
+Getting Started
+---------------
+
+- Change directory into your newly created project.
+
+ cd hello_world
+
+- Create a Python virtual environment.
+
+ python3 -m venv env
+
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/quick_tour/logging/development.ini b/docs/quick_tour/logging/development.ini
new file mode 100644
index 000000000..b0210cbad
--- /dev/null
+++ b/docs/quick_tour/logging/development.ini
@@ -0,0 +1,59 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:hello_world
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_debugtoolbar
+
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+listen = localhost:6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, hello_world
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_hello_world]
+level = DEBUG
+handlers =
+qualname = hello_world
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/__init__.py b/docs/quick_tour/logging/hello_world/__init__.py
index ad5ecbc6f..49dde36d4 100644
--- a/docs/quick_tutorial/scaffolds/scaffolds/__init__.py
+++ b/docs/quick_tour/logging/hello_world/__init__.py
@@ -5,7 +5,7 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(settings=settings)
- config.include('pyramid_chameleon')
+ config.include('pyramid_jinja2')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.scan()
diff --git a/docs/quick_tour/logging/hello_world/static/pyramid-16x16.png b/docs/quick_tour/logging/hello_world/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/quick_tour/logging/hello_world/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/quick_tour/logging/hello_world/static/pyramid.png b/docs/quick_tour/logging/hello_world/static/pyramid.png
new file mode 100644
index 000000000..4ab837be9
--- /dev/null
+++ b/docs/quick_tour/logging/hello_world/static/pyramid.png
Binary files differ
diff --git a/docs/quick_tour/logging/hello_world/static/theme.css b/docs/quick_tour/logging/hello_world/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/quick_tour/logging/hello_world/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt b/docs/quick_tour/logging/hello_world/templates/layout.jinja2
index f8cbe2e2c..916127267 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt
+++ b/docs/quick_tour/logging/hello_world/templates/layout.jinja2
@@ -1,20 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('hello_world:static/pyramid-16x16.png')}}">
- <title>ZODB Scaffold for The Pyramid Web Framework</title>
+ <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">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <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]>
@@ -29,22 +29,19 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('hello_world:static/pyramid.png') }}" alt="pyramid web framework">
</div>
<div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
- </div>
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
</div>
</div>
<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><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-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>
</ul>
</div>
diff --git a/docs/quick_tour/logging/hello_world/templates/mytemplate.jinja2 b/docs/quick_tour/logging/hello_world/templates/mytemplate.jinja2
new file mode 100644
index 000000000..cf2d7f996
--- /dev/null
+++ b/docs/quick_tour/logging/hello_world/templates/mytemplate.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% 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>
+</div>
+{% endblock content %}
diff --git a/docs/quick_tour/logging/hello_world/tests.py b/docs/quick_tour/logging/hello_world/tests.py
new file mode 100644
index 000000000..ee9745685
--- /dev/null
+++ b/docs/quick_tour/logging/hello_world/tests.py
@@ -0,0 +1,29 @@
+import unittest
+
+from pyramid import testing
+
+
+class ViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_my_view(self):
+ from .views import my_view
+ request = testing.DummyRequest()
+ info = my_view(request)
+ self.assertEqual(info['project'], 'hello_world')
+
+
+class FunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from hello_world import main
+ app = main({})
+ from webtest import TestApp
+ self.testapp = TestApp(app)
+
+ def test_root(self):
+ res = self.testapp.get('/', status=200)
+ self.assertTrue(b'Pyramid' in res.body)
diff --git a/docs/quick_tour/logging/hello_world/views.py b/docs/quick_tour/logging/hello_world/views.py
new file mode 100644
index 000000000..a648d6ba3
--- /dev/null
+++ b/docs/quick_tour/logging/hello_world/views.py
@@ -0,0 +1,9 @@
+from pyramid.view import view_config
+
+import logging
+log = logging.getLogger(__name__)
+
+@view_config(route_name='home', renderer='templates/mytemplate.jinja2')
+def my_view(request):
+ log.debug('Some Message')
+ return {'project': 'hello_world'}
diff --git a/docs/narr/MyProject/production.ini b/docs/quick_tour/logging/production.ini
index 1174b1cc7..9c12bc4ec 100644
--- a/docs/narr/MyProject/production.ini
+++ b/docs/quick_tour/logging/production.ini
@@ -1,10 +1,10 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
-use = egg:MyProject
+use = egg:hello_world
pyramid.reload_templates = false
pyramid.debug_authorization = false
@@ -18,16 +18,15 @@ pyramid.default_locale_name = en
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
-keys = root, myproject
+keys = root, hello_world
[handlers]
keys = console
@@ -39,10 +38,10 @@ keys = generic
level = WARN
handlers = console
-[logger_myproject]
+[logger_hello_world]
level = WARN
handlers =
-qualname = myproject
+qualname = hello_world
[handler_console]
class = StreamHandler
diff --git a/docs/quick_tour/logging/pytest.ini b/docs/quick_tour/logging/pytest.ini
new file mode 100644
index 000000000..f707d54e4
--- /dev/null
+++ b/docs/quick_tour/logging/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = hello_world
+python_files = *.py
diff --git a/docs/quick_tour/logging/setup.py b/docs/quick_tour/logging/setup.py
new file mode 100644
index 000000000..e32aecacd
--- /dev/null
+++ b/docs/quick_tour/logging/setup.py
@@ -0,0 +1,51 @@
+import os
+
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
+
+requires = [
+ 'pyramid',
+ 'pyramid_jinja2',
+ 'pyramid_debugtoolbar',
+ 'waitress',
+]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest',
+ 'pytest-cov',
+]
+
+setup(
+ name='hello_world',
+ version='0.0',
+ description='hello_world',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = hello_world:main',
+ ],
+ },
+)
diff --git a/docs/quick_tour/package/.coveragerc b/docs/quick_tour/package/.coveragerc
new file mode 100644
index 000000000..128e26410
--- /dev/null
+++ b/docs/quick_tour/package/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = hello_world
+omit = hello_world/test*
diff --git a/docs/quick_tour/package/CHANGES.txt b/docs/quick_tour/package/CHANGES.txt
index ffa255da8..14b902fd1 100644
--- a/docs/quick_tour/package/CHANGES.txt
+++ b/docs/quick_tour/package/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/quick_tour/package/MANIFEST.in b/docs/quick_tour/package/MANIFEST.in
index 1d0352f7d..a75da6dad 100644
--- a/docs/quick_tour/package/MANIFEST.in
+++ b/docs/quick_tour/package/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include hello_world *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.jinja2 *.js *.html *.xml
+recursive-include hello_world *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/quick_tour/package/README.txt b/docs/quick_tour/package/README.txt
index 63aaf6fbd..ff70a1354 100644
--- a/docs/quick_tour/package/README.txt
+++ b/docs/quick_tour/package/README.txt
@@ -1,4 +1,29 @@
-hello_world README
+hello_world
+===========
+Getting Started
+---------------
+- Change directory into your newly created project.
+ cd hello_world
+
+- Create a Python virtual environment.
+
+ python3 -m venv env
+
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/quick_tour/package/development.ini b/docs/quick_tour/package/development.ini
index 20f9817a9..b0210cbad 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/1.6-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -10,7 +10,6 @@ pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
-pyramid.debug_templates = true
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
@@ -25,12 +24,11 @@ pyramid.includes =
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.6-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/quick_tour/package/hello_world/__init__.py b/docs/quick_tour/package/hello_world/__init__.py
index 97f93d5a8..49dde36d4 100644
--- a/docs/quick_tour/package/hello_world/__init__.py
+++ b/docs/quick_tour/package/hello_world/__init__.py
@@ -1,26 +1,12 @@
from pyramid.config import Configurator
-from hello_world.resources import get_root
-from pyramid.session import SignedCookieSessionFactory
def main(global_config, **settings):
- """ This function returns a WSGI application.
-
- It is usually called by the PasteDeploy framework during
- ``paster serve``.
+ """ This function returns a Pyramid WSGI application.
"""
- settings = dict(settings)
- settings.setdefault('jinja2.i18n.domain', 'hello_world')
-
- my_session_factory = SignedCookieSessionFactory('itsaseekreet')
- config = Configurator(root_factory=get_root, settings=settings,
- session_factory=my_session_factory)
- config.add_translation_dirs('locale/')
+ config = Configurator(settings=settings)
config.include('pyramid_jinja2')
-
- config.add_static_view('static', 'static')
- config.add_view('hello_world.views.my_view',
- context='hello_world.resources.MyResource',
- renderer="templates/mytemplate.jinja2")
-
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.add_route('home', '/')
+ config.scan()
return config.make_wsgi_app()
diff --git a/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.mo b/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.mo
deleted file mode 100644
index 40bf0c271..000000000
--- a/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.mo
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.po b/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.po
deleted file mode 100644
index 0df243dba..000000000
--- a/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.po
+++ /dev/null
@@ -1,21 +0,0 @@
-# Translations template for PROJECT.
-# Copyright (C) 2011 ORGANIZATION
-# This file is distributed under the same license as the PROJECT project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PROJECT VERSION\n"
-"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2011-05-12 09:14-0330\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-
-msgid "Hello!"
-msgstr "Hallo!"
diff --git a/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.mo b/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.mo
deleted file mode 100644
index 4fc438bfe..000000000
--- a/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.mo
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.po b/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.po
deleted file mode 100644
index dc0aae5d7..000000000
--- a/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.po
+++ /dev/null
@@ -1,21 +0,0 @@
-# Translations template for PROJECT.
-# Copyright (C) 2011 ORGANIZATION
-# This file is distributed under the same license as the PROJECT project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PROJECT VERSION\n"
-"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2011-05-12 09:14-0330\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-
-msgid "Hello!"
-msgstr "Bonjour!"
diff --git a/docs/quick_tour/package/hello_world/locale/hello_world.pot b/docs/quick_tour/package/hello_world/locale/hello_world.pot
deleted file mode 100644
index 9c9460cb2..000000000
--- a/docs/quick_tour/package/hello_world/locale/hello_world.pot
+++ /dev/null
@@ -1,21 +0,0 @@
-# Translations template for PROJECT.
-# Copyright (C) 2011 ORGANIZATION
-# This file is distributed under the same license as the PROJECT project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PROJECT VERSION\n"
-"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2011-05-12 09:14-0330\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-
-msgid "Hello!"
-msgstr ""
diff --git a/docs/quick_tour/package/hello_world/resources.py b/docs/quick_tour/package/hello_world/resources.py
deleted file mode 100644
index e89c2f363..000000000
--- a/docs/quick_tour/package/hello_world/resources.py
+++ /dev/null
@@ -1,8 +0,0 @@
-class MyResource(object):
- pass
-
-root = MyResource()
-
-
-def get_root(request):
- return root
diff --git a/docs/quick_tour/package/hello_world/static/favicon.ico b/docs/quick_tour/package/hello_world/static/favicon.ico
deleted file mode 100644
index 71f837c9e..000000000
--- a/docs/quick_tour/package/hello_world/static/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/package/hello_world/static/theme.css b/docs/quick_tour/package/hello_world/static/theme.css
index e3cf3f290..0f4b1a4d4 100644
--- a/docs/quick_tour/package/hello_world/static/theme.css
+++ b/docs/quick_tour/package/hello_world/static/theme.css
@@ -72,10 +72,12 @@ p {
color: #f2b7bd;
font-weight: 400;
}
-.starter-template .links ul li a {
- color: #ffffff;
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
}
-.starter-template .links ul li a:hover {
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
text-decoration: underline;
}
.starter-template .links ul li .icon-muted {
@@ -150,4 +152,3 @@ p {
margin-top: 20px;
}
}
-
diff --git a/docs/narr/MyProject/myproject/templates/mytemplate.pt b/docs/quick_tour/package/hello_world/templates/layout.jinja2
index 543663fe8..916127267 100644
--- a/docs/narr/MyProject/myproject/templates/mytemplate.pt
+++ b/docs/quick_tour/package/hello_world/templates/layout.jinja2
@@ -1,20 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('myproject:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('hello_world:static/pyramid-16x16.png')}}">
- <title>Starter Scaffold for The Pyramid Web Framework</title>
+ <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">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('myproject:static/theme.css')}" rel="stylesheet">
+ <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]>
@@ -29,22 +29,19 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('myproject:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('hello_world:static/pyramid.png') }}" alt="pyramid web framework">
</div>
<div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
- </div>
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
</div>
</div>
<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><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-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>
</ul>
</div>
diff --git a/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2 b/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2
index a6089aebc..cf2d7f996 100644
--- a/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2
+++ b/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2
@@ -1,72 +1,8 @@
-<!DOCTYPE html>
-<html lang="{{request.locale_name}}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="{{request.static_url('hello_world:static/pyramid-16x16.png')}}">
-
- <title>Starter Scaffold for Pyramid Jinja2</title>
-
- <!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
-
- <!-- 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>
- <![endif]-->
- </head>
-
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="{{request.static_url('hello_world:static/pyramid.png')}}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <h1>
- <span class="font-semi-bold">Pyramid</span>
- <span class="smaller">Jinja2 scaffold</span>
- </h1>
- <p class="lead">
- {% trans %}Hello{% endtrans %} to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.6</span>.</p>
- <p>Counter: {{ request.session.counter }}</p>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="links">
- <ul>
- <li class="current-version">Generated by v1.6</li>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.6-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>
- </ul>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- 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>
- </body>
-</html>
+{% extends "layout.jinja2" %}
+
+{% 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>
+</div>
+{% endblock content %}
diff --git a/docs/quick_tour/package/hello_world/tests.py b/docs/quick_tour/package/hello_world/tests.py
index ccec14f70..ee9745685 100644
--- a/docs/quick_tour/package/hello_world/tests.py
+++ b/docs/quick_tour/package/hello_world/tests.py
@@ -1,20 +1,29 @@
import unittest
-from pyramid import testing
-from pyramid.i18n import TranslationStringFactory
-_ = TranslationStringFactory('hello_world')
+from pyramid import testing
class ViewTests(unittest.TestCase):
-
def setUp(self):
- testing.setUp()
-
+ self.config = testing.setUp()
+
def tearDown(self):
testing.tearDown()
def test_my_view(self):
- from hello_world.views import my_view
+ from .views import my_view
request = testing.DummyRequest()
- response = my_view(request)
- self.assertEqual(response['project'], 'hello_world')
+ info = my_view(request)
+ self.assertEqual(info['project'], 'hello_world')
+
+
+class FunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from hello_world import main
+ app = main({})
+ from webtest import TestApp
+ self.testapp = TestApp(app)
+
+ def test_root(self):
+ res = self.testapp.get('/', status=200)
+ self.assertTrue(b'Pyramid' in res.body)
diff --git a/docs/quick_tour/package/hello_world/views.py b/docs/quick_tour/package/hello_world/views.py
index 9f7953c8e..67f78dad7 100644
--- a/docs/quick_tour/package/hello_world/views.py
+++ b/docs/quick_tour/package/hello_world/views.py
@@ -1,16 +1,6 @@
-from pyramid.i18n import TranslationStringFactory
-
-import logging
-log = logging.getLogger(__name__)
-
-_ = TranslationStringFactory('hello_world')
+from pyramid.view import view_config
+@view_config(route_name='home', renderer='templates/mytemplate.jinja2')
def my_view(request):
- log.debug('Some Message')
- session = request.session
- if 'counter' in session:
- session['counter'] += 1
- else:
- session['counter'] = 0
return {'project': 'hello_world'}
diff --git a/docs/quick_tour/package/message-extraction.ini b/docs/quick_tour/package/message-extraction.ini
deleted file mode 100644
index 0c3d54bc1..000000000
--- a/docs/quick_tour/package/message-extraction.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[python: **.py]
-[jinja2: **.jinja2]
-encoding = utf-8
diff --git a/docs/quick_tour/package/production.ini b/docs/quick_tour/package/production.ini
new file mode 100644
index 000000000..9c12bc4ec
--- /dev/null
+++ b/docs/quick_tour/package/production.ini
@@ -0,0 +1,53 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:hello_world
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+listen = *:6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, hello_world
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+
+[logger_hello_world]
+level = WARN
+handlers =
+qualname = hello_world
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/quick_tour/package/pytest.ini b/docs/quick_tour/package/pytest.ini
new file mode 100644
index 000000000..f707d54e4
--- /dev/null
+++ b/docs/quick_tour/package/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = hello_world
+python_files = *.py
diff --git a/docs/quick_tour/package/setup.cfg b/docs/quick_tour/package/setup.cfg
deleted file mode 100644
index 186e796fc..000000000
--- a/docs/quick_tour/package/setup.cfg
+++ /dev/null
@@ -1,28 +0,0 @@
-[nosetests]
-match = ^test
-nocapture = 1
-cover-package = hello_world
-with-coverage = 1
-cover-erase = 1
-
-[compile_catalog]
-directory = hello_world/locale
-domain = hello_world
-statistics = true
-
-[extract_messages]
-add_comments = TRANSLATORS:
-output_file = hello_world/locale/hello_world.pot
-width = 80
-mapping_file = message-extraction.ini
-
-[init_catalog]
-domain = hello_world
-input_file = hello_world/locale/hello_world.pot
-output_dir = hello_world/locale
-
-[update_catalog]
-domain = hello_world
-input_file = hello_world/locale/hello_world.pot
-output_dir = hello_world/locale
-previous = true
diff --git a/docs/quick_tour/package/setup.py b/docs/quick_tour/package/setup.py
index 61ed3c406..e32aecacd 100644
--- a/docs/quick_tour/package/setup.py
+++ b/docs/quick_tour/package/setup.py
@@ -15,30 +15,37 @@ requires = [
'waitress',
]
-setup(name='hello_world',
- version='0.0',
- description='hello_world',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web pyramid pylons',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- install_requires=requires,
- tests_require={
- 'testing': ['nose', 'coverage'],
- },
- test_suite="hello_world",
- entry_points="""\
- [paste.app_factory]
- main = hello_world:main
- """,
- )
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest',
+ 'pytest-cov',
+]
+
+setup(
+ name='hello_world',
+ version='0.0',
+ description='hello_world',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = hello_world:main',
+ ],
+ },
+)
diff --git a/docs/quick_tour/sessions/.coveragerc b/docs/quick_tour/sessions/.coveragerc
new file mode 100644
index 000000000..128e26410
--- /dev/null
+++ b/docs/quick_tour/sessions/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = hello_world
+omit = hello_world/test*
diff --git a/docs/quick_tour/sessions/CHANGES.txt b/docs/quick_tour/sessions/CHANGES.txt
new file mode 100644
index 000000000..14b902fd1
--- /dev/null
+++ b/docs/quick_tour/sessions/CHANGES.txt
@@ -0,0 +1,4 @@
+0.0
+---
+
+- Initial version.
diff --git a/docs/quick_tour/sessions/MANIFEST.in b/docs/quick_tour/sessions/MANIFEST.in
new file mode 100644
index 000000000..a75da6dad
--- /dev/null
+++ b/docs/quick_tour/sessions/MANIFEST.in
@@ -0,0 +1,2 @@
+include *.txt *.ini *.cfg *.rst
+recursive-include hello_world *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/quick_tour/sessions/README.txt b/docs/quick_tour/sessions/README.txt
new file mode 100644
index 000000000..ff70a1354
--- /dev/null
+++ b/docs/quick_tour/sessions/README.txt
@@ -0,0 +1,29 @@
+hello_world
+===========
+
+Getting Started
+---------------
+
+- Change directory into your newly created project.
+
+ cd hello_world
+
+- Create a Python virtual environment.
+
+ python3 -m venv env
+
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/quick_tour/sessions/development.ini b/docs/quick_tour/sessions/development.ini
new file mode 100644
index 000000000..b0210cbad
--- /dev/null
+++ b/docs/quick_tour/sessions/development.ini
@@ -0,0 +1,59 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:hello_world
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_debugtoolbar
+
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+listen = localhost:6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, hello_world
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_hello_world]
+level = DEBUG
+handlers =
+qualname = hello_world
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/quick_tour/sessions/hello_world/__init__.py b/docs/quick_tour/sessions/hello_world/__init__.py
new file mode 100644
index 000000000..7cdc55ebe
--- /dev/null
+++ b/docs/quick_tour/sessions/hello_world/__init__.py
@@ -0,0 +1,14 @@
+from pyramid.config import Configurator
+from pyramid.session import SignedCookieSessionFactory
+
+def main(global_config, **settings):
+ """ This function returns a Pyramid WSGI application.
+ """
+ config = Configurator(settings=settings)
+ config.include('pyramid_jinja2')
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.add_route('home', '/')
+ my_session_factory = SignedCookieSessionFactory('itsaseekreet')
+ config.set_session_factory(my_session_factory)
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/quick_tour/sessions/hello_world/static/pyramid-16x16.png b/docs/quick_tour/sessions/hello_world/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/quick_tour/sessions/hello_world/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/quick_tour/sessions/hello_world/static/pyramid.png b/docs/quick_tour/sessions/hello_world/static/pyramid.png
new file mode 100644
index 000000000..4ab837be9
--- /dev/null
+++ b/docs/quick_tour/sessions/hello_world/static/pyramid.png
Binary files differ
diff --git a/docs/quick_tour/sessions/hello_world/static/theme.css b/docs/quick_tour/sessions/hello_world/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/quick_tour/sessions/hello_world/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 b/docs/quick_tour/sessions/hello_world/templates/layout.jinja2
new file mode 100644
index 000000000..916127267
--- /dev/null
+++ b/docs/quick_tour/sessions/hello_world/templates/layout.jinja2
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="{{request.static_url('hello_world:static/pyramid-16x16.png')}}">
+
+ <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">
+
+ <!-- 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>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="{{request.static_url('hello_world:static/pyramid.png') }}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <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>
+ </ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- 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>
+ </body>
+</html>
diff --git a/docs/quick_tour/sessions/hello_world/templates/mytemplate.jinja2 b/docs/quick_tour/sessions/hello_world/templates/mytemplate.jinja2
new file mode 100644
index 000000000..c7776144c
--- /dev/null
+++ b/docs/quick_tour/sessions/hello_world/templates/mytemplate.jinja2
@@ -0,0 +1,9 @@
+{% extends "layout.jinja2" %}
+
+{% 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>Counter: {{ request.session.counter }}</p>
+</div>
+{% endblock content %}
diff --git a/docs/quick_tour/sessions/hello_world/tests.py b/docs/quick_tour/sessions/hello_world/tests.py
new file mode 100644
index 000000000..ee9745685
--- /dev/null
+++ b/docs/quick_tour/sessions/hello_world/tests.py
@@ -0,0 +1,29 @@
+import unittest
+
+from pyramid import testing
+
+
+class ViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_my_view(self):
+ from .views import my_view
+ request = testing.DummyRequest()
+ info = my_view(request)
+ self.assertEqual(info['project'], 'hello_world')
+
+
+class FunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from hello_world import main
+ app = main({})
+ from webtest import TestApp
+ self.testapp = TestApp(app)
+
+ def test_root(self):
+ res = self.testapp.get('/', status=200)
+ self.assertTrue(b'Pyramid' in res.body)
diff --git a/docs/quick_tour/sessions/hello_world/views.py b/docs/quick_tour/sessions/hello_world/views.py
new file mode 100644
index 000000000..9716f854e
--- /dev/null
+++ b/docs/quick_tour/sessions/hello_world/views.py
@@ -0,0 +1,14 @@
+from pyramid.view import view_config
+
+import logging
+log = logging.getLogger(__name__)
+
+@view_config(route_name='home', renderer='templates/mytemplate.jinja2')
+def my_view(request):
+ log.debug('Some Message')
+ session = request.session
+ if 'counter' in session:
+ session['counter'] += 1
+ else:
+ session['counter'] = 0
+ return {'project': 'hello_world'}
diff --git a/docs/quick_tour/sessions/production.ini b/docs/quick_tour/sessions/production.ini
new file mode 100644
index 000000000..9c12bc4ec
--- /dev/null
+++ b/docs/quick_tour/sessions/production.ini
@@ -0,0 +1,53 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:hello_world
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+listen = *:6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, hello_world
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+
+[logger_hello_world]
+level = WARN
+handlers =
+qualname = hello_world
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/quick_tour/sessions/pytest.ini b/docs/quick_tour/sessions/pytest.ini
new file mode 100644
index 000000000..f707d54e4
--- /dev/null
+++ b/docs/quick_tour/sessions/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = hello_world
+python_files = *.py
diff --git a/docs/quick_tour/sessions/setup.py b/docs/quick_tour/sessions/setup.py
new file mode 100644
index 000000000..e32aecacd
--- /dev/null
+++ b/docs/quick_tour/sessions/setup.py
@@ -0,0 +1,51 @@
+import os
+
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
+
+requires = [
+ 'pyramid',
+ 'pyramid_jinja2',
+ 'pyramid_debugtoolbar',
+ 'waitress',
+]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest',
+ 'pytest-cov',
+]
+
+setup(
+ name='hello_world',
+ version='0.0',
+ description='hello_world',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = hello_world:main',
+ ],
+ },
+)
diff --git a/docs/quick_tour/sqla_demo/.coveragerc b/docs/quick_tour/sqla_demo/.coveragerc
new file mode 100644
index 000000000..918f8dea0
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = sqla_demo
+omit = sqla_demo/test*
diff --git a/docs/quick_tour/sqla_demo/CHANGES.txt b/docs/quick_tour/sqla_demo/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/quick_tour/sqla_demo/CHANGES.txt
+++ b/docs/quick_tour/sqla_demo/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/quick_tour/sqla_demo/MANIFEST.in b/docs/quick_tour/sqla_demo/MANIFEST.in
index a432577e9..e079655f9 100644
--- a/docs/quick_tour/sqla_demo/MANIFEST.in
+++ b/docs/quick_tour/sqla_demo/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include sqla_demo *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include sqla_demo *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/quick_tour/sqla_demo/README.txt b/docs/quick_tour/sqla_demo/README.txt
index b6d4c7798..27bbff5a7 100644
--- a/docs/quick_tour/sqla_demo/README.txt
+++ b/docs/quick_tour/sqla_demo/README.txt
@@ -1,14 +1,33 @@
-sqla_demo README
-==================
+sqla_demo
+=========
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd sqla_demo
-- $VENV/bin/initialize_sqla_demo_db development.ini
+- Create a Python virtual environment.
-- $VENV/bin/pserve development.ini
+ python3 -m venv env
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Configure the database.
+
+ env/bin/initialize_sqla_demo_db development.ini
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/quick_tour/sqla_demo/development.ini b/docs/quick_tour/sqla_demo/development.ini
index 0db0950a0..8d45a0975 100644
--- a/docs/quick_tour/sqla_demo/development.ini
+++ b/docs/quick_tour/sqla_demo/development.ini
@@ -13,7 +13,6 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite
@@ -27,8 +26,7 @@ sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
diff --git a/docs/quick_tour/sqla_demo/production.ini b/docs/quick_tour/sqla_demo/production.ini
index 38f3b6318..a85c354d3 100644
--- a/docs/quick_tour/sqla_demo/production.ini
+++ b/docs/quick_tour/sqla_demo/production.ini
@@ -11,15 +11,16 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
diff --git a/docs/quick_tour/sqla_demo/pytest.ini b/docs/quick_tour/sqla_demo/pytest.ini
new file mode 100644
index 000000000..2d7535841
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = sqla_demo
+python_files = *.py
diff --git a/docs/quick_tour/sqla_demo/setup.cfg b/docs/quick_tour/sqla_demo/setup.cfg
deleted file mode 100644
index 9f91cd122..000000000
--- a/docs/quick_tour/sqla_demo/setup.cfg
+++ /dev/null
@@ -1,27 +0,0 @@
-[nosetests]
-match=^test
-nocapture=1
-cover-package=sqla_demo
-with-coverage=1
-cover-erase=1
-
-[compile_catalog]
-directory = sqla_demo/locale
-domain = sqla_demo
-statistics = true
-
-[extract_messages]
-add_comments = TRANSLATORS:
-output_file = sqla_demo/locale/sqla_demo.pot
-width = 80
-
-[init_catalog]
-domain = sqla_demo
-input_file = sqla_demo/locale/sqla_demo.pot
-output_dir = sqla_demo/locale
-
-[update_catalog]
-domain = sqla_demo
-input_file = sqla_demo/locale/sqla_demo.pot
-output_dir = sqla_demo/locale
-previous = true
diff --git a/docs/quick_tour/sqla_demo/setup.py b/docs/quick_tour/sqla_demo/setup.py
index 312a97c06..75c1403fb 100644
--- a/docs/quick_tour/sqla_demo/setup.py
+++ b/docs/quick_tour/sqla_demo/setup.py
@@ -17,31 +17,42 @@ requires = [
'transaction',
'zope.sqlalchemy',
'waitress',
- ]
+]
-setup(name='sqla_demo',
- version='0.0',
- description='sqla_demo',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest',
+ 'pytest-cov',
+]
+
+setup(
+ name='sqla_demo',
+ version='0.0',
+ description='sqla_demo',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = sqla_demo:main',
+ ],
+ 'console_scripts': [
+ 'initialize_sqla_demo_db = sqla_demo.scripts.initializedb:main',
],
- author='',
- author_email='',
- url='',
- keywords='web wsgi bfg pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- test_suite='sqla_demo',
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = sqla_demo:main
- [console_scripts]
- initialize_sqla_demo_db = sqla_demo.scripts.initializedb:main
- """,
- )
+ },
+)
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/__init__.py b/docs/quick_tour/sqla_demo/sqla_demo/__init__.py
index 7994bbfa8..4dab44823 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/__init__.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/__init__.py
@@ -6,8 +6,7 @@ def main(global_config, **settings):
"""
config = Configurator(settings=settings)
config.include('pyramid_jinja2')
- config.include('.models.meta')
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.add_route('home', '/')
+ config.include('.models')
+ config.include('.routes')
config.scan()
return config.make_wsgi_app()
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 76e0fd26b..339326758 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py
@@ -1,7 +1,73 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import configure_mappers
-# import all models classes here for sqlalchemy mappers
-# to pick up
-from .mymodel import MyModel # noqa
+import zope.sqlalchemy
-# run configure mappers to ensure we avoid any race conditions
+# import or define all models here to ensure they are attached to the
+# Base.metadata prior to any initialization routines
+from .mymodel import MyModel # flake8: noqa
+
+# run configure_mappers after defining all of the models to ensure
+# all relationships can be setup
configure_mappers()
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_session_factory(engine):
+ factory = sessionmaker()
+ factory.configure(bind=engine)
+ return factory
+
+
+def get_tm_session(session_factory, transaction_manager):
+ """
+ Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.
+
+ This function will hook the session to the transaction manager which
+ will take care of committing any changes.
+
+ - When using pyramid_tm it will automatically be committed or aborted
+ depending on whether an exception is raised.
+
+ - When using scripts you should wrap the session in a manager yourself.
+ For example::
+
+ import transaction
+
+ engine = get_engine(settings)
+ session_factory = get_session_factory(engine)
+ with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ """
+ dbsession = session_factory()
+ zope.sqlalchemy.register(
+ dbsession, transaction_manager=transaction_manager)
+ return dbsession
+
+
+def includeme(config):
+ """
+ Initialize the model for a Pyramid app.
+
+ Activate this setup using ``config.include('sqla_demo.models')``.
+
+ """
+ settings = config.get_settings()
+
+ # use pyramid_tm to hook the transaction lifecycle to the request
+ config.include('pyramid_tm')
+
+ session_factory = get_session_factory(get_engine(settings))
+ config.registry['dbsession_factory'] = session_factory
+
+ # make request.dbsession available for use in Pyramid
+ config.add_request_method(
+ # r.tm is the transaction manager used by pyramid_tm
+ lambda r: get_tm_session(session_factory, r.tm),
+ 'dbsession',
+ reify=True
+ )
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 03c50ae93..0682247b5 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py
@@ -1,8 +1,5 @@
-from sqlalchemy import engine_from_config
from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import sessionmaker
from sqlalchemy.schema import MetaData
-import zope.sqlalchemy
# Recommended naming convention used by Alembic, as various different database
# providers will autogenerate vastly different names making migrations more
@@ -17,33 +14,3 @@ NAMING_CONVENTION = {
metadata = MetaData(naming_convention=NAMING_CONVENTION)
Base = declarative_base(metadata=metadata)
-
-
-def includeme(config):
- settings = config.get_settings()
- dbmaker = get_dbmaker(get_engine(settings))
-
- config.add_request_method(
- lambda r: get_session(r.tm, dbmaker),
- 'dbsession',
- reify=True
- )
-
- config.include('pyramid_tm')
-
-
-def get_session(transaction_manager, dbmaker):
- dbsession = dbmaker()
- zope.sqlalchemy.register(dbsession,
- transaction_manager=transaction_manager)
- return dbsession
-
-
-def get_engine(settings, prefix='sqlalchemy.'):
- return engine_from_config(settings, prefix)
-
-
-def get_dbmaker(engine):
- dbmaker = sessionmaker()
- dbmaker.configure(bind=engine)
- return dbmaker
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py b/docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py
index eb645bfe6..d65a01a42 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py
@@ -1,4 +1,3 @@
-from .meta import Base
from sqlalchemy import (
Column,
Index,
@@ -6,14 +5,14 @@ from sqlalchemy import (
Text,
)
+from .meta import Base
+
-# Start Sphinx Include
class MyModel(Base):
__tablename__ = 'models'
id = Column(Integer, primary_key=True)
name = Column(Text)
value = Column(Integer)
- # End Sphinx Include
Index('my_index', MyModel.name, unique=True, mysql_length=255)
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/routes.py b/docs/quick_tour/sqla_demo/sqla_demo/routes.py
new file mode 100644
index 000000000..25504ad4d
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/sqla_demo/routes.py
@@ -0,0 +1,3 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.add_route('home', '/')
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py b/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py
index f0d09729e..7307ecc5c 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py
@@ -9,13 +9,13 @@ from pyramid.paster import (
from pyramid.scripts.common import parse_vars
-from ..models.meta import (
- Base,
- get_session,
+from ..models.meta import Base
+from ..models import (
get_engine,
- get_dbmaker,
+ get_session_factory,
+ get_tm_session,
)
-from ..models.mymodel import MyModel
+from ..models import MyModel
def usage(argv):
@@ -34,12 +34,12 @@ def main(argv=sys.argv):
settings = get_appsettings(config_uri, options=options)
engine = get_engine(settings)
- dbmaker = get_dbmaker(engine)
-
- dbsession = get_session(transaction.manager, dbmaker)
-
Base.metadata.create_all(engine)
+ session_factory = get_session_factory(engine)
+
with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
model = MyModel(name='one', value=1)
dbsession.add(model)
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/templates/404.jinja2 b/docs/quick_tour/sqla_demo/sqla_demo/templates/404.jinja2
new file mode 100644
index 000000000..1917f83c7
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/404.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+</div>
+{% endblock content %}
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 76a098122..4607eb11f 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2
+++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2
@@ -8,7 +8,7 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="{{request.static_url('sqla_demo:static/pyramid-16x16.png')}}">
- <title>Alchemy Scaffold for The Pyramid Web Framework</title>
+ <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">
@@ -29,7 +29,7 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="{{request.static_url('sqla_demo:static/pyramid.png')}}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('sqla_demo:static/pyramid.png') }}" alt="pyramid web framework">
</div>
<div class="col-md-10">
{% block content %}
@@ -40,10 +40,8 @@
<div class="row">
<div class="links">
<ul>
- <li class="current-version">Generated by v1.7.dev0</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><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-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>
</ul>
</div>
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 bb622bf5a..bd00845d5 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2
+++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2
@@ -2,7 +2,7 @@
{% block content %}
<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7.dev0</span>.</p>
+ <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>
</div>
{% endblock content %}
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/tests.py b/docs/quick_tour/sqla_demo/sqla_demo/tests.py
index b6b6fdf4d..6db538ffd 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/tests.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/tests.py
@@ -13,19 +13,19 @@ class BaseTest(unittest.TestCase):
self.config = testing.setUp(settings={
'sqlalchemy.url': 'sqlite:///:memory:'
})
- self.config.include('.models.meta')
+ self.config.include('.models')
settings = self.config.get_settings()
- from .models.meta import (
- get_session,
+ from .models import (
get_engine,
- get_dbmaker,
+ get_session_factory,
+ get_tm_session,
)
self.engine = get_engine(settings)
- dbmaker = get_dbmaker(self.engine)
+ session_factory = get_session_factory(self.engine)
- self.session = get_session(transaction.manager, dbmaker)
+ self.session = get_tm_session(session_factory, transaction.manager)
def init_database(self):
from .models.meta import Base
@@ -36,7 +36,7 @@ class BaseTest(unittest.TestCase):
testing.tearDown()
transaction.abort()
- Base.metadata.create_all(self.engine)
+ Base.metadata.drop_all(self.engine)
class TestMyViewSuccessCondition(BaseTest):
@@ -45,7 +45,7 @@ class TestMyViewSuccessCondition(BaseTest):
super(TestMyViewSuccessCondition, self).setUp()
self.init_database()
- from .models.mymodel import MyModel
+ from .models import MyModel
model = MyModel(name='one', value=55)
self.session.add(model)
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/views/default.py b/docs/quick_tour/sqla_demo/sqla_demo/views/default.py
index e5e70cf9d..d4afb1b0b 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/views/default.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/views/default.py
@@ -3,18 +3,16 @@ from pyramid.view import view_config
from sqlalchemy.exc import DBAPIError
-from ..models.mymodel import MyModel
+from ..models import MyModel
@view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
def my_view(request):
try:
query = request.dbsession.query(MyModel)
- # Start Sphinx Include
one = query.filter(MyModel.name == 'one').first()
- # End Sphinx Include
except DBAPIError:
- return Response(db_err_msg, content_type='text/plain', status_int=500)
+ return Response(db_err_msg, content_type='text/plain', status=500)
return {'one': one, 'project': 'sqla_demo'}
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/views/notfound.py b/docs/quick_tour/sqla_demo/sqla_demo/views/notfound.py
new file mode 100644
index 000000000..69d6e2804
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/sqla_demo/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.jinja2')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/quick_tutorial/authentication/development.ini b/docs/quick_tutorial/authentication/development.ini
index 8a39b2fe7..a4586d45f 100644
--- a/docs/quick_tutorial/authentication/development.ini
+++ b/docs/quick_tutorial/authentication/development.ini
@@ -7,5 +7,4 @@ tutorial.secret = 98zd
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/authorization/development.ini b/docs/quick_tutorial/authorization/development.ini
index 8a39b2fe7..a4586d45f 100644
--- a/docs/quick_tutorial/authorization/development.ini
+++ b/docs/quick_tutorial/authorization/development.ini
@@ -7,5 +7,4 @@ tutorial.secret = 98zd
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/cookiecutters.rst b/docs/quick_tutorial/cookiecutters.rst
new file mode 100644
index 000000000..337a5c535
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters.rst
@@ -0,0 +1,87 @@
+.. _qtut_cookiecutters:
+
+=================================================
+Prelude: Quick Project Startup with Cookiecutters
+=================================================
+
+To ease the process of getting started on a project, the Pylons Project provides :term:`cookiecutter`\ s that generate sample :app:`Pyramid` projects from project templates. These cookiecutters will install :app:`Pyramid` and its dependencies as well. We will still cover many topics of web application development using :app:`Pyramid`, but it's good to know of this facility. This prelude will demonstrate how to get a working :app:`Pyramid` web application running via ``cookiecutter``.
+
+
+Objectives
+==========
+
+- Use a cookiecutter to make a new project.
+
+- Start up a :app:`Pyramid` application and visit it in a web browser.
+
+
+Steps
+=====
+
+#. Install cookiecutter into your virtual environment.
+
+ .. code-block:: bash
+
+ $VENV/bin/pip install cookiecutter
+
+#. Let's use the cookiecutter ``pyramid-cookiecutter-starter`` to create a starter :app:`Pyramid` project in the current directory, entering values at the prompts as shown below for the following command.
+
+ .. code-block:: bash
+
+ $ $VENV/bin/cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter
+
+ 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]: 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.
+
+ .. code-block:: bash
+
+ # Change directory into your newly created project.
+ $ cd cc_starter
+ # Create a new virtual environment...
+ $ python3 -m venv env
+ # ...where we upgrade packaging tools...
+ $ env/bin/pip install --upgrade pip setuptools
+ # ...and into which we install our project.
+ $ env/bin/pip install -e .
+
+#. Start up the application by pointing :app:`Pyramid`'s ``pserve`` command at the
+ project's (generated) configuration file:
+
+ .. code-block:: bash
+
+ $ env/bin/pserve development.ini --reload
+
+ On start up, ``pserve`` logs some output:
+
+ .. code-block:: text
+
+ Starting subprocess with file monitor
+ Starting server in PID 73732.
+ Serving on http://localhost:6543
+ Serving on http://localhost:6543
+
+#. Open http://localhost:6543/ in your browser.
+
+Analysis
+========
+
+Rather than starting from scratch, a cookiecutter can make it easy to get a Python
+project containing a working :app:`Pyramid` application. The Pylons Project provides `several cookiecutters <https://github.com/Pylons?q=pyramid-cookiecutter>`_.
+
+``pserve`` is :app:`Pyramid`'s application runner, separating operational details from
+your code. When you install :app:`Pyramid`, a small command program called ``pserve``
+is written to your ``bin`` directory. This program is an executable Python
+module. It is passed a configuration file (in this case, ``development.ini``).
diff --git a/docs/quick_tutorial/cookiecutters/.coveragerc b/docs/quick_tutorial/cookiecutters/.coveragerc
new file mode 100644
index 000000000..1bcbb8c3e
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = cc_starter
+omit = cc_starter/test*
diff --git a/docs/quick_tutorial/cookiecutters/CHANGES.txt b/docs/quick_tutorial/cookiecutters/CHANGES.txt
new file mode 100644
index 000000000..14b902fd1
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/CHANGES.txt
@@ -0,0 +1,4 @@
+0.0
+---
+
+- Initial version.
diff --git a/docs/quick_tutorial/cookiecutters/MANIFEST.in b/docs/quick_tutorial/cookiecutters/MANIFEST.in
new file mode 100644
index 000000000..79c7ec16c
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/MANIFEST.in
@@ -0,0 +1,2 @@
+include *.txt *.ini *.cfg *.rst
+recursive-include cc_starter *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/quick_tutorial/cookiecutters/README.txt b/docs/quick_tutorial/cookiecutters/README.txt
new file mode 100644
index 000000000..55c5dcec6
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/README.txt
@@ -0,0 +1,29 @@
+cc_starter
+==========
+
+Getting Started
+---------------
+
+- Change directory into your newly created project.
+
+ cd cc_starter
+
+- Create a Python virtual environment.
+
+ python3 -m venv env
+
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/__init__.py b/docs/quick_tutorial/cookiecutters/cc_starter/__init__.py
new file mode 100644
index 000000000..49dde36d4
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/cc_starter/__init__.py
@@ -0,0 +1,12 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ """ This function returns a Pyramid WSGI application.
+ """
+ config = Configurator(settings=settings)
+ config.include('pyramid_jinja2')
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.add_route('home', '/')
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid-16x16.png b/docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid.png b/docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid.png
new file mode 100644
index 000000000..4ab837be9
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/cc_starter/static/pyramid.png
Binary files differ
diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/static/theme.css b/docs/quick_tutorial/cookiecutters/cc_starter/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/cc_starter/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 b/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2
new file mode 100644
index 000000000..20da74879
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="{{request.static_url('cc_starter:static/pyramid-16x16.png')}}">
+
+ <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">
+
+ <!-- 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>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="{{request.static_url('cc_starter:static/pyramid.png') }}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <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>
+ </ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- 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>
+ </body>
+</html>
diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/templates/mytemplate.jinja2 b/docs/quick_tutorial/cookiecutters/cc_starter/templates/mytemplate.jinja2
new file mode 100644
index 000000000..979ee5071
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/cc_starter/templates/mytemplate.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% 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>
+</div>
+{% endblock content %}
diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/tests.py b/docs/quick_tutorial/cookiecutters/cc_starter/tests.py
new file mode 100644
index 000000000..2f553bbb4
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/cc_starter/tests.py
@@ -0,0 +1,29 @@
+import unittest
+
+from pyramid import testing
+
+
+class ViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_my_view(self):
+ from .views import my_view
+ request = testing.DummyRequest()
+ info = my_view(request)
+ self.assertEqual(info['project'], 'cc_starter')
+
+
+class FunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from cc_starter import main
+ app = main({})
+ from webtest import TestApp
+ self.testapp = TestApp(app)
+
+ def test_root(self):
+ res = self.testapp.get('/', status=200)
+ self.assertTrue(b'Pyramid' in res.body)
diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/views.py b/docs/quick_tutorial/cookiecutters/cc_starter/views.py
new file mode 100644
index 000000000..deedd53b8
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/cc_starter/views.py
@@ -0,0 +1,6 @@
+from pyramid.view import view_config
+
+
+@view_config(route_name='home', renderer='templates/mytemplate.jinja2')
+def my_view(request):
+ return {'project': 'cc_starter'}
diff --git a/docs/quick_tutorial/scaffolds/development.ini b/docs/quick_tutorial/cookiecutters/development.ini
index b31d06194..a5093fb52 100644
--- a/docs/quick_tutorial/scaffolds/development.ini
+++ b/docs/quick_tutorial/cookiecutters/development.ini
@@ -4,14 +4,14 @@
###
[app:main]
-use = egg:scaffolds
+use = egg:cc_starter
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
+pyramid.includes =
pyramid_debugtoolbar
# By default, the toolbar only appears for clients from IP addresses
@@ -24,8 +24,7 @@ pyramid.includes =
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = localhost:6543
###
# logging configuration
@@ -33,7 +32,7 @@ port = 6543
###
[loggers]
-keys = root, scaffolds
+keys = root, cc_starter
[handlers]
keys = console
@@ -45,10 +44,10 @@ keys = generic
level = INFO
handlers = console
-[logger_scaffolds]
+[logger_cc_starter]
level = DEBUG
handlers =
-qualname = scaffolds
+qualname = cc_starter
[handler_console]
class = StreamHandler
@@ -57,4 +56,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/quick_tutorial/cookiecutters/production.ini b/docs/quick_tutorial/cookiecutters/production.ini
new file mode 100644
index 000000000..e24a065b1
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/production.ini
@@ -0,0 +1,53 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:cc_starter
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+listen = *:6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, cc_starter
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+
+[logger_cc_starter]
+level = WARN
+handlers =
+qualname = cc_starter
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/quick_tutorial/cookiecutters/pytest.ini b/docs/quick_tutorial/cookiecutters/pytest.ini
new file mode 100644
index 000000000..a7bd797f0
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = cc_starter
+python_files = *.py
diff --git a/docs/quick_tutorial/cookiecutters/setup.py b/docs/quick_tutorial/cookiecutters/setup.py
new file mode 100644
index 000000000..e47cdf8ea
--- /dev/null
+++ b/docs/quick_tutorial/cookiecutters/setup.py
@@ -0,0 +1,51 @@
+import os
+
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
+
+requires = [
+ 'pyramid',
+ 'pyramid_jinja2',
+ 'pyramid_debugtoolbar',
+ 'waitress',
+]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest',
+ 'pytest-cov',
+]
+
+setup(
+ name='cc_starter',
+ version='0.0',
+ description='cc_starter',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = cc_starter:main',
+ ],
+ },
+)
diff --git a/docs/quick_tutorial/databases.rst b/docs/quick_tutorial/databases.rst
index c8d87c180..87f2703c7 100644
--- a/docs/quick_tutorial/databases.rst
+++ b/docs/quick_tutorial/databases.rst
@@ -21,7 +21,7 @@ storage and retrieval for the wiki pages in the previous step.
.. note::
- The ``alchemy`` scaffold is really helpful for getting an SQLAlchemy
+ The ``pyramid-cookiecutter-alchemy`` cookiecutter is really helpful for getting an SQLAlchemy
project going, including generation of the console script. Since we want to
see all the decisions, we will forgo convenience in this tutorial, and wire
it up ourselves.
diff --git a/docs/quick_tutorial/databases/development.ini b/docs/quick_tutorial/databases/development.ini
index 5da87d602..270643071 100644
--- a/docs/quick_tutorial/databases/development.ini
+++ b/docs/quick_tutorial/databases/development.ini
@@ -9,7 +9,6 @@ sqlalchemy.url = sqlite:///%(here)s/sqltutorial.sqlite
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
# Begin logging configuration
diff --git a/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt b/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt
index d1fea0d7f..25bab04d0 100644
--- a/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt
+++ b/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt
@@ -2,12 +2,23 @@
<html lang="en">
<head>
<title>WikiPage: Add/Edit</title>
+ <link rel="stylesheet"
+ href="${request.static_url('deform:static/css/bootstrap.min.css')}"
+ type="text/css" media="screen" charset="utf-8"/>
+ <link rel="stylesheet"
+ href="${request.static_url('deform:static/css/form.css')}"
+ type="text/css"/>
<tal:block tal:repeat="reqt view.reqts['css']">
<link rel="stylesheet" type="text/css"
- href="${request.static_url('deform:static/' + reqt)}"/>
+ href="${request.static_url(reqt)}"/>
</tal:block>
+ <script src="${request.static_url('deform:static/scripts/jquery-2.0.3.min.js')}"
+ type="text/javascript"></script>
+ <script src="${request.static_url('deform:static/scripts/bootstrap.min.js')}"
+ type="text/javascript"></script>
+
<tal:block tal:repeat="reqt view.reqts['js']">
- <script src="${request.static_url('deform:static/' + reqt)}"
+ <script src="${request.static_url(reqt)}"
type="text/javascript"></script>
</tal:block>
</head>
diff --git a/docs/quick_tutorial/debugtoolbar/development.ini b/docs/quick_tutorial/debugtoolbar/development.ini
index 52b2a3a41..17b479011 100644
--- a/docs/quick_tutorial/debugtoolbar/development.ini
+++ b/docs/quick_tutorial/debugtoolbar/development.ini
@@ -5,5 +5,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/forms/development.ini b/docs/quick_tutorial/forms/development.ini
index 4d47e54a5..7066668bf 100644
--- a/docs/quick_tutorial/forms/development.ini
+++ b/docs/quick_tutorial/forms/development.ini
@@ -6,5 +6,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt b/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt
index d1fea0d7f..25bab04d0 100644
--- a/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt
+++ b/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt
@@ -2,12 +2,23 @@
<html lang="en">
<head>
<title>WikiPage: Add/Edit</title>
+ <link rel="stylesheet"
+ href="${request.static_url('deform:static/css/bootstrap.min.css')}"
+ type="text/css" media="screen" charset="utf-8"/>
+ <link rel="stylesheet"
+ href="${request.static_url('deform:static/css/form.css')}"
+ type="text/css"/>
<tal:block tal:repeat="reqt view.reqts['css']">
<link rel="stylesheet" type="text/css"
- href="${request.static_url('deform:static/' + reqt)}"/>
+ href="${request.static_url(reqt)}"/>
</tal:block>
+ <script src="${request.static_url('deform:static/scripts/jquery-2.0.3.min.js')}"
+ type="text/javascript"></script>
+ <script src="${request.static_url('deform:static/scripts/bootstrap.min.js')}"
+ type="text/javascript"></script>
+
<tal:block tal:repeat="reqt view.reqts['js']">
- <script src="${request.static_url('deform:static/' + reqt)}"
+ <script src="${request.static_url(reqt)}"
type="text/javascript"></script>
</tal:block>
</head>
diff --git a/docs/quick_tutorial/functional_testing/development.ini b/docs/quick_tutorial/functional_testing/development.ini
index 52b2a3a41..17b479011 100644
--- a/docs/quick_tutorial/functional_testing/development.ini
+++ b/docs/quick_tutorial/functional_testing/development.ini
@@ -5,5 +5,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/hello_world.rst b/docs/quick_tutorial/hello_world.rst
index 56dccde58..2f2515fcd 100644
--- a/docs/quick_tutorial/hello_world.rst
+++ b/docs/quick_tutorial/hello_world.rst
@@ -11,7 +11,7 @@ Python packages, no ``pip install -e .``, no other machinery.
Background
==========
-Microframeworks are all the rage these days. "Microframework" is a marketing
+Microframeworks were all the rage, until the next shiny thing came along. "Microframework" is a marketing
term, not a technical one. They have a low mental overhead: they do so little,
the only things you have to worry about are *your things*.
@@ -49,7 +49,7 @@ Steps
.. code-block:: bash
- $ mkdir hello_world; cd hello_world
+ $ cd ~/projects/quick_tutorial; mkdir hello_world; cd hello_world
#. Copy the following into ``hello_world/app.py``:
diff --git a/docs/quick_tutorial/index.rst b/docs/quick_tutorial/index.rst
index 29b4d8fb7..b5b7b3313 100644
--- a/docs/quick_tutorial/index.rst
+++ b/docs/quick_tutorial/index.rst
@@ -19,7 +19,7 @@ Contents
requirements
tutorial_approach
- scaffolds
+ cookiecutters
hello_world
package
ini
diff --git a/docs/quick_tutorial/ini.rst b/docs/quick_tutorial/ini.rst
index 9a65d66d1..96dfc5b5f 100644
--- a/docs/quick_tutorial/ini.rst
+++ b/docs/quick_tutorial/ini.rst
@@ -136,7 +136,7 @@ Extra credit
.. seealso::
:ref:`project_narr`,
- :ref:`scaffolding_chapter`,
+ :ref:`cookiecutters`,
:ref:`what_is_this_pserve_thing`,
:ref:`environment_chapter`,
:ref:`paste_chapter`
diff --git a/docs/quick_tutorial/ini/development.ini b/docs/quick_tutorial/ini/development.ini
index 8853e2c2b..cffbd66c9 100644
--- a/docs/quick_tutorial/ini/development.ini
+++ b/docs/quick_tutorial/ini/development.ini
@@ -3,5 +3,4 @@ use = egg:tutorial
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/jinja2/development.ini b/docs/quick_tutorial/jinja2/development.ini
index 4d47e54a5..7066668bf 100644
--- a/docs/quick_tutorial/jinja2/development.ini
+++ b/docs/quick_tutorial/jinja2/development.ini
@@ -6,5 +6,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/json/development.ini b/docs/quick_tutorial/json/development.ini
index 4d47e54a5..7066668bf 100644
--- a/docs/quick_tutorial/json/development.ini
+++ b/docs/quick_tutorial/json/development.ini
@@ -6,5 +6,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/logging.rst b/docs/quick_tutorial/logging.rst
index cbbf7860e..0a530e91f 100644
--- a/docs/quick_tutorial/logging.rst
+++ b/docs/quick_tutorial/logging.rst
@@ -15,7 +15,7 @@ It's important to know what is going on inside our web application. In
development we might need to collect some output. In production, we might need
to detect problems when other people use the site. We need *logging*.
-Fortunately Pyramid uses the normal Python approach to logging. The scaffold
+Fortunately Pyramid uses the normal Python approach to logging. The project
generated in your ``development.ini`` has a number of lines that configure the
logging for you to some reasonable defaults. You then see messages sent by
Pyramid, for example, when a new request comes in.
diff --git a/docs/quick_tutorial/logging/development.ini b/docs/quick_tutorial/logging/development.ini
index 62e0c5123..b869ca5b6 100644
--- a/docs/quick_tutorial/logging/development.ini
+++ b/docs/quick_tutorial/logging/development.ini
@@ -6,7 +6,6 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
# Begin logging configuration
diff --git a/docs/quick_tutorial/more_view_classes/development.ini b/docs/quick_tutorial/more_view_classes/development.ini
index 4d47e54a5..7066668bf 100644
--- a/docs/quick_tutorial/more_view_classes/development.ini
+++ b/docs/quick_tutorial/more_view_classes/development.ini
@@ -6,5 +6,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/request_response.rst b/docs/quick_tutorial/request_response.rst
index 0ac9b4f6d..ece8cdd6f 100644
--- a/docs/quick_tutorial/request_response.rst
+++ b/docs/quick_tutorial/request_response.rst
@@ -26,7 +26,7 @@ part of a web application, web developers need a robust, mature set of software
for web requests and returning web responses.
Pyramid has always fit nicely into the existing world of Python web development
-(virtual environments, packaging, scaffolding, first to embrace Python 3, and
+(virtual environments, packaging, cookiecutters, first to embrace Python 3, and
so on). Pyramid turned to the well-regarded :term:`WebOb` Python library for
request and response handling. In our example above, Pyramid hands
``hello_world`` a ``request`` that is :ref:`based on WebOb <webob_chapter>`.
diff --git a/docs/quick_tutorial/request_response/development.ini b/docs/quick_tutorial/request_response/development.ini
index 4d47e54a5..7066668bf 100644
--- a/docs/quick_tutorial/request_response/development.ini
+++ b/docs/quick_tutorial/request_response/development.ini
@@ -6,5 +6,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst
index afa8ed104..70e68514b 100644
--- a/docs/quick_tutorial/requirements.rst
+++ b/docs/quick_tutorial/requirements.rst
@@ -19,11 +19,11 @@ virtual environment.)
This *Quick Tutorial* is based on:
-* **Python 3.5**. Pyramid fully supports Python 3.4+ and Python 2.7+. This
- tutorial uses **Python 3.5** but runs fine under Python 2.7.
+* **Python 3.6**. Pyramid fully supports Python 3.4+ and Python 2.7+. This
+ tutorial uses **Python 3.6** but runs fine under Python 2.7.
* **venv**. We believe in virtual environments. For this tutorial, we use
- Python 3.5's built-in solution :term:`venv`. For Python 2.7, you can install
+ Python 3.6's built-in solution :term:`venv`. For Python 2.7, you can install
:term:`virtualenv`.
* **pip**. We use :term:`pip` for package management.
@@ -156,7 +156,7 @@ environment variable.
.. code-block:: doscon
# Windows
- c:\> c:\Python35\python -m venv %VENV%
+ c:\> python -m venv %VENV%
.. seealso:: See also Python 3's :mod:`venv module <python:venv>` and Python
2's `virtualenv <https://virtualenv.pypa.io/en/latest/>`_ package.
@@ -172,7 +172,7 @@ time of its release.
.. code-block:: bash
# Mac and Linux
- $VENV/bin/pip install --upgrade pip setuptools
+ $ $VENV/bin/pip install --upgrade pip setuptools
.. code-block:: doscon
@@ -214,4 +214,4 @@ tutorial.
.. code-block:: doscon
# Windows
- c:\> %VENV%\Scripts\pip install webtest deform sqlalchemy pyramid_chameleon pyramid_debugtoolbar pyramid_jinja2 waitress pyramid_tm zope.sqlalchemy
+ c:\> %VENV%\Scripts\pip install webtest pytest pytest-cov deform sqlalchemy pyramid_chameleon pyramid_debugtoolbar pyramid_jinja2 waitress pyramid_tm zope.sqlalchemy
diff --git a/docs/quick_tutorial/retail_forms/development.ini b/docs/quick_tutorial/retail_forms/development.ini
index 4d47e54a5..7066668bf 100644
--- a/docs/quick_tutorial/retail_forms/development.ini
+++ b/docs/quick_tutorial/retail_forms/development.ini
@@ -6,5 +6,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/routing/development.ini b/docs/quick_tutorial/routing/development.ini
index 4d47e54a5..7066668bf 100644
--- a/docs/quick_tutorial/routing/development.ini
+++ b/docs/quick_tutorial/routing/development.ini
@@ -6,5 +6,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/scaffolds.rst b/docs/quick_tutorial/scaffolds.rst
deleted file mode 100644
index ad002f4fd..000000000
--- a/docs/quick_tutorial/scaffolds.rst
+++ /dev/null
@@ -1,87 +0,0 @@
-.. _qtut_scaffolds:
-
-=============================================
-Prelude: Quick Project Startup with Scaffolds
-=============================================
-
-To ease the process of getting started, Pyramid provides *scaffolds* that
-generate sample projects from templates in Pyramid and Pyramid add-ons.
-
-
-Background
-==========
-
-We're going to cover a lot in this tutorial, focusing on one topic at a time
-and writing everything from scratch. As a warm up, though, it sure would be
-nice to see some pixels on a screen.
-
-Like other web development frameworks, Pyramid provides a number of "scaffolds"
-that generate working Python, template, and CSS code for sample applications.
-In this step we'll use a built-in scaffold to let us preview a Pyramid
-application, before starting from scratch on Step 1.
-
-
-Objectives
-==========
-
-- Use Pyramid's ``pcreate`` command to list scaffolds and make a new project.
-
-- Start up a Pyramid application and visit it in a web browser.
-
-
-Steps
-=====
-
-#. Pyramid's ``pcreate`` command can list the available scaffolds:
-
- .. code-block:: bash
-
- $ $VENV/bin/pcreate --list
- Available scaffolds:
- alchemy: Pyramid project using SQLAlchemy, SQLite, URL dispatch, and Jinja2
- starter: Pyramid starter project using URL dispatch and Chameleon
- zodb: Pyramid project using ZODB, traversal, and Chameleon
-
-#. Tell ``pcreate`` to use the ``starter`` scaffold to make our project:
-
- .. code-block:: bash
-
- $ $VENV/bin/pcreate --scaffold starter scaffolds
-
-#. Install our project in editable mode for development in the current
- directory:
-
- .. code-block:: bash
-
- $ cd scaffolds
- $ $VENV/bin/pip install -e .
-
-#. Start up the application by pointing Pyramid's ``pserve`` command at the
- project's (generated) configuration file:
-
- .. code-block:: bash
-
- $ $VENV/bin/pserve development.ini --reload
-
- On start up, ``pserve`` logs some output:
-
- .. code-block:: bash
-
- Starting subprocess with file monitor
- Starting server in PID 72213.
- Starting HTTP server on http://0.0.0.0:6543
-
-#. Open http://localhost:6543/ in your browser.
-
-Analysis
-========
-
-Rather than starting from scratch, ``pcreate`` can make getting a Python
-project containing a Pyramid application a quick matter. Pyramid ships with a
-few scaffolds. But installing a Pyramid add-on can give you new scaffolds from
-that add-on.
-
-``pserve`` is Pyramid's application runner, separating operational details from
-your code. When you install Pyramid, a small command program called ``pserve``
-is written to your ``bin`` directory. This program is an executable Python
-module. It is passed a configuration file (in this case, ``development.ini``).
diff --git a/docs/quick_tutorial/scaffolds/CHANGES.txt b/docs/quick_tutorial/scaffolds/CHANGES.txt
deleted file mode 100644
index 35a34f332..000000000
--- a/docs/quick_tutorial/scaffolds/CHANGES.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-0.0
----
-
-- Initial version
diff --git a/docs/quick_tutorial/scaffolds/MANIFEST.in b/docs/quick_tutorial/scaffolds/MANIFEST.in
deleted file mode 100644
index 91d3f763b..000000000
--- a/docs/quick_tutorial/scaffolds/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-include *.txt *.ini *.cfg *.rst
-recursive-include scaffolds *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
diff --git a/docs/quick_tutorial/scaffolds/README.txt b/docs/quick_tutorial/scaffolds/README.txt
deleted file mode 100644
index 7776dd2d5..000000000
--- a/docs/quick_tutorial/scaffolds/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-scaffolds README
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/favicon.ico b/docs/quick_tutorial/scaffolds/scaffolds/static/favicon.ico
deleted file mode 100644
index 71f837c9e..000000000
--- a/docs/quick_tutorial/scaffolds/scaffolds/static/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png b/docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png
deleted file mode 100644
index 1fbc873da..000000000
--- a/docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png b/docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png
deleted file mode 100644
index 0596f2020..000000000
--- a/docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css b/docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css
deleted file mode 100644
index b7c8493d8..000000000
--- a/docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css
+++ /dev/null
@@ -1,8 +0,0 @@
-* html img,
-* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none",
-this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')",
-this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''),
-this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')",
-this.runtimeStyle.backgroundImage = "none")),this.pngSet=true)
-);}
-#wrap{display:table;height:100%}
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.png b/docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.png
deleted file mode 100644
index 2369cfb7d..000000000
--- a/docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.png
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css b/docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css
deleted file mode 100644
index 4b1c017cd..000000000
--- a/docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css
+++ /dev/null
@@ -1,372 +0,0 @@
-html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
-{
- margin: 0;
- padding: 0;
- border: 0;
- outline: 0;
- font-size: 100%; /* 16px */
- vertical-align: baseline;
- background: transparent;
-}
-
-body
-{
- line-height: 1;
-}
-
-ol, ul
-{
- list-style: none;
-}
-
-blockquote, q
-{
- quotes: none;
-}
-
-blockquote:before, blockquote:after, q:before, q:after
-{
- content: '';
- content: none;
-}
-
-:focus
-{
- outline: 0;
-}
-
-ins
-{
- text-decoration: none;
-}
-
-del
-{
- text-decoration: line-through;
-}
-
-table
-{
- border-collapse: collapse;
- border-spacing: 0;
-}
-
-sub
-{
- vertical-align: sub;
- font-size: smaller;
- line-height: normal;
-}
-
-sup
-{
- vertical-align: super;
- font-size: smaller;
- line-height: normal;
-}
-
-ul, menu, dir
-{
- display: block;
- list-style-type: disc;
- margin: 1em 0;
- padding-left: 40px;
-}
-
-ol
-{
- display: block;
- list-style-type: decimal-leading-zero;
- margin: 1em 0;
- padding-left: 40px;
-}
-
-li
-{
- display: list-item;
-}
-
-ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
-{
- margin-top: 0;
- margin-bottom: 0;
-}
-
-ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
-{
- list-style-type: circle;
-}
-
-ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
-{
- list-style-type: square;
-}
-
-.hidden
-{
- display: none;
-}
-
-p
-{
- line-height: 1.5em;
-}
-
-h1
-{
- font-size: 1.75em;
- line-height: 1.7em;
- font-family: helvetica, verdana;
-}
-
-h2
-{
- font-size: 1.5em;
- line-height: 1.7em;
- font-family: helvetica, verdana;
-}
-
-h3
-{
- font-size: 1.25em;
- line-height: 1.7em;
- font-family: helvetica, verdana;
-}
-
-h4
-{
- font-size: 1em;
- line-height: 1.7em;
- font-family: helvetica, verdana;
-}
-
-html, body
-{
- width: 100%;
- height: 100%;
-}
-
-body
-{
- margin: 0;
- padding: 0;
- background-color: #fff;
- position: relative;
- font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
-}
-
-a
-{
- color: #1b61d6;
- text-decoration: none;
-}
-
-a:hover
-{
- color: #e88f00;
- text-decoration: underline;
-}
-
-body h1, body h2, body h3, body h4, body h5, body h6
-{
- font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
- font-weight: 400;
- color: #373839;
- font-style: normal;
-}
-
-#wrap
-{
- min-height: 100%;
-}
-
-#header, #footer
-{
- width: 100%;
- color: #fff;
- height: 40px;
- position: absolute;
- text-align: center;
- line-height: 40px;
- overflow: hidden;
- font-size: 12px;
- vertical-align: middle;
-}
-
-#header
-{
- background: #000;
- top: 0;
- font-size: 14px;
-}
-
-#footer
-{
- bottom: 0;
- background: #000 url(footerbg.png) repeat-x 0 top;
- position: relative;
- margin-top: -40px;
- clear: both;
-}
-
-.header, .footer
-{
- width: 750px;
- margin-right: auto;
- margin-left: auto;
-}
-
-.wrapper
-{
- width: 100%;
-}
-
-#top, #top-small, #bottom
-{
- width: 100%;
-}
-
-#top
-{
- color: #000;
- height: 230px;
- background: #fff url(headerbg.png) repeat-x 0 top;
- position: relative;
-}
-
-#top-small
-{
- color: #000;
- height: 60px;
- background: #fff url(headerbg.png) repeat-x 0 top;
- position: relative;
-}
-
-#bottom
-{
- color: #222;
- background-color: #fff;
-}
-
-.top, .top-small, .middle, .bottom
-{
- width: 750px;
- margin-right: auto;
- margin-left: auto;
-}
-
-.top
-{
- padding-top: 40px;
-}
-
-.top-small
-{
- padding-top: 10px;
-}
-
-#middle
-{
- width: 100%;
- height: 100px;
- background: url(middlebg.png) repeat-x;
- border-top: 2px solid #fff;
- border-bottom: 2px solid #b2b2b2;
-}
-
-.app-welcome
-{
- margin-top: 25px;
-}
-
-.app-name
-{
- color: #000;
- font-weight: 700;
-}
-
-.bottom
-{
- padding-top: 50px;
-}
-
-#left
-{
- width: 350px;
- float: left;
- padding-right: 25px;
-}
-
-#right
-{
- width: 350px;
- float: right;
- padding-left: 25px;
-}
-
-.align-left
-{
- text-align: left;
-}
-
-.align-right
-{
- text-align: right;
-}
-
-.align-center
-{
- text-align: center;
-}
-
-ul.links
-{
- margin: 0;
- padding: 0;
-}
-
-ul.links li
-{
- list-style-type: none;
- font-size: 14px;
-}
-
-form
-{
- border-style: none;
-}
-
-fieldset
-{
- border-style: none;
-}
-
-input
-{
- color: #222;
- border: 1px solid #ccc;
- font-family: sans-serif;
- font-size: 12px;
- line-height: 16px;
-}
-
-input[type=text], input[type=password]
-{
- width: 205px;
-}
-
-input[type=submit]
-{
- background-color: #ddd;
- font-weight: 700;
-}
-
-/*Opera Fix*/
-body:before
-{
- content: "";
- height: 100%;
- float: left;
- width: 0;
- margin-top: -32767px;
-}
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png b/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png
deleted file mode 100644
index a5bc0ade7..000000000
--- a/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.png b/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.png
deleted file mode 100644
index 347e05549..000000000
--- a/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.png
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif b/docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif
deleted file mode 100644
index 0341802e5..000000000
--- a/docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt b/docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt
deleted file mode 100644
index b43a174e3..000000000
--- a/docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt
+++ /dev/null
@@ -1,73 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head>
- <title>The Pyramid Web Framework</title>
- <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
- <meta name="keywords" content="python web application" />
- <meta name="description" content="pyramid web application" />
- <link rel="shortcut icon" href="${request.static_url('scaffolds:static/favicon.ico')}" />
- <link rel="stylesheet" href="${request.static_url('scaffolds:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" />
- <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" />
- <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" />
- <!--[if lte IE 6]>
- <link rel="stylesheet" href="${request.static_url('scaffolds:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" />
- <![endif]-->
-</head>
-<body>
- <div id="wrap">
- <div id="top">
- <div class="top align-center">
- <div><img src="${request.static_url('scaffolds:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div>
- </div>
- </div>
- <div id="middle">
- <div class="middle align-center">
- <p class="app-welcome">
- Welcome to <span class="app-name">${project}</span>, an application generated by<br/>
- the Pyramid Web Framework.
- </p>
- </div>
- </div>
- <div id="bottom">
- <div class="bottom">
- <div id="left" class="align-right">
- <h2>Search documentation</h2>
- <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/search.html">
- <input type="text" id="q" name="q" value="" />
- <input type="submit" id="x" value="Go" />
- </form>
- </div>
- <div id="right" class="align-left">
- <h2>Pyramid links</h2>
- <ul class="links">
- <li>
- <a href="http://pylonsproject.org">Pylons Website</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#narrative-documentation">Narrative Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#reference-material">API Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#tutorials">Tutorials</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#detailed-change-history">Change History</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#sample-applications">Sample Applications</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#support-and-development">Support and Development</a>
- </li>
- <li>
- <a href="irc://irc.freenode.net#pyramid">IRC Channel</a>
- </li>
- </ul>
- </div>
- </div>
- </div>
- </div>
-</body>
-</html>
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/tests.py b/docs/quick_tutorial/scaffolds/scaffolds/tests.py
deleted file mode 100644
index 4f906ffa9..000000000
--- a/docs/quick_tutorial/scaffolds/scaffolds/tests.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import unittest
-
-from pyramid import testing
-
-
-class ViewTests(unittest.TestCase):
- def setUp(self):
- self.config = testing.setUp()
-
- def tearDown(self):
- testing.tearDown()
-
- def test_my_view(self):
- from .views import my_view
- request = testing.DummyRequest()
- info = my_view(request)
- self.assertEqual(info['project'], 'scaffolds')
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/views.py b/docs/quick_tutorial/scaffolds/scaffolds/views.py
deleted file mode 100644
index db90d8364..000000000
--- a/docs/quick_tutorial/scaffolds/scaffolds/views.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from pyramid.view import view_config
-
-
-@view_config(route_name='home', renderer='templates/mytemplate.pt')
-def my_view(request):
- return {'project': 'scaffolds'}
diff --git a/docs/quick_tutorial/scaffolds/setup.py b/docs/quick_tutorial/scaffolds/setup.py
deleted file mode 100644
index ec95946a5..000000000
--- a/docs/quick_tutorial/scaffolds/setup.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import os
-
-from setuptools import setup, find_packages
-
-here = os.path.abspath(os.path.dirname(__file__))
-with open(os.path.join(here, 'README.txt')) as f:
- README = f.read()
-with open(os.path.join(here, 'CHANGES.txt')) as f:
- CHANGES = f.read()
-
-requires = [
- 'pyramid',
- 'pyramid_chameleon',
- 'pyramid_debugtoolbar',
- 'waitress',
- ]
-
-setup(name='scaffolds',
- version='0.0',
- description='scaffolds',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web pyramid pylons',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- install_requires=requires,
- tests_require=requires,
- test_suite="scaffolds",
- entry_points="""\
- [paste.app_factory]
- main = scaffolds:main
- """,
- )
diff --git a/docs/quick_tutorial/sessions/development.ini b/docs/quick_tutorial/sessions/development.ini
index 4d47e54a5..7066668bf 100644
--- a/docs/quick_tutorial/sessions/development.ini
+++ b/docs/quick_tutorial/sessions/development.ini
@@ -6,5 +6,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/static_assets/development.ini b/docs/quick_tutorial/static_assets/development.ini
index 4d47e54a5..7066668bf 100644
--- a/docs/quick_tutorial/static_assets/development.ini
+++ b/docs/quick_tutorial/static_assets/development.ini
@@ -6,5 +6,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/templating/development.ini b/docs/quick_tutorial/templating/development.ini
index 4d47e54a5..7066668bf 100644
--- a/docs/quick_tutorial/templating/development.ini
+++ b/docs/quick_tutorial/templating/development.ini
@@ -6,5 +6,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/tutorial_approach.rst b/docs/quick_tutorial/tutorial_approach.rst
index 49a6bfd85..8da9f71b3 100644
--- a/docs/quick_tutorial/tutorial_approach.rst
+++ b/docs/quick_tutorial/tutorial_approach.rst
@@ -7,25 +7,16 @@ Details, references, and deeper discussions are mentioned in "See also" notes.
.. seealso:: This is an example "See also" note.
-This "Getting Started" tutorial is broken into independent steps, starting with
-the smallest possible "single file WSGI app" example. Each of these steps
-introduce a topic and a very small set of concepts via working code. The steps
-each correspond to a directory in this repo, where each step/topic/directory is
-a Python package.
-
-To successfully run each step:
-
-.. code-block:: bash
-
- $ cd request_response
- $ $VENV/bin/pip install -e .
-
-...and repeat for each step you would like to work on. In most cases we will
-start with the results of an earlier step.
Directory tree
==============
+This "Getting Started" tutorial is broken into independent steps, starting with
+the smallest possible "single file WSGI app" example. Each of these steps
+introduces a topic and a very small set of concepts via working code. The steps
+each correspond to a directory in our workspace, where each step's directory is
+a Python package.
+
As we develop our tutorial, our directory tree will resemble the structure
below:
@@ -41,7 +32,15 @@ below:
│── development.ini
`── setup.py
-Each of the first-level directories (e.g., ``request_response``) is a *Python
+Each of the directories in our ``quick_tutorial`` workspace (e.g., ``request_response``) is a *Python
project* (except as noted for the ``hello_world`` step). The ``tutorial``
-directory is a *Python package*. At the end of each step, we copy a previous
-directory into a new directory to use as a starting point.
+directory is a *Python package*.
+
+For most steps you will copy the previous step's directory to a new directory, and change your working directory to the new directory, then install your project:
+
+.. code-block:: bash
+
+ $ cd ..; cp -r package ini; cd ini
+ $ $VENV/bin/pip install -e .
+
+For a few steps, you won't copy the previous step's directory, but you will still need to install your project with ``$VENV/bin/pip install -e .``.
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 52b2a3a41..17b479011 100644
--- a/docs/quick_tutorial/unit_testing/development.ini
+++ b/docs/quick_tutorial/unit_testing/development.ini
@@ -5,5 +5,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/view_classes/development.ini b/docs/quick_tutorial/view_classes/development.ini
index 4d47e54a5..7066668bf 100644
--- a/docs/quick_tutorial/view_classes/development.ini
+++ b/docs/quick_tutorial/view_classes/development.ini
@@ -6,5 +6,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/quick_tutorial/views/development.ini b/docs/quick_tutorial/views/development.ini
index 52b2a3a41..17b479011 100644
--- a/docs/quick_tutorial/views/development.ini
+++ b/docs/quick_tutorial/views/development.ini
@@ -5,5 +5,4 @@ pyramid.includes =
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
port = 6543
diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst
index c66786b11..170f2ebc8 100644
--- a/docs/tutorials/modwsgi/index.rst
+++ b/docs/tutorials/modwsgi/index.rst
@@ -32,60 +32,71 @@ specific path information for commands and files.
<https://code.google.com/archive/p/modwsgi/wikis/InstallationInstructions.wiki>`_
for your platform into your system's Apache installation.
-#. Create a :term:`virtual environment` which we'll use to install our
- application.
+#. Create a :app:`Pyramid` application. For this tutorial we'll use the
+ ``starter`` :term:`cookiecutter`. See :ref:`project_narr` for more
+ in-depth information about creating a new project.
- .. code-block:: text
+ .. code-block:: bash
$ cd ~
- $ mkdir modwsgi
- $ cd modwsgi
- $ python3 -m venv env
+ $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter
-#. Install :app:`Pyramid` into the newly created virtual environment:
+ If prompted for the first item, accept the default ``yes`` by hitting return.
- .. parsed-literal::
+ .. code-block:: text
- $ cd ~/modwsgi/env
- $ $VENV/bin/pip install "pyramid==\ |release|\ "
+ 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 and install your :app:`Pyramid` application. For the purposes of
- this tutorial, we'll just be using the ``pyramid_starter`` application as
- a baseline application. Substitute your existing :app:`Pyramid`
- application as necessary if you already have one.
+#. Create a :term:`virtual environment` which we'll use to install our
+ application. It is important to use the same base Python interpreter
+ that was used to build ``mod_wsgi``. For example, if ``mod_wsgi`` was
+ built against the system Python 3.x, then your project should use a
+ virtual environment created from that same system Python 3.x.
- .. code-block:: text
+ .. code-block:: bash
- $ cd ~/modwsgi/env
- $ $VENV/bin/pcreate -s starter myapp
- $ cd myapp
- $ $VENV/bin/pip install -e .
+ $ cd myproject
+ $ python3 -m venv env
+
+#. Install your :app:`Pyramid` application and its dependencies.
+
+ .. code-block:: bash
-#. Within the virtual environment directory (``~/modwsgi/env``), create a
- script named ``pyramid.wsgi``. Give it these contents:
+ $ env/bin/pip install -e .
+
+#. Within the project directory (``~/myproject``), create a script
+ named ``pyramid.wsgi``. Give it these contents:
.. code-block:: python
from pyramid.paster import get_app, setup_logging
- ini_path = '/Users/chrism/modwsgi/env/myapp/production.ini'
+ ini_path = '/Users/chrism/myproject/production.ini'
setup_logging(ini_path)
application = get_app(ini_path, 'main')
- The first argument to ``get_app`` is the project configuration file
- name. It's best to use the ``production.ini`` file provided by your
- scaffold, as it contains settings appropriate for
- production. The second is the name of the section within the .ini file
- that should be loaded by ``mod_wsgi``. The assignment to the name
+ The first argument to :func:`pyramid.paster.get_app` is the project
+ configuration file name. It's best to use the ``production.ini`` file
+ provided by your cookiecutter, as it contains settings appropriate for
+ production. The second is the name of the section within the ``.ini``
+ file that should be loaded by ``mod_wsgi``. The assignment to the name
``application`` is important: mod_wsgi requires finding such an
assignment when it opens the file.
- The call to ``setup_logging`` initializes the standard library's
- `logging` module to allow logging within your application.
+ The call to :func:`pyramid.paster.setup_logging` initializes the standard
+ library's `logging` module to allow logging within your application.
See :ref:`logging_config`.
There is no need to make the ``pyramid.wsgi`` script executable.
However, you'll need to make sure that *two* users have access to change
- into the ``~/modwsgi/env`` directory: your current user (mine is
+ into the ``~/myproject`` directory: your current user (mine is
``chrism`` and the user that Apache will run as often named ``apache`` or
``httpd``). Make sure both of these users can "cd" into that directory.
@@ -101,18 +112,17 @@ specific path information for commands and files.
WSGIApplicationGroup %{GLOBAL}
WSGIPassAuthorization On
WSGIDaemonProcess pyramid user=chrism group=staff threads=4 \
- python-path=/Users/chrism/modwsgi/env/lib/python2.7/site-packages
- WSGIScriptAlias /myapp /Users/chrism/modwsgi/env/pyramid.wsgi
+ python-path=/Users/chrism/myproject/env/lib/python3.5/site-packages
+ WSGIScriptAlias /myapp /Users/chrism/myproject/pyramid.wsgi
- <Directory /Users/chrism/modwsgi/env>
+ <Directory /Users/chrism/myproject>
WSGIProcessGroup pyramid
- Order allow,deny
- Allow from all
+ Require all granted
</Directory>
#. Restart Apache
- .. code-block:: text
+ .. code-block:: bash
$ sudo /usr/sbin/apachectl restart
@@ -122,5 +132,5 @@ specific path information for commands and files.
:term:`mod_wsgi` has many knobs and a great variety of deployment modes. This
is just one representation of how you might use it to serve up a :app:`Pyramid`
application. See the `mod_wsgi configuration documentation
-<https://code.google.com/archive/p/modwsgi/wikis/ConfigurationGuidelines.wiki>`_
+<https://modwsgi.readthedocs.io/en/develop/configuration.html>`_
for more in-depth configuration information.
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index 44097b35b..0ba734f83 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -18,6 +18,7 @@ require permission, instead of a default "403 Forbidden" page.
We will implement the access control with the following steps:
+* Add password hashing dependencies.
* Add users and groups (``security.py``, a new module).
* Add an :term:`ACL` (``models.py``).
* Add an :term:`authentication policy` and an :term:`authorization policy`
@@ -25,7 +26,7 @@ We will implement the access control with the following steps:
* Add :term:`permission` declarations to the ``edit_page`` and ``add_page``
views (``views.py``).
-Then we will add the login and logout feature:
+Then we will add the login and logout features:
* Add ``login`` and ``logout`` views (``views.py``).
* Add a login template (``login.pt``).
@@ -38,11 +39,32 @@ Then we will add the login and logout feature:
Access control
--------------
+
+Add dependencies
+~~~~~~~~~~~~~~~~
+
+Just like in :ref:`wiki_defining_views`, we need a new dependency. We need to add the `bcrypt <https://pypi.python.org/pypi/bcrypt>`_ package, to our tutorial package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function.
+
+Open ``setup.py`` and edit it to look like the following:
+
+.. literalinclude:: src/authorization/setup.py
+ :linenos:
+ :emphasize-lines: 21
+ :language: python
+
+Only the highlighted line needs to be added.
+
+Do not forget to run ``pip install -e .`` just like in :ref:`wiki-running-pip-install`.
+
+.. note::
+
+ We are using the ``bcrypt`` package from PyPI to hash our passwords securely. There are other one-way hash algorithms for passwords if bcrypt is an issue on your system. Just make sure that it's an algorithm approved for storing passwords versus a generic one-way hash.
+
+
Add users and groups
~~~~~~~~~~~~~~~~~~~~
-Create a new ``tutorial/security.py`` module with the
-following content:
+Create a new ``tutorial/security.py`` module with the following content:
.. literalinclude:: src/authorization/tutorial/security.py
:linenos:
@@ -51,7 +73,7 @@ following content:
The ``groupfinder`` function accepts a userid and a request and
returns one of these values:
-- If the userid exists in the system, it will return a sequence of group
+- If ``userid`` exists in the system, it will return a sequence of group
identifiers (or an empty sequence if the user isn't a member of any groups).
- If the userid *does not* exist in the system, it will return ``None``.
@@ -61,26 +83,38 @@ request)`` returns ``None``. We will use ``groupfinder()`` as an
:term:`authentication policy` "callback" that will provide the
:term:`principal` or principals for a user.
-In a production system, user and group data will most often come from a
+There are two helper methods that will help us later to authenticate users.
+The first is ``hash_password`` which takes a raw password and transforms it using
+bcrypt into an irreversible representation, a process known as "hashing". The
+second method, ``check_password``, will allow us to compare the hashed value of the
+submitted password against the hashed value of the password stored in the user's
+record. If the two hashed values match, then the submitted
+password is valid, and we can authenticate the user.
+
+We hash passwords so that it is impossible to decrypt and use them to
+authenticate in the application. If we stored passwords foolishly in clear text,
+then anyone with access to the database could retrieve any password to authenticate
+as any user.
+
+In a production system, user and group data will most often be saved and come from a
database, but here we use "dummy" data to represent user and groups sources.
Add an ACL
~~~~~~~~~~
Open ``tutorial/models.py`` and add the following import
-statement at the head:
+statement near the top:
.. literalinclude:: src/authorization/tutorial/models.py
- :lines: 4-7
- :linenos:
+ :lines: 4-8
+ :lineno-match:
:language: python
Add the following lines to the ``Wiki`` class:
.. literalinclude:: src/authorization/tutorial/models.py
:lines: 9-13
- :linenos:
- :lineno-start: 9
+ :lineno-match:
:emphasize-lines: 4-5
:language: python
@@ -89,10 +123,10 @@ permission is allowed, and :data:`~pyramid.security.Everyone`, a special
:term:`principal` that is associated to all requests. Both are used in the
:term:`ACE` entries that make up the ACL.
-The ACL is a list that needs to be named `__acl__` and be an attribute of a
+The ACL is a list that needs to be named ``__acl__`` and be an attribute of a
class. We define an :term:`ACL` with two :term:`ACE` entries: the first entry
-allows any user the `view` permission. The second entry allows the
-``group:editors`` principal the `edit` permission.
+allows any user the ``view`` permission. The second entry allows the
+``group:editors`` principal the ``edit`` permission.
The ``Wiki`` class that contains the ACL is the :term:`resource` constructor
for the :term:`root` resource, which is a ``Wiki`` instance. The ACL is
@@ -115,15 +149,14 @@ statements:
.. literalinclude:: src/authorization/tutorial/__init__.py
:lines: 1-8
:linenos:
- :emphasize-lines: 4-5,8
+ :emphasize-lines: 3-6,8
:language: python
Now add those policies to the configuration:
.. literalinclude:: src/authorization/tutorial/__init__.py
:lines: 18-23
- :linenos:
- :lineno-start: 18
+ :lineno-match:
:emphasize-lines: 1-3,5-6
:language: python
@@ -146,12 +179,12 @@ Open ``tutorial/views.py`` and add a ``permission='edit'`` parameter
to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``:
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 50-52
+ :lines: 49-51
:emphasize-lines: 2-3
:language: python
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 70-72
+ :lines: 68-70
:emphasize-lines: 2-3
:language: python
@@ -213,9 +246,8 @@ cookie.
Now add the ``login`` and ``logout`` views at the end of the file:
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 82-116
- :linenos:
- :lineno-start: 82
+ :lines: 80-
+ :lineno-match:
:language: python
``login()`` has two decorators:
@@ -252,17 +284,17 @@ the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as
follows:
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 47-48
+ :lines: 46-47
:emphasize-lines: 1-2
:language: python
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 67-68
+ :lines: 65-66
:emphasize-lines: 1-2
:language: python
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 78-80
+ :lines: 76-78
:emphasize-lines: 2-3
:language: python
@@ -279,7 +311,7 @@ Open ``tutorial/templates/edit.pt`` and
indicated by the highlighted lines.
.. literalinclude:: src/authorization/tutorial/templates/edit.pt
- :lines: 34-38
+ :lines: 35-39
:emphasize-lines: 3-5
:language: html
@@ -313,7 +345,7 @@ Our ``tutorial/views.py`` will look like this when we're done:
.. literalinclude:: src/authorization/tutorial/views.py
:linenos:
- :emphasize-lines: 8,11-15,17,24,29,48,52,68,72,80,82-120
+ :emphasize-lines: 8,11-15,17,24,29,47,51,66,70,78,80-
:language: python
Only the highlighted lines need to be added or edited.
@@ -323,7 +355,7 @@ we're done:
.. literalinclude:: src/authorization/tutorial/templates/edit.pt
:linenos:
- :emphasize-lines: 36-38
+ :emphasize-lines: 37-39
:language: html
Only the highlighted lines need to be added or edited.
@@ -333,7 +365,7 @@ we're done:
.. literalinclude:: src/authorization/tutorial/templates/view.pt
:linenos:
- :emphasize-lines: 36-38
+ :emphasize-lines: 37-39
:language: html
Only the highlighted lines need to be added or edited.
diff --git a/docs/tutorials/wiki/background.rst b/docs/tutorials/wiki/background.rst
index 31dcd6b53..c583b375c 100644
--- a/docs/tutorials/wiki/background.rst
+++ b/docs/tutorials/wiki/background.rst
@@ -13,7 +13,7 @@ Python web framework experience.
To code along with this tutorial, the developer will need a UNIX
machine with development tools (Mac OS X with XCode, any Linux or BSD
-variant, etc.) *or* a Windows system of any kind.
+variant, and so on) *or* a Windows system of any kind.
.. warning::
diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst
index 20bfdf754..d00eab956 100644
--- a/docs/tutorials/wiki/basiclayout.rst
+++ b/docs/tutorials/wiki/basiclayout.rst
@@ -4,7 +4,7 @@
Basic Layout
============
-The starter files generated by the ``zodb`` scaffold are very basic, but
+The starter files generated by the ``zodb`` cookiecutter are very basic, but
they provide a good orientation for the high-level patterns common to most
:term:`traversal`-based (and :term:`ZODB`-based) :app:`Pyramid` projects.
@@ -44,7 +44,11 @@ Open ``tutorial/__init__.py``. It should already contain the following:
#. *Line 15*. Include support for the :term:`Chameleon` template rendering
bindings, allowing us to use the ``.pt`` templates.
-#. *Line 16*. Register a "static view", which answers requests whose URL
+#. *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_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application.
+
+#. *Line 18*. 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
@@ -54,19 +58,19 @@ Open ``tutorial/__init__.py``. It should already contain the following:
will be ``/static``. The second argument of this tag is the "path",
which is a relative :term:`asset specification`, so it finds the resources
it should serve within the ``static`` directory inside the ``tutorial``
- package. Alternatively the scaffold could have used an *absolute* asset
+ package. Alternatively the cookiecutter could have used an *absolute* asset
specification as the path (``tutorial:static``).
-#. *Line 17*. Perform a :term:`scan`. A scan will find :term:`configuration
+#. *Line 19*. 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
:meth:`~pyramid.config.Configurator.scan`, which implies that the scan
should take place in the current package (in this case, ``tutorial``).
- The scaffold could have equivalently said ``config.scan('tutorial')``, but
+ The cookiecutter could have equivalently said ``config.scan('tutorial')``, but
it chose to omit the package name argument.
-#. *Line 18*. Use the
+#. *Line 20*. Use the
:meth:`pyramid.config.Configurator.make_wsgi_app` method
to return a :term:`WSGI` application.
@@ -79,7 +83,7 @@ hierarchically in a :term:`resource tree`. This tree is consulted by
tree represents the site structure, but it *also* represents the
:term:`domain model` of the application, because each resource is a node
stored persistently in a :term:`ZODB` database. The ``models.py`` file is
-where the ``zodb`` scaffold put the classes that implement our
+where the ``zodb`` cookiecutter put the classes that implement our
resource objects, each of which also happens to be a domain model object.
Here is the source for ``models.py``:
@@ -93,7 +97,7 @@ Here is the source for ``models.py``:
because the class inherits from the
:class:`persistent.mapping.PersistentMapping` class. The ``__parent__``
and ``__name__`` are important parts of the :term:`traversal` protocol.
- By default, have these as ``None`` indicating that this is the
+ 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
@@ -110,7 +114,7 @@ Here is the source for ``models.py``:
Views With ``views.py``
-----------------------
-Our scaffold generated a default ``views.py`` on our behalf. It
+Our cookiecutter generated a default ``views.py`` on our behalf. It
contains a single view, which is used to render the page shown when you visit
the URL ``http://localhost:6543/``.
@@ -156,7 +160,7 @@ Let's try to understand the components in this module:
#. *Lines 6-7*. We define a :term:`view callable` named ``my_view``, which
we decorated in the step above. This view callable is a *function* we
- write generated by the ``zodb`` scaffold that is given a
+ write generated by the ``zodb`` cookiecutter that is given a
``request`` and which returns a dictionary. The ``mytemplate.pt``
:term:`renderer` named by the asset specification in the step above will
convert this dictionary to a :term:`response` on our behalf.
@@ -168,8 +172,8 @@ Let's try to understand the components in this module:
Configuration in ``development.ini``
------------------------------------
-The ``development.ini`` (in the tutorial :term:`project` directory, as
-opposed to the tutorial :term:`package` directory) looks like this:
+The ``development.ini`` (in the ``tutorial`` :term:`project` directory, as
+opposed to the ``tutorial`` :term:`package` directory) looks like this:
.. literalinclude:: src/basiclayout/development.ini
:language: ini
diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst
index 73dce14d5..419fede62 100644
--- a/docs/tutorials/wiki/definingmodels.rst
+++ b/docs/tutorials/wiki/definingmodels.rst
@@ -4,7 +4,7 @@
Defining the Domain Model
=========================
-The first change we'll make to our stock ``pcreate``-generated application will
+The first change we'll make to our stock cookiecutter-generated application will
be to define two :term:`resource` constructors, one representing a wiki page,
and another representing the wiki as a mapping of wiki page names to page
objects. We'll do this inside our ``models.py`` file.
@@ -50,7 +50,9 @@ The first thing we want to do is remove the ``MyModel`` class from the
generated ``models.py`` file. The ``MyModel`` class is only a sample and
we're not going to use it.
-Then, we'll add a ``Wiki`` class. We want it to inherit from the
+Then we'll add an import at the top for the :class:`persistent.Persistent` class. We'll use this for a new ``Page`` class in a moment.
+
+Then we'll add a ``Wiki`` class. We want it to inherit from the
:class:`persistent.mapping.PersistentMapping` class because it provides
mapping behavior, and it makes sure that our Wiki page is stored as a
"first-class" persistent object in our ZODB database.
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index ac94d8059..442d5ed18 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -4,7 +4,7 @@
Defining Views
==============
-A :term:`view callable` in a :term:`traversal` -based :app:`Pyramid`
+A :term:`view callable` in a :term:`traversal`-based :app:`Pyramid`
application is typically a simple Python function that accepts two
parameters: :term:`context` and :term:`request`. A view callable is
assumed to return a :term:`response` object.
@@ -16,7 +16,7 @@ assumed to return a :term:`response` object.
this one-argument pattern used in other :app:`Pyramid` tutorials
and applications. Either calling convention will work in any
:app:`Pyramid` application; the calling conventions can be used
- interchangeably as necessary. In :term:`traversal` based applications,
+ interchangeably as necessary. In :term:`traversal`-based applications,
URLs are mapped to a context :term:`resource`, and since our
:term:`resource tree` also represents our application's
"domain model", we're often interested in the context because
@@ -36,7 +36,7 @@ Declaring Dependencies in Our ``setup.py`` File
The view code in our application will depend on a package which is not a
dependency of the original "tutorial" application. The original "tutorial"
-application was generated by the ``pcreate`` command; it doesn't know
+application was generated by the cookiecutter; it doesn't know
about our custom application requirements.
We need to add a dependency on the ``docutils`` package to our ``tutorial``
@@ -52,6 +52,7 @@ Open ``setup.py`` and edit it to look like the following:
Only the highlighted line needs to be added.
+.. _wiki-running-pip-install:
Running ``pip install -e .``
============================
@@ -74,15 +75,15 @@ On Windows:
.. code-block:: doscon
- c:\pyramidtut> cd tutorial
- c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e .
+ c:\> cd tutorial
+ c:\tutorial> %VENV%\Scripts\pip install -e .
Success executing this command will end with a line to the console something
like:
.. code-block:: text
- Successfully installed docutils-0.12 tutorial-0.0
+ Successfully installed docutils-0.13.1 tutorial
Adding view functions in ``views.py``
@@ -98,11 +99,11 @@ like the following:
We added some imports and created a regular expression to find "WikiWords".
We got rid of the ``my_view`` view function and its decorator that was added
-when we originally rendered the ``zodb`` scaffold. It was only an example and
+when we originally rendered the ``zodb`` cookiecutter. It was only an example and
isn't relevant to our application.
Then we added four :term:`view callable` functions to our ``views.py``
-module:
+module:
* ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL.
* ``view_page()`` - Displays an individual page.
@@ -126,8 +127,7 @@ Following is the code for the ``view_wiki`` view function and its decorator:
.. literalinclude:: src/views/tutorial/views.py
:lines: 12-14
- :lineno-start: 12
- :linenos:
+ :lineno-match:
:language: python
.. note:: In our code, we use an *import* that is *relative* to our package
@@ -165,8 +165,7 @@ Here is the code for the ``view_page`` view function and its decorator:
.. literalinclude:: src/views/tutorial/views.py
:lines: 16-33
- :lineno-start: 16
- :linenos:
+ :lineno-match:
:language: python
The ``view_page`` function is configured to respond as the default view
@@ -219,8 +218,7 @@ Here is the code for the ``add_page`` view function and its decorator:
.. literalinclude:: src/views/tutorial/views.py
:lines: 35-50
- :lineno-start: 35
- :linenos:
+ :lineno-match:
:language: python
The ``add_page`` function is configured to respond when the context resource
@@ -247,7 +245,7 @@ are found *after* the :term:`view name` in the URL segments given in the
add view is invoked via, e.g., ``http://localhost:6543/add_page/SomeName``,
the :term:`subpath` will be a tuple: ``('SomeName',)``.
-The add view takes the zeroth element of the subpath (the wiki page name),
+The add view takes the zero\ :sup:`th` element of the subpath (the wiki page name),
and aliases it to the name attribute in order to know the name of the page
we're trying to add.
@@ -274,8 +272,7 @@ Here is the code for the ``edit_page`` view function and its decorator:
.. literalinclude:: src/views/tutorial/views.py
:lines: 52-60
- :lineno-start: 52
- :linenos:
+ :lineno-match:
:language: python
The ``edit_page`` function is configured to respond when the context is
@@ -316,26 +313,26 @@ extension to be recognized as such.
The ``view.pt`` template
------------------------
-Create ``tutorial/templates/view.pt`` and add the following
-content:
+Rename ``tutorial/templates/mytemplate.pt`` to ``tutorial/templates/view.pt`` and edit the emphasized lines to look like the following:
.. literalinclude:: src/views/tutorial/templates/view.pt
:linenos:
:language: html
+ :emphasize-lines: 11-12,37-52
This template is used by ``view_page()`` for displaying a single
wiki page. It includes:
- A ``div`` element that is replaced with the ``content`` value provided by
- the view (lines 36-38). ``content`` contains HTML, so the ``structure``
+ the view (lines 37-39). ``content`` contains HTML, so the ``structure``
keyword is used to prevent escaping it (i.e., changing ">" to "&gt;", etc.)
- A link that points at the "edit" URL which invokes the ``edit_page`` view
- for the page being viewed (lines 40-42).
+ for the page being viewed (lines 41-43).
The ``edit.pt`` template
------------------------
-Create ``tutorial/templates/edit.pt`` and add the following content:
+Copy ``tutorial/templates/view.pt`` to ``tutorial/templates/edit.pt`` and edit the emphasized lines to look like the following:
.. literalinclude:: src/views/tutorial/templates/edit.pt
:linenos:
@@ -344,12 +341,12 @@ Create ``tutorial/templates/edit.pt`` and add the following content:
This template is used by ``add_page()`` and ``edit_page()`` for adding and
editing a wiki page. It displays a page containing a form that includes:
-- A 10 row by 60 column ``textarea`` field named ``body`` that is filled
- with any existing page data when it is rendered (line 45).
-- A submit button that has the name ``form.submitted`` (line 48).
+- A 10-row by 60-column ``textarea`` field named ``body`` that is filled
+ with any existing page data when it is rendered (line 46).
+- A submit button that has the name ``form.submitted`` (line 49).
The form POSTs back to the ``save_url`` argument supplied by the view (line
-43). The view will use the ``body`` and ``form.submitted`` values.
+44). The view will use the ``body`` and ``form.submitted`` values.
.. note:: Our templates use a ``request`` object that none of our tutorial
views return in their dictionary. ``request`` is one of several names that
@@ -394,5 +391,5 @@ each of the following URLs, checking that the result is as expected:
- http://localhost:6543/add_page/SomePageName invokes the add view for a Page.
- To generate an error, visit http://localhost:6543/add_page which will
- generate an ``IndexErrorr: tuple index out of range`` error. You'll see an
+ generate an ``IndexError: tuple index out of range`` error. You'll see an
interactive traceback facility provided by :term:`pyramid_debugtoolbar`.
diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst
index f2a02176b..30d443bb8 100644
--- a/docs/tutorials/wiki/design.rst
+++ b/docs/tutorials/wiki/design.rst
@@ -43,11 +43,8 @@ editing, and viewing wiki pages, plus one view for the wiki front page.
Two templates will be used, one for viewing, and one for both adding
and editing wiki pages.
-The default templating systems in :app:`Pyramid` are
-:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of
-:term:`ZPT`, which is an XML-based templating language. Mako is a
-non-XML-based templating language. Because we had to pick one,
-we chose Chameleon for this tutorial.
+As of version 1.5 :app:`Pyramid` no longer ships with templating systems. In this tutorial, we will use :term:`Chameleon`. Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language.
+
Security
--------
diff --git a/docs/tutorials/wiki/distributing.rst b/docs/tutorials/wiki/distributing.rst
index 386b880e6..fb0a552e0 100644
--- a/docs/tutorials/wiki/distributing.rst
+++ b/docs/tutorials/wiki/distributing.rst
@@ -6,9 +6,8 @@ Distributing Your Application
Once your application works properly, you can create a "tarball" from it by
using the ``setup.py sdist`` command. The following commands assume your
-current working directory is the ``tutorial`` package we've created and that
-the parent directory of the ``tutorial`` package is a virtual environment
-representing a :app:`Pyramid` environment.
+current working directory contains the ``tutorial`` package and the
+``setup.py`` file.
On UNIX:
@@ -20,7 +19,7 @@ On Windows:
.. code-block:: doscon
- c:\pyramidtut> %VENV%\Scripts\python setup.py sdist
+ c:\tutorial> %VENV%\Scripts\python setup.py sdist
The output of such a command will be something like:
@@ -35,7 +34,7 @@ The output of such a command will be something like:
Note that this command creates a tarball in the "dist" subdirectory named
``tutorial-0.0.tar.gz``. You can send this file to your friends to show them
your cool new application. They should be able to install it by pointing the
-``pip install .`` command directly at it. Or you can upload it to `PyPI
+``pip install`` command directly at it. Or you can upload it to `PyPI
<https://pypi.python.org/pypi>`_ and share it with the rest of the world, where
it can be downloaded via ``pip install`` remotely like any other package people
download from PyPI.
diff --git a/docs/tutorials/wiki/index.rst b/docs/tutorials/wiki/index.rst
index 7808c7623..7bd58656b 100644
--- a/docs/tutorials/wiki/index.rst
+++ b/docs/tutorials/wiki/index.rst
@@ -5,13 +5,12 @@ ZODB + Traversal Wiki Tutorial
This tutorial introduces a :term:`ZODB` and :term:`traversal`-based
:app:`Pyramid` application to a developer familiar with Python. It will be
-most familiar to developers with previous :term:`Zope` experience. When the
-is finished, the developer will have created a basic Wiki application with
+most familiar to developers with previous :term:`Zope` experience. When
+finished, the developer will have created a basic Wiki application with
authentication.
For cut and paste purposes, the source code for all stages of this
-tutorial can be browsed on GitHub at `docs/tutorials/wiki/src
-<https://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src>`_,
+tutorial can be browsed on GitHub at `GitHub <https://github.com/Pylons/pyramid/>`_ for a specific branch or version under ``docs/tutorials/wiki/src``,
which corresponds to the same location if you have Pyramid sources.
.. toctree::
diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst
index 6172b122b..de057b1cc 100644
--- a/docs/tutorials/wiki/installation.rst
+++ b/docs/tutorials/wiki/installation.rst
@@ -15,159 +15,125 @@ install Pyramid**. Thereby you will satisfy the following requirements.
* You've satisfied the :ref:`requirements-for-installing-packages`.
-Create directory to contain the project
----------------------------------------
-
-We need a workspace for our project files.
-
-On UNIX
-^^^^^^^
-
-.. code-block:: bash
+Install cookiecutter
+--------------------
+We will use a :term:`cookiecutter` to create a Python package project from a Python package project template. See `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ for instructions.
- $ mkdir ~/pyramidtut
-On Windows
-^^^^^^^^^^
-
-.. code-block:: doscon
-
- c:\> mkdir pyramidtut
-
-
-Create and use a virtual Python environment
--------------------------------------------
+Generate a Pyramid project from a cookiecutter
+----------------------------------------------
-Next let's create a virtual environment workspace for our project. We will use
-the ``VENV`` environment variable instead of the absolute path of the virtual
-environment.
+We will create a Pyramid project in your home directory for UNIX or at the root for Windows. It is assumed you know the path to where you installed ``cookiecutter``. Issue the following commands and override the defaults in the prompts as follows.
On UNIX
^^^^^^^
.. code-block:: bash
- $ export VENV=~/pyramidtut
- $ python3 -m venv $VENV
+ $ cd ~
+ $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-zodb
On Windows
^^^^^^^^^^
.. code-block:: doscon
- c:\> set VENV=c:\pyramidtut
+ c:\> cd \
+ c:\> cookiecutter https://github.com/Pylons/pyramid-cookiecutter-zodb
-Each version of Python uses different paths, so you will need to adjust the
-path to the command for your Python version.
+On all operating systems
+^^^^^^^^^^^^^^^^^^^^^^^^
+If prompted for the first item, accept the default ``yes`` by hitting return.
-Python 2.7:
-
-.. code-block:: doscon
-
- c:\> c:\Python27\Scripts\virtualenv %VENV%
-
-Python 3.5:
-
-.. code-block:: doscon
-
- c:\> c:\Python35\Scripts\python -m venv %VENV%
+.. 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
-Upgrade ``pip`` and ``setuptools`` in the virtual environment
--------------------------------------------------------------
+Change directory into your newly created project
+------------------------------------------------
On UNIX
^^^^^^^
.. code-block:: bash
- $ $VENV/bin/pip install --upgrade pip setuptools
+ $ cd tutorial
On Windows
^^^^^^^^^^
.. code-block:: doscon
- c:\> %VENV%\Scripts\pip install --upgrade pip setuptools
+ c:\> cd tutorial
-Install Pyramid into the virtual Python environment
----------------------------------------------------
+Set and use a ``VENV`` environment variable
+-------------------------------------------
+
+We will set the ``VENV`` environment variable to the absolute path of the virtual environment, and use it going forward.
On UNIX
^^^^^^^
-.. parsed-literal::
+.. code-block:: bash
- $ $VENV/bin/pip install "pyramid==\ |release|\ "
+ $ export VENV=~/tutorial
On Windows
^^^^^^^^^^
-.. parsed-literal::
-
- c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ "
+.. code-block:: doscon
+ c:\tutorial> set VENV=c:\tutorial
-Change directory to your virtual Python environment
----------------------------------------------------
-Change directory to the ``pyramidtut`` directory, which is both your workspace
-and your virtual environment.
+Create a virtual environment
+----------------------------
On UNIX
^^^^^^^
.. code-block:: bash
- $ cd pyramidtut
+ $ python3 -m venv $VENV
On Windows
^^^^^^^^^^
-.. code-block:: doscon
+Each version of Python uses different paths, so you might need to adjust the path to the command for your Python version. Recent versions of the Python 3 installer for Windows now install a Python launcher.
- c:\> cd pyramidtut
+Python 2.7:
+.. code-block:: doscon
-.. _making_a_project:
+ c:\tutorial> c:\Python27\Scripts\virtualenv %VENV%
-Making a project
-----------------
+Python 3.6:
-Your next step is to create a project. For this tutorial, we will use
-the :term:`scaffold` named ``zodb``, which generates an application
-that uses :term:`ZODB` and :term:`traversal`.
+.. code-block:: doscon
-:app:`Pyramid` supplies a variety of scaffolds to generate sample projects. We
-will use ``pcreate``, a script that comes with Pyramid, to create our project
-using a scaffold.
+ c:\tutorial> python -m venv %VENV%
-By passing ``zodb`` into the ``pcreate`` command, the script creates the files
-needed to use ZODB. By passing in our application name ``tutorial``, the script
-inserts that application name into all the required files.
-The below instructions assume your current working directory is "pyramidtut".
+Upgrade packaging tools in the virtual environment
+--------------------------------------------------
On UNIX
^^^^^^^
.. code-block:: bash
- $ $VENV/bin/pcreate -s zodb tutorial
+ $ $VENV/bin/pip install --upgrade pip setuptools
On Windows
^^^^^^^^^^
.. code-block:: doscon
- c:\pyramidtut> %VENV%\Scripts\pcreate -s zodb tutorial
-
-.. note:: If you are using Windows, the ``zodb`` scaffold may not deal
- gracefully with installation into a location that contains spaces in the
- path. If you experience startup problems, try putting both the virtual
- environment and the project into directories that do not contain spaces in
- their paths.
+ c:\tutorial> %VENV%\Scripts\pip install --upgrade pip setuptools
.. _installing_project_in_dev_mode_zodb:
@@ -175,76 +141,51 @@ On Windows
Installing the project in development mode
------------------------------------------
-In order to do development on the project easily, you must "register" the
-project as a development egg in your workspace using the ``pip install -e .``
-command. In order to do so, change directory to the ``tutorial`` directory that
-you created in :ref:`making_a_project`, and run the ``pip install -e .``
-command using the virtual environment Python interpreter.
+In order to do development on the project easily, you must "register" the project as a development egg in your workspace. We will install testing requirements at the same time. We do so with the following command.
On UNIX
^^^^^^^
.. code-block:: bash
- $ cd tutorial
- $ $VENV/bin/pip install -e .
+ $ $VENV/bin/pip install -e ".[testing]"
On Windows
^^^^^^^^^^
.. code-block:: doscon
- c:\pyramidtut> cd tutorial
- c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e .
-
-The console will show ``pip`` checking for packages and installing missing
-packages. Success executing this command will show a line like the following:
-
-.. code-block:: bash
+ c:\tutorial> %VENV%\Scripts\pip install -e ".[testing]"
- Successfully installed BTrees-4.2.0 Chameleon-2.24 Mako-1.0.4 \
- MarkupSafe-0.23 Pygments-2.1.3 ZConfig-3.1.0 ZEO-4.2.0b1 ZODB-4.2.0 \
- ZODB3-3.11.0 mock-2.0.0 pbr-1.8.1 persistent-4.1.1 pyramid-chameleon-0.3 \
- pyramid-debugtoolbar-2.4.2 pyramid-mako-1.0.2 pyramid-tm-0.12.1 \
- pyramid-zodbconn-0.7 six-1.10.0 transaction-1.4.4 tutorial waitress-0.8.10 \
- zc.lockfile-1.1.0 zdaemon-4.1.0 zodbpickle-0.6.0 zodburi-2.0
+On all operating systems
+^^^^^^^^^^^^^^^^^^^^^^^^
+The console will show ``pip`` checking for packages and installing missing packages. Success executing this command will show a line like the following:
-.. _install-testing-requirements-zodb:
+.. code-block:: bash
-Install testing requirements
-----------------------------
+ Successfully installed BTrees-4.3.1 Chameleon-3.0 Mako-1.0.6 \
+ MarkupSafe-0.23 PasteDeploy-1.5.2 Pygments-2.1.3 WebOb-1.6.3 \
+ WebTest-2.0.23 ZConfig-3.1.0 ZEO-5.0.4 ZODB-5.1.1 ZODB3-3.11.0 \
+ beautifulsoup4-4.5.1 coverage-4.2 mock-2.0.0 pbr-1.10.0 persistent-4.2.2 \
+ py-1.4.31 pyramid-1.7.3 pyramid-chameleon-0.3 pyramid-debugtoolbar-3.0.5 \
+ pyramid-mako-1.0.2 pyramid-tm-1.1.1 pyramid-zodbconn-0.7 pytest-3.0.5 \
+ pytest-cov-2.4.0 repoze.lru-0.6 six-1.10.0 transaction-2.0.3 \
+ translationstring-1.3 tutorial venusian-1.0 waitress-1.0.1 \
+ zc.lockfile-1.2.1 zdaemon-4.2.0 zodbpickle-0.6.0 zodburi-2.0 \
+ zope.deprecation-4.2.0 zope.interface-4.3.3
-In order to run tests, we need to install the testing requirements. This is
-done through our project's ``setup.py`` file, in the ``tests_require`` and
-``extras_require`` stanzas, and by issuing the command below for your
-operating system.
+Testing requirements are defined in our project's ``setup.py`` file, in the ``tests_require`` and ``extras_require`` stanzas.
.. literalinclude:: src/installation/setup.py
- :language: python
- :linenos:
- :lineno-start: 22
- :lines: 22-26
+ :language: python
+ :lineno-match:
+ :lines: 22-26
.. literalinclude:: src/installation/setup.py
- :language: python
- :linenos:
- :lineno-start: 45
- :lines: 45-47
-
-On UNIX
-^^^^^^^
-
-.. code-block:: bash
-
- $ $VENV/bin/pip install -e ".[testing]"
-
-On Windows
-^^^^^^^^^^
-
-.. code-block:: doscon
-
- c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e ".[testing]"
+ :language: python
+ :lineno-match:
+ :lines: 46-48
.. _running_tests:
@@ -269,7 +210,7 @@ On Windows
.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q
+ c:\tutorial> %VENV%\Scripts\py.test -q
For a successful test run, you should see output that ends like this:
@@ -284,7 +225,7 @@ Expose test coverage information
You can run the ``py.test`` command to see test coverage information. This
runs the tests in the same way that ``py.test`` does, but provides additional
-"coverage" information, exposing which lines of your project are covered by the
+:term:`coverage` information, exposing which lines of your project are covered by the
tests.
We've already installed the ``pytest-cov`` package into our virtual
@@ -302,41 +243,39 @@ On Windows
.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov \
- --cov-report=term-missing
+ c:\tutorial> %VENV%\Scripts\py.test --cov --cov-report=term-missing
If successful, you will see output something like this:
.. code-block:: bash
- ======================== test session starts ========================
- platform Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
- rootdir: /Users/stevepiercy/projects/pyramidtut/tutorial, inifile:
- plugins: cov-2.2.1
- collected 1 items
-
- tutorial/tests.py .
- ------------------ coverage: platform Python 3.5.1 ------------------
- Name Stmts Miss Cover Missing
- ----------------------------------------------------
- tutorial/__init__.py 12 7 42% 7-8, 14-18
- tutorial/models.py 10 6 40% 9-14
- tutorial/tests.py 12 0 100%
- tutorial/views.py 4 0 100%
- ----------------------------------------------------
- TOTAL 38 13 66%
-
- ===================== 1 passed in 0.31 seconds ======================
+ ======================== test session starts ========================
+ platform Python 3.6.0, pytest-3.0.5, py-1.4.31, pluggy-0.4.0
+ rootdir: /Users/stevepiercy/tutorial, inifile:
+ plugins: cov-2.4.0
+ collected 1 items
+
+ tutorial/tests.py .
+ ------------------ coverage: platform Python 3.6.0 ------------------
+ Name Stmts Miss Cover Missing
+ -------------------------------------------------------
+ tutorial/__init__.py 14 9 36% 7-8, 14-20
+ tutorial/models.py 10 6 40% 9-14
+ tutorial/views.py 4 0 100%
+ -------------------------------------------------------
+ TOTAL 28 15 46%
+
+ ===================== 1 passed in 0.31 seconds ======================
Our package doesn't quite have 100% test coverage.
-.. _test_and_coverage_scaffold_defaults_zodb:
+.. _test_and_coverage_cookiecutter_defaults_zodb:
-Test and coverage scaffold defaults
------------------------------------
+Test and coverage cookiecutter defaults
+---------------------------------------
-Scaffolds include configuration defaults for ``py.test`` and test coverage.
+Cookiecutters include configuration defaults for ``py.test`` and test coverage.
These configuration files are ``pytest.ini`` and ``.coveragerc``, located at
the root of your package. Without these defaults, we would need to specify the
path to the module on which we want to run tests and coverage.
@@ -353,11 +292,10 @@ On Windows
.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov=tutorial \
- --cov-report=term-missing tutorial\tests.py -q
+ c:\tutorial> %VENV%\Scripts\py.test --cov=tutorial tutorial\tests.py -q
py.test follows :ref:`conventions for Python test discovery
-<pytest:test discovery>`, and the configuration defaults from the scaffold
+<pytest:test discovery>`, and the configuration defaults from the cookiecutter
tell ``py.test`` where to find the module on which we want to run tests and
coverage.
@@ -370,7 +308,8 @@ coverage.
Start the application
---------------------
-Start the application.
+Start the application. See :ref:`what_is_this_pserve_thing` for more
+information on ``pserve``.
On UNIX
^^^^^^^
@@ -384,7 +323,7 @@ On Windows
.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\pserve development.ini --reload
+ c:\tutorial> %VENV%\Scripts\pserve development.ini --reload
.. note::
@@ -395,9 +334,10 @@ If successful, you will see something like this on your console:
.. code-block:: text
- Starting subprocess with file monitor
- Starting server in PID 82349.
- serving on http://127.0.0.1:6543
+ Starting subprocess with file monitor
+ Starting server in PID 44078.
+ Serving on http://localhost:6543
+ Serving on http://localhost:6543
This means the server is ready to accept requests.
@@ -414,13 +354,13 @@ page. You can read more about the purpose of the icon at
application while you develop.
-Decisions the ``zodb`` scaffold has made for you
-------------------------------------------------
+Decisions the ``zodb`` cookiecutter has made for you
+----------------------------------------------------
-Creating a project using the ``zodb`` scaffold makes the following
+Creating a project using the ``zodb`` cookiecutter makes the following
assumptions:
-- You are willing to use :term:`ZODB` as persistent storage.
+- You are willing to use :term:`ZODB` for persistent storage.
- You are willing to use :term:`traversal` to map URLs to code.
diff --git a/docs/tutorials/wiki/src/authorization/.coveragerc b/docs/tutorials/wiki/src/authorization/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki/src/authorization/CHANGES.txt b/docs/tutorials/wiki/src/authorization/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki/src/authorization/CHANGES.txt
+++ b/docs/tutorials/wiki/src/authorization/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki/src/authorization/README.txt b/docs/tutorials/wiki/src/authorization/README.txt
index dcb3605b8..5ec53bf9d 100644
--- a/docs/tutorials/wiki/src/authorization/README.txt
+++ b/docs/tutorials/wiki/src/authorization/README.txt
@@ -1,12 +1,29 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/pserve development.ini
+- Create a Python virtual environment.
+ python3 -m venv env
+
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini
index 6bf4b198e..74e7457d6 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,10 +13,7 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_zodbconn
- pyramid_tm
-tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
# By default, the toolbar only appears for clients from IP addresses
@@ -29,12 +26,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 4e9892e7b..60b6fe253 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -11,11 +11,7 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
- pyramid_tm
- pyramid_zodbconn
-tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
###
@@ -24,12 +20,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/authorization/pytest.ini b/docs/tutorials/wiki/src/authorization/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py
index beeed75c9..4a9f041e3 100644
--- a/docs/tutorials/wiki/src/authorization/setup.py
+++ b/docs/tutorials/wiki/src/authorization/setup.py
@@ -18,37 +18,40 @@ requires = [
'ZODB3',
'waitress',
'docutils',
- ]
+ 'bcrypt',
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ },
+)
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
index 39b94abd1..8af2ee5c0 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
@@ -22,6 +22,8 @@ def main(global_config, **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()
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/security.py b/docs/tutorials/wiki/src/authorization/tutorial/security.py
index d88c9c71f..cbb3acd5d 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/security.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/security.py
@@ -1,5 +1,18 @@
-USERS = {'editor':'editor',
- 'viewer':'viewer'}
+import bcrypt
+
+
+def hash_password(pw):
+ hashed_pw = bcrypt.hashpw(pw.encode('utf-8'), bcrypt.gensalt())
+ # return unicode instead of bytes because databases handle it better
+ return hashed_pw.decode('utf-8')
+
+def check_password(expected_hash, pw):
+ if expected_hash is not None:
+ return bcrypt.checkpw(pw.encode('utf-8'), expected_hash.encode('utf-8'))
+ return False
+
+USERS = {'editor': hash_password('editor'),
+ 'viewer': hash_password('viewer')}
GROUPS = {'editor':['group:editors']}
def groupfinder(userid, request):
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
index 823fa8972..19adc5932 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
@@ -23,6 +23,7 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
index 4a938e9bb..02f7038fe 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
@@ -23,6 +23,7 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
index fa35d758d..17a715b50 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
@@ -23,6 +23,7 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/tests.py b/docs/tutorials/wiki/src/authorization/tutorial/tests.py
index 40f3c47af..ca7a47279 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/tests.py
@@ -14,4 +14,4 @@ class ViewTests(unittest.TestCase):
from .views import my_view
request = testing.DummyRequest()
info = my_view(request)
- self.assertEqual(info['project'], 'tutorial')
+ self.assertEqual(info['project'], 'myproj')
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py
index c271d2cc1..ea2da01af 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py
@@ -14,7 +14,7 @@ from pyramid.security import (
)
-from .security import USERS
+from .security import USERS, check_password
from .models import Page
# regular expression used to find WikiWords
@@ -43,7 +43,6 @@ def view_page(context, request):
content = publish_parts(context.data, writer_name='html')['html_body']
content = wikiwords.sub(check, content)
edit_url = request.resource_url(context, 'edit_page')
-
return dict(page=context, content=content, edit_url=edit_url,
logged_in=request.authenticated_userid)
@@ -63,7 +62,6 @@ def add_page(context, request):
page = Page('')
page.__name__ = pagename
page.__parent__ = context
-
return dict(page=page, save_url=save_url,
logged_in=request.authenticated_userid)
@@ -94,7 +92,7 @@ def login(request):
if 'form.submitted' in request.params:
login = request.params['login']
password = request.params['password']
- if USERS.get(login) == password:
+ if check_password(USERS.get(login), password):
headers = remember(request, login)
return HTTPFound(location=came_from,
headers=headers)
diff --git a/docs/tutorials/wiki/src/basiclayout/.coveragerc b/docs/tutorials/wiki/src/basiclayout/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki/src/basiclayout/CHANGES.txt b/docs/tutorials/wiki/src/basiclayout/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki/src/basiclayout/CHANGES.txt
+++ b/docs/tutorials/wiki/src/basiclayout/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki/src/basiclayout/README.txt b/docs/tutorials/wiki/src/basiclayout/README.txt
index dcb3605b8..5ec53bf9d 100644
--- a/docs/tutorials/wiki/src/basiclayout/README.txt
+++ b/docs/tutorials/wiki/src/basiclayout/README.txt
@@ -1,12 +1,29 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/pserve development.ini
+- Create a Python virtual environment.
+ python3 -m venv env
+
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini
index 6bf4b198e..74e7457d6 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,10 +13,7 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_zodbconn
- pyramid_tm
-tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
# By default, the toolbar only appears for clients from IP addresses
@@ -29,12 +26,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 4e9892e7b..60b6fe253 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -11,11 +11,7 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
- pyramid_tm
- pyramid_zodbconn
-tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
###
@@ -24,12 +20,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/basiclayout/pytest.ini b/docs/tutorials/wiki/src/basiclayout/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py
index 46b395568..5d1e9c7b5 100644
--- a/docs/tutorials/wiki/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki/src/basiclayout/setup.py
@@ -17,37 +17,39 @@ requires = [
'transaction',
'ZODB3',
'waitress',
- ]
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ },
+)
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
index f2a86df47..728f7ac02 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
@@ -13,6 +13,8 @@ def main(global_config, **settings):
"""
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()
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
index f8cbe2e2c..3ac122711 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
@@ -8,7 +8,7 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
- <title>ZODB Scaffold for The Pyramid Web Framework</title>
+ <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">
@@ -33,18 +33,16 @@
</div>
<div class="col-md-10">
<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB Project</span></h1>
+ <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>
</div>
</div>
<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><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-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>
</ul>
</div>
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
index 40f3c47af..ca7a47279 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
@@ -14,4 +14,4 @@ class ViewTests(unittest.TestCase):
from .views import my_view
request = testing.DummyRequest()
info = my_view(request)
- self.assertEqual(info['project'], 'tutorial')
+ self.assertEqual(info['project'], 'myproj')
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py
index 628ce15ed..c1878bdd0 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py
@@ -4,4 +4,4 @@ from .models import MyModel
@view_config(context=MyModel, renderer='templates/mytemplate.pt')
def my_view(request):
- return {'project': 'tutorial'}
+ return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/installation/.coveragerc b/docs/tutorials/wiki/src/installation/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki/src/installation/CHANGES.txt b/docs/tutorials/wiki/src/installation/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki/src/installation/CHANGES.txt
+++ b/docs/tutorials/wiki/src/installation/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki/src/installation/README.txt b/docs/tutorials/wiki/src/installation/README.txt
index dcb3605b8..5ec53bf9d 100644
--- a/docs/tutorials/wiki/src/installation/README.txt
+++ b/docs/tutorials/wiki/src/installation/README.txt
@@ -1,12 +1,29 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/pserve development.ini
+- Create a Python virtual environment.
+ python3 -m venv env
+
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/installation/development.ini b/docs/tutorials/wiki/src/installation/development.ini
index 6bf4b198e..74e7457d6 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,10 +13,7 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_zodbconn
- pyramid_tm
-tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
# By default, the toolbar only appears for clients from IP addresses
@@ -29,12 +26,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 4e9892e7b..60b6fe253 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -11,11 +11,7 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
- pyramid_tm
- pyramid_zodbconn
-tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
###
@@ -24,12 +20,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/installation/pytest.ini b/docs/tutorials/wiki/src/installation/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki/src/installation/setup.py b/docs/tutorials/wiki/src/installation/setup.py
index 46b395568..5d1e9c7b5 100644
--- a/docs/tutorials/wiki/src/installation/setup.py
+++ b/docs/tutorials/wiki/src/installation/setup.py
@@ -17,37 +17,39 @@ requires = [
'transaction',
'ZODB3',
'waitress',
- ]
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ },
+)
diff --git a/docs/tutorials/wiki/src/installation/tutorial/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
index f2a86df47..728f7ac02 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
@@ -13,6 +13,8 @@ def main(global_config, **settings):
"""
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()
diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
index f8cbe2e2c..3ac122711 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
@@ -8,7 +8,7 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
- <title>ZODB Scaffold for The Pyramid Web Framework</title>
+ <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">
@@ -33,18 +33,16 @@
</div>
<div class="col-md-10">
<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB Project</span></h1>
+ <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>
</div>
</div>
<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><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-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>
</ul>
</div>
diff --git a/docs/tutorials/wiki/src/installation/tutorial/tests.py b/docs/tutorials/wiki/src/installation/tutorial/tests.py
index 40f3c47af..ca7a47279 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/tests.py
@@ -14,4 +14,4 @@ class ViewTests(unittest.TestCase):
from .views import my_view
request = testing.DummyRequest()
info = my_view(request)
- self.assertEqual(info['project'], 'tutorial')
+ self.assertEqual(info['project'], 'myproj')
diff --git a/docs/tutorials/wiki/src/installation/tutorial/views.py b/docs/tutorials/wiki/src/installation/tutorial/views.py
index 628ce15ed..c1878bdd0 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/views.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/views.py
@@ -4,4 +4,4 @@ from .models import MyModel
@view_config(context=MyModel, renderer='templates/mytemplate.pt')
def my_view(request):
- return {'project': 'tutorial'}
+ return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/models/.coveragerc b/docs/tutorials/wiki/src/models/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki/src/models/CHANGES.txt b/docs/tutorials/wiki/src/models/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki/src/models/CHANGES.txt
+++ b/docs/tutorials/wiki/src/models/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki/src/models/README.txt b/docs/tutorials/wiki/src/models/README.txt
index dcb3605b8..5ec53bf9d 100644
--- a/docs/tutorials/wiki/src/models/README.txt
+++ b/docs/tutorials/wiki/src/models/README.txt
@@ -1,12 +1,29 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/pserve development.ini
+- Create a Python virtual environment.
+ python3 -m venv env
+
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini
index 6bf4b198e..74e7457d6 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,10 +13,7 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_zodbconn
- pyramid_tm
-tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
# By default, the toolbar only appears for clients from IP addresses
@@ -29,12 +26,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 4e9892e7b..60b6fe253 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -11,11 +11,7 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
- pyramid_tm
- pyramid_zodbconn
-tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
###
@@ -24,12 +20,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/models/pytest.ini b/docs/tutorials/wiki/src/models/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py
index 46b395568..5d1e9c7b5 100644
--- a/docs/tutorials/wiki/src/models/setup.py
+++ b/docs/tutorials/wiki/src/models/setup.py
@@ -17,37 +17,39 @@ requires = [
'transaction',
'ZODB3',
'waitress',
- ]
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ },
+)
diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py
index f2a86df47..728f7ac02 100644
--- a/docs/tutorials/wiki/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py
@@ -13,6 +13,8 @@ def main(global_config, **settings):
"""
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()
diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
index f8cbe2e2c..3ac122711 100644
--- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
@@ -8,7 +8,7 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
- <title>ZODB Scaffold for The Pyramid Web Framework</title>
+ <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">
@@ -33,18 +33,16 @@
</div>
<div class="col-md-10">
<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB Project</span></h1>
+ <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>
</div>
</div>
<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><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-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>
</ul>
</div>
diff --git a/docs/tutorials/wiki/src/models/tutorial/tests.py b/docs/tutorials/wiki/src/models/tutorial/tests.py
index 40f3c47af..ca7a47279 100644
--- a/docs/tutorials/wiki/src/models/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/models/tutorial/tests.py
@@ -14,4 +14,4 @@ class ViewTests(unittest.TestCase):
from .views import my_view
request = testing.DummyRequest()
info = my_view(request)
- self.assertEqual(info['project'], 'tutorial')
+ self.assertEqual(info['project'], 'myproj')
diff --git a/docs/tutorials/wiki/src/models/tutorial/views.py b/docs/tutorials/wiki/src/models/tutorial/views.py
index 628ce15ed..c1878bdd0 100644
--- a/docs/tutorials/wiki/src/models/tutorial/views.py
+++ b/docs/tutorials/wiki/src/models/tutorial/views.py
@@ -4,4 +4,4 @@ from .models import MyModel
@view_config(context=MyModel, renderer='templates/mytemplate.pt')
def my_view(request):
- return {'project': 'tutorial'}
+ return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/tests/.coveragerc b/docs/tutorials/wiki/src/tests/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki/src/tests/CHANGES.txt b/docs/tutorials/wiki/src/tests/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki/src/tests/CHANGES.txt
+++ b/docs/tutorials/wiki/src/tests/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki/src/tests/README.txt b/docs/tutorials/wiki/src/tests/README.txt
index dcb3605b8..5ec53bf9d 100644
--- a/docs/tutorials/wiki/src/tests/README.txt
+++ b/docs/tutorials/wiki/src/tests/README.txt
@@ -1,12 +1,29 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/pserve development.ini
+- Create a Python virtual environment.
+ python3 -m venv env
+
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/tests/development.ini b/docs/tutorials/wiki/src/tests/development.ini
index 6bf4b198e..74e7457d6 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,10 +13,7 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_zodbconn
- pyramid_tm
-tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
# By default, the toolbar only appears for clients from IP addresses
@@ -29,12 +26,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 4e9892e7b..60b6fe253 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -11,11 +11,7 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
- pyramid_tm
- pyramid_zodbconn
-tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
###
@@ -24,12 +20,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/tests/pytest.ini b/docs/tutorials/wiki/src/tests/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py
index beeed75c9..4a9f041e3 100644
--- a/docs/tutorials/wiki/src/tests/setup.py
+++ b/docs/tutorials/wiki/src/tests/setup.py
@@ -18,37 +18,40 @@ requires = [
'ZODB3',
'waitress',
'docutils',
- ]
+ 'bcrypt',
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ },
+)
diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
index 39b94abd1..8af2ee5c0 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
@@ -22,6 +22,8 @@ def main(global_config, **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()
diff --git a/docs/tutorials/wiki/src/tests/tutorial/security.py b/docs/tutorials/wiki/src/tests/tutorial/security.py
index d88c9c71f..cbb3acd5d 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/security.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/security.py
@@ -1,5 +1,18 @@
-USERS = {'editor':'editor',
- 'viewer':'viewer'}
+import bcrypt
+
+
+def hash_password(pw):
+ hashed_pw = bcrypt.hashpw(pw.encode('utf-8'), bcrypt.gensalt())
+ # return unicode instead of bytes because databases handle it better
+ return hashed_pw.decode('utf-8')
+
+def check_password(expected_hash, pw):
+ if expected_hash is not None:
+ return bcrypt.checkpw(pw.encode('utf-8'), expected_hash.encode('utf-8'))
+ return False
+
+USERS = {'editor': hash_password('editor'),
+ 'viewer': hash_password('viewer')}
GROUPS = {'editor':['group:editors']}
def groupfinder(userid, request):
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
index 823fa8972..19adc5932 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
@@ -23,6 +23,7 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
index 4a938e9bb..02f7038fe 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
@@ -23,6 +23,7 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
index fa35d758d..17a715b50 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
@@ -23,6 +23,7 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py
index 04beaea44..098e9c1bd 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py
@@ -122,6 +122,17 @@ class EditPageTests(unittest.TestCase):
self.assertEqual(response.location, 'http://example.com/')
self.assertEqual(context.data, 'Hello yo!')
+class SecurityTests(unittest.TestCase):
+ def test_hashing(self):
+ from .security import hash_password, check_password
+ password = 'secretpassword'
+ hashed_password = hash_password(password)
+ self.assertTrue(check_password(hashed_password, password))
+
+ self.assertFalse(check_password(hashed_password, 'attackerpassword'))
+
+ self.assertFalse(check_password(None, password))
+
class FunctionalTests(unittest.TestCase):
viewer_login = '/login?login=viewer&password=viewer' \
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views.py
index c271d2cc1..ea2da01af 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/views.py
@@ -14,7 +14,7 @@ from pyramid.security import (
)
-from .security import USERS
+from .security import USERS, check_password
from .models import Page
# regular expression used to find WikiWords
@@ -43,7 +43,6 @@ def view_page(context, request):
content = publish_parts(context.data, writer_name='html')['html_body']
content = wikiwords.sub(check, content)
edit_url = request.resource_url(context, 'edit_page')
-
return dict(page=context, content=content, edit_url=edit_url,
logged_in=request.authenticated_userid)
@@ -63,7 +62,6 @@ def add_page(context, request):
page = Page('')
page.__name__ = pagename
page.__parent__ = context
-
return dict(page=page, save_url=save_url,
logged_in=request.authenticated_userid)
@@ -94,7 +92,7 @@ def login(request):
if 'form.submitted' in request.params:
login = request.params['login']
password = request.params['password']
- if USERS.get(login) == password:
+ if check_password(USERS.get(login), password):
headers = remember(request, login)
return HTTPFound(location=came_from,
headers=headers)
diff --git a/docs/tutorials/wiki/src/views/.coveragerc b/docs/tutorials/wiki/src/views/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki/src/views/CHANGES.txt b/docs/tutorials/wiki/src/views/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki/src/views/CHANGES.txt
+++ b/docs/tutorials/wiki/src/views/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki/src/views/README.txt b/docs/tutorials/wiki/src/views/README.txt
index dcb3605b8..5ec53bf9d 100644
--- a/docs/tutorials/wiki/src/views/README.txt
+++ b/docs/tutorials/wiki/src/views/README.txt
@@ -1,12 +1,29 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/pserve development.ini
+- Create a Python virtual environment.
+ python3 -m venv env
+
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/views/development.ini b/docs/tutorials/wiki/src/views/development.ini
index 6bf4b198e..74e7457d6 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,10 +13,7 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_zodbconn
- pyramid_tm
-tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
# By default, the toolbar only appears for clients from IP addresses
@@ -29,12 +26,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 4e9892e7b..60b6fe253 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -11,11 +11,7 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
- pyramid_tm
- pyramid_zodbconn
-tm.attempts = 3
zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
###
@@ -24,12 +20,11 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki/src/views/pytest.ini b/docs/tutorials/wiki/src/views/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py
index beeed75c9..598ad8146 100644
--- a/docs/tutorials/wiki/src/views/setup.py
+++ b/docs/tutorials/wiki/src/views/setup.py
@@ -18,37 +18,39 @@ requires = [
'ZODB3',
'waitress',
'docutils',
- ]
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ },
+)
diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py
index f2a86df47..728f7ac02 100644
--- a/docs/tutorials/wiki/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py
@@ -13,6 +13,8 @@ def main(global_config, **settings):
"""
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()
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
index 93580658b..5caaef4af 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
@@ -23,6 +23,7 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
diff --git a/docs/tutorials/wiki/src/views/tutorial/tests.py b/docs/tutorials/wiki/src/views/tutorial/tests.py
index 40f3c47af..ca7a47279 100644
--- a/docs/tutorials/wiki/src/views/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/views/tutorial/tests.py
@@ -14,4 +14,4 @@ class ViewTests(unittest.TestCase):
from .views import my_view
request = testing.DummyRequest()
info = my_view(request)
- self.assertEqual(info['project'], 'tutorial')
+ self.assertEqual(info['project'], 'myproj')
diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views.py
index 61517c31d..fd2b0edc1 100644
--- a/docs/tutorials/wiki/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki/src/views/tutorial/views.py
@@ -24,13 +24,13 @@ def view_page(context, request):
view_url = request.resource_url(page)
return '<a href="%s">%s</a>' % (view_url, word)
else:
- add_url = request.application_url + '/add_page/' + word
+ add_url = request.application_url + '/add_page/' + word
return '<a href="%s">%s</a>' % (add_url, word)
content = publish_parts(context.data, writer_name='html')['html_body']
content = wikiwords.sub(check, content)
edit_url = request.resource_url(context, 'edit_page')
- return dict(page = context, content = content, edit_url = edit_url)
+ return dict(page=context, content=content, edit_url=edit_url)
@view_config(name='add_page', context='.models.Wiki',
renderer='templates/edit.pt')
@@ -42,19 +42,19 @@ def add_page(context, request):
page.__name__ = pagename
page.__parent__ = context
context[pagename] = page
- return HTTPFound(location = request.resource_url(page))
+ return HTTPFound(location=request.resource_url(page))
save_url = request.resource_url(context, 'add_page', pagename)
page = Page('')
page.__name__ = pagename
page.__parent__ = context
- return dict(page = page, save_url = save_url)
+ return dict(page=page, save_url=save_url)
@view_config(name='edit_page', context='.models.Page',
renderer='templates/edit.pt')
def edit_page(context, request):
if 'form.submitted' in request.params:
context.data = request.params['body']
- return HTTPFound(location = request.resource_url(context))
+ return HTTPFound(location=request.resource_url(context))
return dict(page=context,
- save_url=request.resource_url(context, 'edit_page'))
+ save_url=request.resource_url(context, 'edit_page')) \ No newline at end of file
diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst
index 85a023cc9..cd82c0118 100644
--- a/docs/tutorials/wiki/tests.rst
+++ b/docs/tutorials/wiki/tests.rst
@@ -16,7 +16,7 @@ We write tests for the ``model`` classes and the ``appmaker``. Changing
we'll write a test class for the ``appmaker``.
To do so, we'll retain the ``tutorial.tests.ViewTests`` class that was
-generated as part of the ``zodb`` scaffold. We'll add three test classes: one
+generated as part of the ``zodb`` cookiecutter. We'll add three test classes: one
for the ``Page`` model named ``PageModelTests``, one for the ``Wiki`` model
named ``WikiModelTests``, and one for the appmaker named ``AppmakerTests``.
@@ -24,8 +24,8 @@ Test the views
==============
We'll modify our ``tests.py`` file, adding tests for each view function we
-added previously. As a result, we'll *delete* the ``ViewTests`` class that
-the ``zodb`` scaffold provided, and add four other test classes:
+added previously. As a result, we'll delete the ``ViewTests`` class that
+the ``zodb`` cookiecutter provided, and add four other test classes:
``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``.
These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page``
views.
@@ -52,25 +52,25 @@ Running the tests
=================
We can run these tests by using ``py.test`` similarly to how we did in
-:ref:`running_tests`. Courtesy of the scaffold, our testing dependencies have
+:ref:`running_tests`. Courtesy of the cookiecutter, our testing dependencies have
already been satisfied and ``py.test`` and coverage have already been
configured, so we can jump right to running tests.
On UNIX:
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/py.test -q
On Windows:
-.. code-block:: text
+.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q
+ c:\tutorial> %VENV%\Scripts\py.test -q
The expected result should look like the following:
.. code-block:: text
- ........................
- 24 passed in 2.46 seconds
+ .........................
+ 25 passed in 6.87 seconds
diff --git a/docs/tutorials/wiki2/authentication.rst b/docs/tutorials/wiki2/authentication.rst
index 5447db861..ff59ce70b 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: 18-20
+ :lines: 17-19
:emphasize-lines: 3
:lineno-match:
:language: ini
diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst
index 98a14c644..233231f8d 100644
--- a/docs/tutorials/wiki2/basiclayout.rst
+++ b/docs/tutorials/wiki2/basiclayout.rst
@@ -4,7 +4,7 @@
Basic Layout
============
-The starter files generated by the ``alchemy`` scaffold are very basic, but
+The starter files generated by the ``alchemy`` cookiecutter are very basic, but
they provide a good orientation for the high-level patterns common to most
:term:`URL dispatch`-based :app:`Pyramid` projects.
@@ -29,7 +29,6 @@ code:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:end-before: main
- :linenos:
:lineno-match:
:language: py
@@ -38,7 +37,6 @@ the ``main`` function we've defined in our ``__init__.py``:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:pyobject: main
- :linenos:
:lineno-match:
:language: py
@@ -179,7 +177,7 @@ decorator in order to create a view configuration within our application.
Without being processed by ``scan``, the decorator effectively does nothing.
``@view_config`` is inert without being detected via a :term:`scan`.
-The sample ``my_view()`` created by the scaffold uses a ``try:`` and
+The sample ``my_view()`` created by the cookiecutter uses a ``try:`` and
``except:`` clause to detect if there is a problem accessing the project
database and provide an alternate error response. That response will include
the text shown at the end of the file, which will be displayed in the browser
@@ -191,7 +189,7 @@ Content models with the ``models`` package
In an SQLAlchemy-based application, a *model* object is an object composed by
querying the SQL database. The ``models`` package is where the ``alchemy``
-scaffold put the classes that implement our models.
+cookiecutter put the classes that implement our models.
First, open ``tutorial/models/meta.py``, which should already contain the
following:
@@ -222,7 +220,6 @@ application's database schema.
.. literalinclude:: src/basiclayout/tutorial/models/meta.py
:lines: 15-16
:lineno-match:
- :linenos:
:language: py
Next open ``tutorial/models/mymodel.py``, which should already contain the
@@ -239,7 +236,6 @@ we have defined one named ``MyModel`` in ``mymodel.py``:
.. literalinclude:: src/basiclayout/tutorial/models/mymodel.py
:pyobject: MyModel
:lineno-match:
- :linenos:
:language: py
Our example model does not require an ``__init__`` method because SQLAlchemy
@@ -292,7 +288,6 @@ database engine using :func:`sqlalchemy.engine_from_config` from the
.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
:pyobject: get_engine
:lineno-match:
- :linenos:
:language: py
The function ``get_session_factory`` accepts an :term:`SQLAlchemy` database
@@ -303,7 +298,6 @@ used for creating sessions bound to the database engine.
.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
:pyobject: get_session_factory
:lineno-match:
- :linenos:
:language: py
The function ``get_tm_session`` registers a database session with a transaction
@@ -314,7 +308,6 @@ unless an exception is raised, in which case the transaction will be aborted.
.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
:pyobject: get_tm_session
:lineno-match:
- :linenos:
:language: py
Finally, we define an ``includeme`` function, which is a hook for use with
@@ -328,7 +321,6 @@ of an incoming request to our application.
.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
:pyobject: includeme
:lineno-match:
- :linenos:
:language: py
That's about all there is to it regarding models, views, and initialization
diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst
index 9f7b82d1d..5cebb943c 100644
--- a/docs/tutorials/wiki2/definingmodels.rst
+++ b/docs/tutorials/wiki2/definingmodels.rst
@@ -4,7 +4,7 @@
Defining the Domain Model
=========================
-The first change we'll make to our stock ``pcreate``-generated application will
+The first change we'll make to our stock cookiecutter-generated application will
be to define a wiki page :term:`domain model`.
.. note::
@@ -22,10 +22,10 @@ Declaring dependencies in our ``setup.py`` file
The models code in our application will depend on a package which is not a
dependency of the original "tutorial" application. The original "tutorial"
-application was generated by the ``pcreate`` command; it doesn't know about our
+application was generated by the cookiecutter; it doesn't know about our
custom application requirements.
-We need to add a dependency, the ``bcrypt`` package, to our ``tutorial``
+We need to add a dependency, the `bcrypt <https://pypi.python.org/pypi/bcrypt>`_ package, to our ``tutorial``
package's ``setup.py`` file by assigning this dependency to the ``requires``
parameter in the ``setup()`` function.
@@ -38,6 +38,10 @@ Open ``tutorial/setup.py`` and edit it to look like the following:
Only the highlighted line needs to be added.
+.. note::
+
+ We are using the ``bcrypt`` package from PyPI to hash our passwords securely. There are other one-way hash algorithms for passwords if ``bcrypt`` is an issue on your system. Just make sure that it's an algorithm approved for storing passwords versus a generic one-way hash.
+
Running ``pip install -e .``
============================
@@ -53,31 +57,31 @@ On UNIX:
.. code-block:: bash
- $ cd tutorial
- $ $VENV/bin/pip install -e .
+ $ $VENV/bin/pip install -e .
On Windows:
.. code-block:: doscon
- c:\pyramidtut> cd tutorial
- c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e .
+ c:\tutorial> %VENV%\Scripts\pip install -e .
Success executing this command will end with a line to the console something
-like this::
+like the following.
- Successfully installed bcrypt-2.0.0 cffi-1.5.2 pycparser-2.14 tutorial-0.0
+.. code-block:: text
+
+ Successfully installed bcrypt-3.1.2 cffi-1.9.1 pycparser-2.17 tutorial
Remove ``mymodel.py``
----------------------
+=====================
Let's delete the file ``tutorial/models/mymodel.py``. The ``MyModel`` class is
only a sample and we're not going to use it.
Add ``user.py``
----------------
+===============
Create a new file ``tutorial/models/user.py`` with the following contents:
@@ -98,12 +102,12 @@ and ``role`` (all instances of :class:`sqlalchemy.schema.Column`). These will
map to columns in the ``users`` table. The ``id`` attribute will be the primary
key in the table. The ``name`` attribute will be a text column, each value of
which needs to be unique within the column. The ``password_hash`` is a nullable
-text attribute that will contain a securely hashed password [1]_. Finally, the
+text attribute that will contain a securely hashed password. Finally, the
``role`` text attribute will hold the role of the user.
There are two helper methods that will help us later when using the user
objects. The first is ``set_password`` which will take a raw password and
-transform it using bcrypt_ into an irreversible representation, a process known
+transform it using ``bcrypt`` into an irreversible representation, a process known
as "hashing". The second method, ``check_password``, will allow us to compare
the hashed value of the submitted password against the hashed value of the
password stored in the user's record in the database. If the two hashed values
@@ -116,7 +120,7 @@ authenticate as any user.
Add ``page.py``
----------------
+===============
Create a new file ``tutorial/models/page.py`` with the following contents:
@@ -138,7 +142,7 @@ guaranteed that an instance of ``page`` will have a corresponding
Edit ``models/__init__.py``
----------------------------
+===========================
Since we are using a package for our models, we also need to update our
``__init__.py`` file to ensure that the models are attached to the metadata.
@@ -155,12 +159,16 @@ Here we align our imports with the names of the models, ``Page`` and ``User``.
Edit ``scripts/initializedb.py``
---------------------------------
+================================
We haven't looked at the details of this file yet, but within the ``scripts``
directory of your ``tutorial`` package is a file named ``initializedb.py``.
Code in this file is executed whenever we run the ``initialize_tutorial_db``
-command, as we did in the installation step of this tutorial [2]_.
+command, as we did in the installation step of this tutorial.
+
+.. note::
+
+ The command is named ``initialize_tutorial_db`` because of the mapping defined in the ``[console_scripts]`` entry point of our project's ``setup.py`` file.
Since we've changed our model, we need to make changes to our
``initializedb.py`` script. In particular, we'll replace our import of
@@ -180,7 +188,7 @@ Only the highlighted lines need to be changed.
Installing the project and re-initializing the database
--------------------------------------------------------
+=======================================================
Because our model has changed, and in order to reinitialize the database, we
need to rerun the ``initialize_tutorial_db`` command to pick up the changes
@@ -191,53 +199,53 @@ Success will look something like this:
.. code-block:: bash
- 2016-05-22 04:12:09,226 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
- 2016-05-22 04:12:09,226 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] ()
- 2016-05-22 04:12:09,226 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
- 2016-05-22 04:12:09,227 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] ()
- 2016-05-22 04:12:09,227 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("users")
- 2016-05-22 04:12:09,227 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
- 2016-05-22 04:12:09,228 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("pages")
- 2016-05-22 04:12:09,228 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
- 2016-05-22 04:12:09,229 INFO [sqlalchemy.engine.base.Engine:1097][MainThread]
- CREATE TABLE users (
- id INTEGER NOT NULL,
- name TEXT NOT NULL,
- role TEXT NOT NULL,
- password_hash TEXT,
- CONSTRAINT pk_users PRIMARY KEY (id),
- CONSTRAINT uq_users_name UNIQUE (name)
- )
-
-
- 2016-05-22 04:12:09,229 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
- 2016-05-22 04:12:09,230 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
- 2016-05-22 04:12:09,230 INFO [sqlalchemy.engine.base.Engine:1097][MainThread]
- CREATE TABLE pages (
- id INTEGER NOT NULL,
- name TEXT NOT NULL,
- data TEXT NOT NULL,
- creator_id INTEGER NOT NULL,
- CONSTRAINT pk_pages PRIMARY KEY (id),
- CONSTRAINT uq_pages_name UNIQUE (name),
- CONSTRAINT fk_pages_creator_id_users FOREIGN KEY(creator_id) REFERENCES users (id)
- )
-
-
- 2016-05-22 04:12:09,231 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
- 2016-05-22 04:12:09,231 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
- 2016-05-22 04:12:09,782 INFO [sqlalchemy.engine.base.Engine:646][MainThread] BEGIN (implicit)
- 2016-05-22 04:12:09,783 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?)
- 2016-05-22 04:12:09,784 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('editor', 'editor', b'$2b$12$K/WLVKRl5fMAb6UM58ueTetXlE3rlc5cRK5zFPimK598scXBR/xWC')
- 2016-05-22 04:12:09,784 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?)
- 2016-05-22 04:12:09,784 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('basic', 'basic', b'$2b$12$JfwLyCJGv3t.RTSmIrh3B.FKXRT9FevkAqafWdK5oq7Hl4mgAQORe')
- 2016-05-22 04:12:09,785 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO pages (name, data, creator_id) VALUES (?, ?, ?)
- 2016-05-22 04:12:09,785 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('FrontPage', 'This is the front page', 1)
- 2016-05-22 04:12:09,786 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
+ 2016-12-20 02:51:11,195 INFO [sqlalchemy.engine.base.Engine:1235][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
+ 2016-12-20 02:51:11,195 INFO [sqlalchemy.engine.base.Engine:1236][MainThread] ()
+ 2016-12-20 02:51:11,195 INFO [sqlalchemy.engine.base.Engine:1235][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
+ 2016-12-20 02:51:11,195 INFO [sqlalchemy.engine.base.Engine:1236][MainThread] ()
+ 2016-12-20 02:51:11,196 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] PRAGMA table_info("pages")
+ 2016-12-20 02:51:11,196 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ()
+ 2016-12-20 02:51:11,196 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] PRAGMA table_info("users")
+ 2016-12-20 02:51:11,197 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ()
+ 2016-12-20 02:51:11,197 INFO [sqlalchemy.engine.base.Engine:1140][MainThread]
+ CREATE TABLE users (
+ id INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ role TEXT NOT NULL,
+ password_hash TEXT,
+ CONSTRAINT pk_users PRIMARY KEY (id),
+ CONSTRAINT uq_users_name UNIQUE (name)
+ )
+
+
+ 2016-12-20 02:51:11,197 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ()
+ 2016-12-20 02:51:11,198 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT
+ 2016-12-20 02:51:11,199 INFO [sqlalchemy.engine.base.Engine:1140][MainThread]
+ CREATE TABLE pages (
+ id INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ data TEXT NOT NULL,
+ creator_id INTEGER NOT NULL,
+ CONSTRAINT pk_pages PRIMARY KEY (id),
+ CONSTRAINT uq_pages_name UNIQUE (name),
+ CONSTRAINT fk_pages_creator_id_users FOREIGN KEY(creator_id) REFERENCES users (id)
+ )
+
+
+ 2016-12-20 02:51:11,199 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ()
+ 2016-12-20 02:51:11,200 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT
+ 2016-12-20 02:51:11,755 INFO [sqlalchemy.engine.base.Engine:679][MainThread] BEGIN (implicit)
+ 2016-12-20 02:51:11,755 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?)
+ 2016-12-20 02:51:11,755 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ('editor', 'editor', '$2b$12$ds7h2Zb7.l6TEFup5h8f4ekA9GRfEpE1yQGDRvT9PConw73kKuupG')
+ 2016-12-20 02:51:11,756 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?)
+ 2016-12-20 02:51:11,756 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ('basic', 'basic', '$2b$12$KgruXP5Vv7rikr6dGB3TF.flGXYpiE0Li9K583EVomjY.SYmQOsyi')
+ 2016-12-20 02:51:11,757 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] INSERT INTO pages (name, data, creator_id) VALUES (?, ?, ?)
+ 2016-12-20 02:51:11,757 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ('FrontPage', 'This is the front page', 1)
+ 2016-12-20 02:51:11,757 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT
View the application in a browser
----------------------------------
+=================================
We can't. At this point, our system is in a "non-runnable" state; we'll need
to change view-related files in the next chapter to be able to start the
@@ -250,14 +258,3 @@ your console that ends with this exception:
ImportError: cannot import name MyModel
This will also happen if you attempt to run the tests.
-
-.. _bcrypt: https://pypi.python.org/pypi/bcrypt
-
-.. [1] We are using the bcrypt_ package from PyPI to hash our passwords
- securely. There are other one-way hash algorithms for passwords if
- bcrypt is an issue on your system. Just make sure that it's an
- algorithm approved for storing passwords versus a generic one-way hash.
-
-.. [2] The command is named ``initialize_tutorial_db`` because of the mapping
- defined in the ``[console_scripts]`` entry point of our project's
- ``setup.py`` file.
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index 996bff88c..3c343e155 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -42,7 +42,7 @@ installed, so re-run the ``$VENV/bin/pip install -e .`` command.
Static assets
--------------
+=============
Our templates name static assets, including CSS and images. We don't need
to create these files within our package's ``static`` directory because they
@@ -133,7 +133,7 @@ The highlighted lines need to be added or edited.
We added some imports, and created a regular expression to find "WikiWords".
We got rid of the ``my_view`` view function and its decorator that was added
-when we originally rendered the ``alchemy`` scaffold. It was only an example
+when we originally rendered the ``alchemy`` cookiecutter. It was only an example
and isn't relevant to our application. We also deleted the ``db_err_msg``
string.
@@ -340,7 +340,7 @@ indicated by the emphasized lines:
.. literalinclude:: src/views/tutorial/templates/layout.jinja2
:linenos:
- :emphasize-lines: 11,35-36
+ :emphasize-lines: 11,35-37
:language: html
Since we're using a templating engine, we can factor common boilerplate out of
@@ -350,7 +350,7 @@ template inheritance via blocks.
- We have defined two placeholders in the layout template where a child
template can override the content. These blocks are named ``subtitle`` (line
11) and ``content`` (line 36).
-- Please refer to the Jinja2_ documentation for more information about template
+- Please refer to the `Jinja2 documentation <http://jinja.pocoo.org/>`_ for more information about template
inheritance.
@@ -375,7 +375,7 @@ This template is used by ``view_page()`` for displaying a single wiki page.
view (line 6). ``content`` contains HTML, so the ``|safe`` filter is used to
prevent escaping it (e.g., changing ">" to "&gt;").
- We create a link that points at the "edit" URL, which when clicked invokes
- the ``edit_page`` view for the requested page (line 9).
+ the ``edit_page`` view for the requested page (lines 8-10).
The ``edit.jinja2`` template
@@ -436,7 +436,7 @@ There are several important things to note about this configuration:
the view.
Finally, we may delete the ``tutorial/templates/mytemplate.jinja2`` template
-that was provided by the ``alchemy`` scaffold, as we have created our own
+that was provided by the ``alchemy`` cookiecutter, as we have created our own
templates for the wiki.
.. note::
@@ -475,5 +475,3 @@ each of the following URLs, checking that the result is as expected:
will generate a ``NoResultFound: No row was found for one()`` error. You'll
see an interactive traceback facility provided by
:term:`pyramid_debugtoolbar`.
-
-.. _jinja2: http://jinja.pocoo.org/
diff --git a/docs/tutorials/wiki2/design.rst b/docs/tutorials/wiki2/design.rst
index 523a6e6d8..515aff276 100644
--- a/docs/tutorials/wiki2/design.rst
+++ b/docs/tutorials/wiki2/design.rst
@@ -23,22 +23,22 @@ We'll be using an SQLite database to hold our wiki data, and we'll be using
Within the database, we will define two tables:
-- The `users` table which will store the `id`, `name`, `password_hash` and
- `role` of each wiki user.
-- The `pages` table, whose elements will store the wiki pages.
- There are four columns: `id`, `name`, `data` and `creator_id`.
+- The ``users`` table which will store the ``id``, ``name``, ``password_hash`` and
+ ``role`` of each wiki user.
+- The ``pages`` table, whose elements will store the wiki pages.
+ There are four columns: ``id``, ``name``, ``data`` and ``creator_id``.
-There is a one-to-many relationship between `users` and `pages` tracking
-the user who created each wiki page defined by the `creator_id` column on the
-`pages` table.
+There is a one-to-many relationship between ``users`` and ``pages`` tracking
+the user who created each wiki page defined by the ``creator_id`` column on the
+``pages`` table.
-URLs like ``/PageName`` will try to find an element in the `pages` table that
+URLs like ``/PageName`` will try to find an element in the ``pages`` table that
has a corresponding name.
To add a page to the wiki, a new row is created and the text is stored in
-`data`.
+``data``.
-A page named ``FrontPage`` containing the text *This is the front page*, will
+A page named ``FrontPage`` containing the text "This is the front page" will
be created when the storage is initialized, and will be used as the wiki home
page.
@@ -61,12 +61,12 @@ We'll eventually be adding security to our application. To do this, we'll
be using a very simple role-based security model. We'll assign a single
role category to each user in our system.
-`basic`
- An authenticated user who can view content and create new pages. A `basic`
+``basic``
+ An authenticated user who can view content and create new pages. A ``basic``
user may also edit the pages they have created but not pages created by
other users.
-`editor`
+``editor``
An authenticated user who can create and edit any content in the system.
In order to accomplish this we'll need to define an authentication policy
@@ -101,16 +101,12 @@ in the following table:
+----------------------+-----------------------+-------------+----------------+------------+
| URL | Action | View | Template | Permission |
-| | | | | |
+======================+=======================+=============+================+============+
| / | Redirect to | view_wiki | | |
| | /FrontPage | | | |
+----------------------+-----------------------+-------------+----------------+------------+
| /PageName | Display existing | view_page | view.jinja2 | view |
| | page [2]_ | [1]_ | | |
-| | | | | |
-| | | | | |
-| | | | | |
+----------------------+-----------------------+-------------+----------------+------------+
| /PageName/edit_page | Display edit form | edit_page | edit.jinja2 | edit |
| | with existing | | | |
@@ -149,14 +145,13 @@ in the following table:
| | login form with | | | |
| | "login failed" | | | |
| | message. | | | |
-| | | | | |
+----------------------+-----------------------+-------------+----------------+------------+
| /logout | Redirect to | logout | | |
| | /FrontPage | | | |
+----------------------+-----------------------+-------------+----------------+------------+
.. [1] This is the default view for a Page context when there is no view name.
-.. [2] Pyramid will return a default 404 Not Found page if the page *PageName*
+.. [2] Pyramid will return a default 404 Not Found page if the page ``PageName``
does not exist yet.
.. [3] ``pyramid.exceptions.Forbidden`` is reached when a user tries to invoke
a view that is not authorized by the authorization policy.
diff --git a/docs/tutorials/wiki2/distributing.rst b/docs/tutorials/wiki2/distributing.rst
index f38a733f4..dd6b1b750 100644
--- a/docs/tutorials/wiki2/distributing.rst
+++ b/docs/tutorials/wiki2/distributing.rst
@@ -19,22 +19,22 @@ On Windows:
.. code-block:: doscon
- c:\pyramidtut> %VENV%\Scripts\python setup.py sdist
+ c:\tutorial> %VENV%\Scripts\python setup.py sdist
The output of such a command will be something like:
.. code-block:: text
running sdist
- # .. more output ..
+ # more output
creating dist
Creating tar archive
removing 'tutorial-0.0' (and everything under it)
-Note that this command creates a tarball in the "dist" subdirectory named
+Note that this command creates a tarball in the ``dist`` subdirectory named
``tutorial-0.0.tar.gz``. You can send this file to your friends to show them
your cool new application. They should be able to install it by pointing the
-``easy_install`` command directly at it. Or you can upload it to `PyPI
+``pip install`` command directly at it. Or you can upload it to `PyPI
<https://pypi.python.org/pypi>`_ and share it with the rest of the world, where
-it can be downloaded via ``easy_install`` remotely like any other package
-people download from PyPI.
+it can be downloaded via ``pip install`` remotely like any other package people
+download from PyPI.
diff --git a/docs/tutorials/wiki2/index.rst b/docs/tutorials/wiki2/index.rst
index 18e9f552e..740a7f014 100644
--- a/docs/tutorials/wiki2/index.rst
+++ b/docs/tutorials/wiki2/index.rst
@@ -4,13 +4,11 @@ SQLAlchemy + URL dispatch wiki tutorial
=======================================
This tutorial introduces an :term:`SQLAlchemy` and :term:`URL dispatch`-based
-:app:`Pyramid` application to a developer familiar with Python. When the
-tutorial is finished, the developer will have created a basic wiki
+:app:`Pyramid` application to a developer familiar with Python. When finished, the developer will have created a basic wiki
application with authentication and authorization.
For cut and paste purposes, the source code for all stages of this tutorial can
-be browsed on GitHub at `docs/tutorials/wiki2/src
-<https://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src>`_,
+be browsed on `GitHub <https://github.com/Pylons/pyramid/>`_,
which corresponds to the same location if you have Pyramid sources.
.. toctree::
@@ -26,4 +24,3 @@ which corresponds to the same location if you have Pyramid sources.
authorization
tests
distributing
-
diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst
index 0440c2d1d..c61d4360d 100644
--- a/docs/tutorials/wiki2/installation.rst
+++ b/docs/tutorials/wiki2/installation.rst
@@ -15,178 +15,137 @@ install Pyramid**. Thereby you will satisfy the following requirements.
* You've satisfied the :ref:`requirements-for-installing-packages`.
-Create directory to contain the project
----------------------------------------
+Install SQLite3 and its development packages
+--------------------------------------------
-We need a workspace for our project files.
+If you used a package manager to install your Python or if you compiled your Python from source, then you must install SQLite3 and its development packages. If you downloaded your Python as an installer from https://www.python.org, then you already have it installed and can skip this step.
-On UNIX
-^^^^^^^
+If you need to install the SQLite3 packages, then, for example, using the Debian system and ``apt-get``, the command would be the following:
.. code-block:: bash
- $ mkdir ~/pyramidtut
-
-On Windows
-^^^^^^^^^^
+ $ sudo apt-get install libsqlite3-dev
-.. code-block:: doscon
- c:\> mkdir pyramidtut
+Install cookiecutter
+--------------------
+We will use a :term:`cookiecutter` to create a Python package project from a Python package project template. See `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ for instructions.
-Create and use a virtual Python environment
--------------------------------------------
+Generate a Pyramid project from a cookiecutter
+----------------------------------------------
-Next let's create a virtual environment workspace for our project. We will use
-the ``VENV`` environment variable instead of the absolute path of the virtual
-environment.
+We will create a Pyramid project in your home directory for UNIX or at the root for Windows. It is assumed you know the path to where you installed ``cookiecutter``. Issue the following commands and override the defaults in the prompts as follows.
On UNIX
^^^^^^^
.. code-block:: bash
- $ export VENV=~/pyramidtut
- $ python3 -m venv $VENV
+ $ cd ~
+ $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy
On Windows
^^^^^^^^^^
.. code-block:: doscon
- c:\> set VENV=c:\pyramidtut
-
-Each version of Python uses different paths, so you will need to adjust the
-path to the command for your Python version.
-
-Python 2.7:
-
-.. code-block:: doscon
-
- c:\> c:\Python27\Scripts\virtualenv %VENV%
+ c:\> cd \
+ c:\> cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy
-Python 3.5:
+On all operating systems
+^^^^^^^^^^^^^^^^^^^^^^^^
+If prompted for the first item, accept the default ``yes`` by hitting return.
-.. code-block:: doscon
-
- c:\> c:\Python35\Scripts\python -m venv %VENV%
+.. 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
-Upgrade ``pip`` and ``setuptools`` in the virtual environment
--------------------------------------------------------------
+Change directory into your newly created project
+------------------------------------------------
On UNIX
^^^^^^^
.. code-block:: bash
- $ $VENV/bin/pip install --upgrade pip setuptools
+ $ cd tutorial
On Windows
^^^^^^^^^^
.. code-block:: doscon
- c:\> %VENV%\Scripts\pip install --upgrade pip setuptools
+ c:\> cd tutorial
-Install Pyramid into the virtual Python environment
----------------------------------------------------
+Set and use a ``VENV`` environment variable
+-------------------------------------------
+
+We will set the ``VENV`` environment variable to the absolute path of the virtual environment, and use it going forward.
On UNIX
^^^^^^^
-.. parsed-literal::
+.. code-block:: bash
- $ $VENV/bin/pip install "pyramid==\ |release|\ "
+ $ export VENV=~/tutorial
On Windows
^^^^^^^^^^
-.. parsed-literal::
-
- c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ "
-
-
-Install SQLite3 and its development packages
---------------------------------------------
-
-If you used a package manager to install your Python or if you compiled
-your Python from source, then you must install SQLite3 and its
-development packages. If you downloaded your Python as an installer
-from https://www.python.org, then you already have it installed and can skip
-this step.
-
-If you need to install the SQLite3 packages, then, for example, using
-the Debian system and ``apt-get``, the command would be the following:
-
-.. code-block:: bash
-
- $ sudo apt-get install libsqlite3-dev
+.. code-block:: doscon
+ c:\tutorial> set VENV=c:\tutorial
-Change directory to your virtual Python environment
----------------------------------------------------
-Change directory to the ``pyramidtut`` directory, which is both your workspace
-and your virtual environment.
+Create a virtual environment
+----------------------------
On UNIX
^^^^^^^
.. code-block:: bash
- $ cd pyramidtut
+ $ python3 -m venv $VENV
On Windows
^^^^^^^^^^
-.. code-block:: doscon
+Each version of Python uses different paths, so you will need to adjust the path to the command for your Python version. Recent versions of the Python 3 installer for Windows now install a Python launcher.
- c:\> cd pyramidtut
+Python 2.7:
+.. code-block:: doscon
-.. _sql_making_a_project:
+ c:\tutorial> c:\Python27\Scripts\virtualenv %VENV%
-Making a project
-----------------
+Python 3.6:
-Your next step is to create a project. For this tutorial we will use
-the :term:`scaffold` named ``alchemy`` which generates an application
-that uses :term:`SQLAlchemy` and :term:`URL dispatch`.
+.. code-block:: doscon
-:app:`Pyramid` supplies a variety of scaffolds to generate sample projects. We
-will use ``pcreate``, a script that comes with Pyramid, to create our project
-using a scaffold.
+ c:\tutorial> python -m venv %VENV%
-By passing ``alchemy`` into the ``pcreate`` command, the script creates the
-files needed to use SQLAlchemy. By passing in our application name
-``tutorial``, the script inserts that application name into all the required
-files. For example, ``pcreate`` creates the ``initialize_tutorial_db`` in the
-``pyramidtut/bin`` directory.
-The below instructions assume your current working directory is "pyramidtut".
+Upgrade packaging tools in the virtual environment
+--------------------------------------------------
On UNIX
^^^^^^^
.. code-block:: bash
- $ $VENV/bin/pcreate -s alchemy tutorial
+ $ $VENV/bin/pip install --upgrade pip setuptools
On Windows
^^^^^^^^^^
.. code-block:: doscon
- c:\pyramidtut> %VENV%\Scripts\pcreate -s alchemy tutorial
-
-.. note:: If you are using Windows, the ``alchemy`` scaffold may not deal
- gracefully with installation into a location that contains spaces in the
- path. If you experience startup problems, try putting both the virtual
- environment and the project into directories that do not contain spaces in
- their paths.
+ c:\tutorial> %VENV%\Scripts\pip install --upgrade pip setuptools
.. _installing_project_in_dev_mode:
@@ -194,74 +153,49 @@ On Windows
Installing the project in development mode
------------------------------------------
-In order to do development on the project easily, you must "register" the
-project as a development egg in your workspace using the ``pip install -e .``
-command. In order to do so, change directory to the ``tutorial`` directory that
-you created in :ref:`sql_making_a_project`, and run the ``pip install -e .``
-command using the virtual environment Python interpreter.
+In order to do development on the project easily, you must "register" the project as a development egg in your workspace. We will install testing requirements at the same time. We do so with the following command.
On UNIX
^^^^^^^
.. code-block:: bash
- $ cd tutorial
- $ $VENV/bin/pip install -e .
+ $ $VENV/bin/pip install -e ".[testing]"
On Windows
^^^^^^^^^^
.. code-block:: doscon
- c:\pyramidtut> cd tutorial
- c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e .
+ c:\tutorial> %VENV%\Scripts\pip install -e ".[testing]"
-The console will show ``pip`` checking for packages and installing missing
-packages. Success executing this command will show a line like the following:
+On all operating systems
+^^^^^^^^^^^^^^^^^^^^^^^^
-.. code-block:: bash
+The console will show ``pip`` checking for packages and installing missing packages. Success executing this command will show a line like the following:
- Successfully installed Chameleon-2.24 Mako-1.0.4 MarkupSafe-0.23 \
- Pygments-2.1.3 SQLAlchemy-1.0.12 pyramid-chameleon-0.3 \
- pyramid-debugtoolbar-2.4.2 pyramid-mako-1.0.2 pyramid-tm-0.12.1 \
- transaction-1.4.4 tutorial waitress-0.8.10 zope.sqlalchemy-0.7.6
-
-
-.. _install-testing-requirements:
+.. code-block:: bash
-Install testing requirements
-----------------------------
+ Successfully installed Jinja2-2.8 Mako-1.0.6 MarkupSafe-0.23 \
+ PasteDeploy-1.5.2 Pygments-2.1.3 SQLAlchemy-1.1.4 WebOb-1.6.3 \
+ WebTest-2.0.24 beautifulsoup4-4.5.1 coverage-4.2 py-1.4.32 pyramid-1.7.3 \
+ pyramid-debugtoolbar-3.0.5 pyramid-jinja2-2.7 pyramid-mako-1.0.2 \
+ pyramid-tm-1.1.1 pytest-3.0.5 pytest-cov-2.4.0 repoze.lru-0.6 six-1.10.0 \
+ transaction-2.0.3 translationstring-1.3 tutorial venusian-1.0 \
+ waitress-1.0.1 zope.deprecation-4.2.0 zope.interface-4.3.3 \
+ zope.sqlalchemy-0.7.7
-In order to run tests, we need to install the testing requirements. This is
-done through our project's ``setup.py`` file, in the ``tests_require`` and
-``extras_require`` stanzas, and by issuing the command below for your
-operating system.
+Testing requirements are defined in our project's ``setup.py`` file, in the ``tests_require`` and ``extras_require`` stanzas.
.. literalinclude:: src/installation/setup.py
:language: python
- :linenos:
- :lineno-start: 22
+ :lineno-match:
:lines: 22-26
.. literalinclude:: src/installation/setup.py
:language: python
- :linenos:
- :lineno-start: 45
- :lines: 45-47
-
-On UNIX
-^^^^^^^
-
-.. code-block:: bash
-
- $ $VENV/bin/pip install -e ".[testing]"
-
-On Windows
-^^^^^^^^^^
-
-.. code-block:: doscon
-
- c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e ".[testing]"
+ :lineno-match:
+ :lines: 46-48
.. _sql_running_tests:
@@ -286,7 +220,7 @@ On Windows
.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q
+ c:\tutorial> %VENV%\Scripts\py.test -q
For a successful test run, you should see output that ends like this:
@@ -301,7 +235,7 @@ Expose test coverage information
You can run the ``py.test`` command to see test coverage information. This
runs the tests in the same way that ``py.test`` does, but provides additional
-"coverage" information, exposing which lines of your project are covered by the
+:term:`coverage` information, exposing which lines of your project are covered by the
tests.
We've already installed the ``pytest-cov`` package into our virtual
@@ -319,46 +253,45 @@ On Windows
.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov \
- --cov-report=term-missing
+ c:\tutorial> %VENV%\Scripts\py.test --cov --cov-report=term-missing
If successful, you will see output something like this:
.. code-block:: bash
- ======================== test session starts ========================
- platform Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
- rootdir: /Users/stevepiercy/projects/pyramidtut/tutorial, inifile:
- plugins: cov-2.2.1
- collected 2 items
-
- tutorial/tests.py ..
- ------------------ coverage: platform Python 3.5.1 ------------------
- Name Stmts Miss Cover Missing
- ----------------------------------------------------------------
- tutorial/__init__.py 8 6 25% 7-12
- tutorial/models/__init__.py 22 0 100%
- tutorial/models/meta.py 5 0 100%
- tutorial/models/mymodel.py 8 0 100%
- tutorial/routes.py 3 2 33% 2-3
- tutorial/scripts/__init__.py 0 0 100%
- tutorial/scripts/initializedb.py 26 16 38% 22-25, 29-45
- tutorial/views/__init__.py 0 0 100%
- tutorial/views/default.py 12 0 100%
- tutorial/views/notfound.py 4 2 50% 6-7
- ----------------------------------------------------------------
- TOTAL 88 26 70%
- ===================== 2 passed in 0.57 seconds ======================
+ ======================== test session starts ========================
+ platform Python 3.6.0, pytest-3.0.5, py-1.4.31, pluggy-0.4.0
+ rootdir: /Users/stevepiercy/tutorial, inifile:
+ plugins: cov-2.4.0
+ collected 2 items
+
+ tutorial/tests.py ..
+ ------------------ coverage: platform Python 3.6.0 ------------------
+ Name Stmts Miss Cover Missing
+ ----------------------------------------------------------------
+ tutorial/__init__.py 8 6 25% 7-12
+ tutorial/models/__init__.py 22 0 100%
+ tutorial/models/meta.py 5 0 100%
+ tutorial/models/mymodel.py 8 0 100%
+ tutorial/routes.py 3 2 33% 2-3
+ tutorial/scripts/__init__.py 0 0 100%
+ tutorial/scripts/initializedb.py 26 16 38% 22-25, 29-45
+ tutorial/views/__init__.py 0 0 100%
+ tutorial/views/default.py 12 0 100%
+ tutorial/views/notfound.py 4 2 50% 6-7
+ ----------------------------------------------------------------
+ TOTAL 88 26 70%
+ ===================== 2 passed in 0.57 seconds ======================
Our package doesn't quite have 100% test coverage.
-.. _test_and_coverage_scaffold_defaults_sql:
+.. _test_and_coverage_cookiecutter_defaults_sql:
-Test and coverage scaffold defaults
------------------------------------
+Test and coverage cookiecutter defaults
+---------------------------------------
-Scaffolds include configuration defaults for ``py.test`` and test coverage.
+Cookiecutters include configuration defaults for ``py.test`` and test coverage.
These configuration files are ``pytest.ini`` and ``.coveragerc``, located at
the root of your package. Without these defaults, we would need to specify the
path to the module on which we want to run tests and coverage.
@@ -375,11 +308,10 @@ On Windows
.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov=tutorial \
- --cov-report=term-missing tutorial\tests.py -q
+ c:\tutorial> %VENV%\Scripts\py.test --cov=tutorial tutorial\tests.py -q
py.test follows :ref:`conventions for Python test discovery
-<pytest:test discovery>`, and the configuration defaults from the scaffold
+<pytest:test discovery>`, and the configuration defaults from the cookiecutter
tell ``py.test`` where to find the module on which we want to run tests and
coverage.
@@ -417,36 +349,36 @@ On Windows
.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\initialize_tutorial_db development.ini
+ c:\tutorial> %VENV%\Scripts\initialize_tutorial_db development.ini
The output to your console should be something like this:
.. code-block:: bash
- 2016-05-22 04:03:28,888 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
- 2016-05-22 04:03:28,888 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] ()
- 2016-05-22 04:03:28,888 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
- 2016-05-22 04:03:28,889 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] ()
- 2016-05-22 04:03:28,890 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("models")
- 2016-05-22 04:03:28,890 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
- 2016-05-22 04:03:28,892 INFO [sqlalchemy.engine.base.Engine:1097][MainThread]
- CREATE TABLE models (
- id INTEGER NOT NULL,
- name TEXT,
- value INTEGER,
- CONSTRAINT pk_models PRIMARY KEY (id)
- )
-
-
- 2016-05-22 04:03:28,892 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
- 2016-05-22 04:03:28,893 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
- 2016-05-22 04:03:28,893 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] CREATE UNIQUE INDEX my_index ON models (name)
- 2016-05-22 04:03:28,893 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
- 2016-05-22 04:03:28,894 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
- 2016-05-22 04:03:28,896 INFO [sqlalchemy.engine.base.Engine:646][MainThread] BEGIN (implicit)
- 2016-05-22 04:03:28,897 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO models (name, value) VALUES (?, ?)
- 2016-05-22 04:03:28,897 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('one', 1)
- 2016-05-22 04:03:28,898 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
+ 2016-12-18 21:30:08,675 INFO [sqlalchemy.engine.base.Engine:1235][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
+ 2016-12-18 21:30:08,675 INFO [sqlalchemy.engine.base.Engine:1236][MainThread] ()
+ 2016-12-18 21:30:08,676 INFO [sqlalchemy.engine.base.Engine:1235][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
+ 2016-12-18 21:30:08,676 INFO [sqlalchemy.engine.base.Engine:1236][MainThread] ()
+ 2016-12-18 21:30:08,676 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] PRAGMA table_info("models")
+ 2016-12-18 21:30:08,676 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ()
+ 2016-12-18 21:30:08,677 INFO [sqlalchemy.engine.base.Engine:1140][MainThread]
+ CREATE TABLE models (
+ id INTEGER NOT NULL,
+ name TEXT,
+ value INTEGER,
+ CONSTRAINT pk_models PRIMARY KEY (id)
+ )
+
+
+ 2016-12-18 21:30:08,677 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ()
+ 2016-12-18 21:30:08,678 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT
+ 2016-12-18 21:30:08,679 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] CREATE UNIQUE INDEX my_index ON models (name)
+ 2016-12-18 21:30:08,679 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ()
+ 2016-12-18 21:30:08,679 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT
+ 2016-12-18 21:30:08,681 INFO [sqlalchemy.engine.base.Engine:679][MainThread] BEGIN (implicit)
+ 2016-12-18 21:30:08,682 INFO [sqlalchemy.engine.base.Engine:1140][MainThread] INSERT INTO models (name, value) VALUES (?, ?)
+ 2016-12-18 21:30:08,682 INFO [sqlalchemy.engine.base.Engine:1143][MainThread] ('one', 1)
+ 2016-12-18 21:30:08,682 INFO [sqlalchemy.engine.base.Engine:719][MainThread] COMMIT
Success! You should now have a ``tutorial.sqlite`` file in your current
working directory. This is an SQLite database with a single table defined in it
@@ -457,7 +389,8 @@ working directory. This is an SQLite database with a single table defined in it
Start the application
---------------------
-Start the application.
+Start the application. See :ref:`what_is_this_pserve_thing` for more
+information on ``pserve``.
On UNIX
^^^^^^^
@@ -471,7 +404,7 @@ On Windows
.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\pserve development.ini --reload
+ c:\tutorial> %VENV%\Scripts\pserve development.ini --reload
.. note::
@@ -482,9 +415,10 @@ If successful, you will see something like this on your console:
.. code-block:: text
- Starting subprocess with file monitor
- Starting server in PID 82349.
- serving on http://127.0.0.1:6543
+ Starting subprocess with file monitor
+ Starting server in PID 44078.
+ Serving on http://localhost:6543
+ Serving on http://localhost:6543
This means the server is ready to accept requests.
@@ -501,13 +435,15 @@ page. You can read more about the purpose of the icon at
application while you develop.
-Decisions the ``alchemy`` scaffold has made for you
----------------------------------------------------
+Decisions the ``alchemy`` cookiecutter has made for you
+-------------------------------------------------------
-Creating a project using the ``alchemy`` scaffold makes the following
+Creating a project using the ``alchemy`` cookiecutter makes the following
assumptions:
-- You are willing to use :term:`SQLAlchemy` as a database access tool.
+- You are willing to use SQLite for persistent storage, although almost any SQL database could be used with SQLAlchemy.
+
+- You are willing to use :term:`SQLAlchemy` for a database access tool.
- You are willing to use :term:`URL dispatch` to map URLs to code.
diff --git a/docs/tutorials/wiki2/src/authentication/.coveragerc b/docs/tutorials/wiki2/src/authentication/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki2/src/authentication/CHANGES.txt b/docs/tutorials/wiki2/src/authentication/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki2/src/authentication/CHANGES.txt
+++ b/docs/tutorials/wiki2/src/authentication/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki2/src/authentication/MANIFEST.in b/docs/tutorials/wiki2/src/authentication/MANIFEST.in
index 42cd299b5..05cc195d9 100644
--- a/docs/tutorials/wiki2/src/authentication/MANIFEST.in
+++ b/docs/tutorials/wiki2/src/authentication/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki2/src/authentication/README.txt b/docs/tutorials/wiki2/src/authentication/README.txt
index 5b0101e5f..81102a869 100644
--- a/docs/tutorials/wiki2/src/authentication/README.txt
+++ b/docs/tutorials/wiki2/src/authentication/README.txt
@@ -1,14 +1,33 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/initialize_tutorial_db development.ini
+- Create a Python virtual environment.
-- $VENV/bin/pserve development.ini
+ python3 -m venv env
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Configure the database.
+
+ env/bin/initialize_tutorial_db development.ini
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki2/src/authentication/development.ini b/docs/tutorials/wiki2/src/authentication/development.ini
index 4a6c9325c..0786c1f66 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,7 +13,6 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
@@ -29,12 +28,11 @@ auth.secret = seekrit
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 a13a0ca19..05d60feec 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,14 +16,17 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
auth.secret = real-seekrit
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/authentication/pytest.ini b/docs/tutorials/wiki2/src/authentication/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki2/src/authentication/setup.py b/docs/tutorials/wiki2/src/authentication/setup.py
index def3ce1f6..cc1aa421c 100644
--- a/docs/tutorials/wiki2/src/authentication/setup.py
+++ b/docs/tutorials/wiki2/src/authentication/setup.py
@@ -19,39 +19,42 @@ requires = [
'transaction',
'zope.sqlalchemy',
'waitress',
- ]
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web wsgi bfg pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- [console_scripts]
- initialize_tutorial_db = tutorial.scripts.initializedb:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ 'console_scripts': [
+ 'initialize_tutorial_db = tutorial.scripts.initializedb:main',
+ ],
+ },
+)
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/tests.py b/docs/tutorials/wiki2/src/authentication/tutorial/tests.py
index 99e95efd3..ce650ca7c 100644
--- a/docs/tutorials/wiki2/src/authentication/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/tests.py
@@ -54,7 +54,7 @@ class TestMyViewSuccessCondition(BaseTest):
from .views.default import my_view
info = my_view(dummy_request(self.session))
self.assertEqual(info['one'].name, 'one')
- self.assertEqual(info['project'], 'tutorial')
+ self.assertEqual(info['project'], 'myproj')
class TestMyViewFailureCondition(BaseTest):
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/.coveragerc b/docs/tutorials/wiki2/src/authorization/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki2/src/authorization/CHANGES.txt b/docs/tutorials/wiki2/src/authorization/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki2/src/authorization/CHANGES.txt
+++ b/docs/tutorials/wiki2/src/authorization/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki2/src/authorization/MANIFEST.in b/docs/tutorials/wiki2/src/authorization/MANIFEST.in
index 42cd299b5..05cc195d9 100644
--- a/docs/tutorials/wiki2/src/authorization/MANIFEST.in
+++ b/docs/tutorials/wiki2/src/authorization/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki2/src/authorization/README.txt b/docs/tutorials/wiki2/src/authorization/README.txt
index 5b0101e5f..81102a869 100644
--- a/docs/tutorials/wiki2/src/authorization/README.txt
+++ b/docs/tutorials/wiki2/src/authorization/README.txt
@@ -1,14 +1,33 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/initialize_tutorial_db development.ini
+- Create a Python virtual environment.
-- $VENV/bin/pserve development.ini
+ python3 -m venv env
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Configure the database.
+
+ env/bin/initialize_tutorial_db development.ini
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini
index 4a6c9325c..0786c1f66 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,7 +13,6 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
@@ -29,12 +28,11 @@ auth.secret = seekrit
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 a13a0ca19..05d60feec 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,14 +16,17 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
auth.secret = real-seekrit
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/authorization/pytest.ini b/docs/tutorials/wiki2/src/authorization/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py
index def3ce1f6..cc1aa421c 100644
--- a/docs/tutorials/wiki2/src/authorization/setup.py
+++ b/docs/tutorials/wiki2/src/authorization/setup.py
@@ -19,39 +19,42 @@ requires = [
'transaction',
'zope.sqlalchemy',
'waitress',
- ]
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web wsgi bfg pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- [console_scripts]
- initialize_tutorial_db = tutorial.scripts.initializedb:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ 'console_scripts': [
+ 'initialize_tutorial_db = tutorial.scripts.initializedb:main',
+ ],
+ },
+)
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/.coveragerc b/docs/tutorials/wiki2/src/basiclayout/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki2/src/basiclayout/CHANGES.txt b/docs/tutorials/wiki2/src/basiclayout/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki2/src/basiclayout/CHANGES.txt
+++ b/docs/tutorials/wiki2/src/basiclayout/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in b/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in
index 42cd299b5..05cc195d9 100644
--- a/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in
+++ b/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki2/src/basiclayout/README.txt b/docs/tutorials/wiki2/src/basiclayout/README.txt
index 5b0101e5f..81102a869 100644
--- a/docs/tutorials/wiki2/src/basiclayout/README.txt
+++ b/docs/tutorials/wiki2/src/basiclayout/README.txt
@@ -1,14 +1,33 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/initialize_tutorial_db development.ini
+- Create a Python virtual environment.
-- $VENV/bin/pserve development.ini
+ python3 -m venv env
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Configure the database.
+
+ env/bin/initialize_tutorial_db development.ini
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini
index 22b733e10..be80882a5 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,7 +13,6 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
@@ -27,12 +26,11 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 d2ecfe22a..c01ad9a7e 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,14 +14,17 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/basiclayout/pytest.ini b/docs/tutorials/wiki2/src/basiclayout/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py
index ede0a82ef..d3992a8f2 100644
--- a/docs/tutorials/wiki2/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki2/src/basiclayout/setup.py
@@ -17,39 +17,42 @@ requires = [
'transaction',
'zope.sqlalchemy',
'waitress',
- ]
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web wsgi bfg pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- [console_scripts]
- initialize_tutorial_db = tutorial.scripts.initializedb:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ 'console_scripts': [
+ 'initialize_tutorial_db = tutorial.scripts.initializedb:main',
+ ],
+ },
+)
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
index 3fc82cfba..5ca037787 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
@@ -5,7 +5,7 @@ import zope.sqlalchemy
# import or define all models here to ensure they are attached to the
# Base.metadata prior to any initialization routines
-from .mymodel import MyModel # noqa
+from .mymodel import MyModel # flake8: noqa
# run configure_mappers after defining all of the models to ensure
# all relationships can be setup
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2
index ab8c5ea3d..1f658c834 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2
@@ -8,7 +8,7 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>Alchemy Scaffold for The Pyramid Web Framework</title>
+ <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">
@@ -29,7 +29,7 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png') }}" alt="pyramid web framework">
</div>
<div class="col-md-10">
{% block content %}
@@ -40,10 +40,8 @@
<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><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-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>
</ul>
</div>
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2
index 6b49869c4..359f55ffa 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2
@@ -2,7 +2,7 @@
{% block content %}
<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+ <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>
</div>
{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py
index 99e95efd3..ce650ca7c 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py
@@ -54,7 +54,7 @@ class TestMyViewSuccessCondition(BaseTest):
from .views.default import my_view
info = my_view(dummy_request(self.session))
self.assertEqual(info['one'].name, 'one')
- self.assertEqual(info['project'], 'tutorial')
+ self.assertEqual(info['project'], 'myproj')
class TestMyViewFailureCondition(BaseTest):
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py
index ad0c728d7..a404d4154 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py
@@ -13,7 +13,7 @@ def my_view(request):
one = query.filter(MyModel.name == 'one').first()
except DBAPIError:
return Response(db_err_msg, content_type='text/plain', status=500)
- return {'one': one, 'project': 'tutorial'}
+ return {'one': one, 'project': 'myproj'}
db_err_msg = """\
diff --git a/docs/tutorials/wiki2/src/installation/.coveragerc b/docs/tutorials/wiki2/src/installation/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki2/src/installation/CHANGES.txt b/docs/tutorials/wiki2/src/installation/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki2/src/installation/CHANGES.txt
+++ b/docs/tutorials/wiki2/src/installation/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki2/src/installation/MANIFEST.in b/docs/tutorials/wiki2/src/installation/MANIFEST.in
index 42cd299b5..05cc195d9 100644
--- a/docs/tutorials/wiki2/src/installation/MANIFEST.in
+++ b/docs/tutorials/wiki2/src/installation/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki2/src/installation/README.txt b/docs/tutorials/wiki2/src/installation/README.txt
index 5b0101e5f..81102a869 100644
--- a/docs/tutorials/wiki2/src/installation/README.txt
+++ b/docs/tutorials/wiki2/src/installation/README.txt
@@ -1,14 +1,33 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/initialize_tutorial_db development.ini
+- Create a Python virtual environment.
-- $VENV/bin/pserve development.ini
+ python3 -m venv env
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Configure the database.
+
+ env/bin/initialize_tutorial_db development.ini
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki2/src/installation/development.ini b/docs/tutorials/wiki2/src/installation/development.ini
index 22b733e10..be80882a5 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,7 +13,6 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
@@ -27,12 +26,11 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 d2ecfe22a..c01ad9a7e 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,14 +14,17 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/installation/pytest.ini b/docs/tutorials/wiki2/src/installation/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki2/src/installation/setup.py b/docs/tutorials/wiki2/src/installation/setup.py
index ede0a82ef..d3992a8f2 100644
--- a/docs/tutorials/wiki2/src/installation/setup.py
+++ b/docs/tutorials/wiki2/src/installation/setup.py
@@ -17,39 +17,42 @@ requires = [
'transaction',
'zope.sqlalchemy',
'waitress',
- ]
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web wsgi bfg pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- [console_scripts]
- initialize_tutorial_db = tutorial.scripts.initializedb:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ 'console_scripts': [
+ 'initialize_tutorial_db = tutorial.scripts.initializedb:main',
+ ],
+ },
+)
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py
index 3fc82cfba..5ca037787 100644
--- a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py
@@ -5,7 +5,7 @@ import zope.sqlalchemy
# import or define all models here to ensure they are attached to the
# Base.metadata prior to any initialization routines
-from .mymodel import MyModel # noqa
+from .mymodel import MyModel # flake8: noqa
# run configure_mappers after defining all of the models to ensure
# all relationships can be setup
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2
index ab8c5ea3d..1f658c834 100644
--- a/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2
@@ -8,7 +8,7 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>Alchemy Scaffold for The Pyramid Web Framework</title>
+ <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">
@@ -29,7 +29,7 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png') }}" alt="pyramid web framework">
</div>
<div class="col-md-10">
{% block content %}
@@ -40,10 +40,8 @@
<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><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-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>
</ul>
</div>
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2
index 6b49869c4..359f55ffa 100644
--- a/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2
+++ b/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2
@@ -2,7 +2,7 @@
{% block content %}
<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+ <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>
</div>
{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/tests.py b/docs/tutorials/wiki2/src/installation/tutorial/tests.py
index 99e95efd3..ce650ca7c 100644
--- a/docs/tutorials/wiki2/src/installation/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/installation/tutorial/tests.py
@@ -54,7 +54,7 @@ class TestMyViewSuccessCondition(BaseTest):
from .views.default import my_view
info = my_view(dummy_request(self.session))
self.assertEqual(info['one'].name, 'one')
- self.assertEqual(info['project'], 'tutorial')
+ self.assertEqual(info['project'], 'myproj')
class TestMyViewFailureCondition(BaseTest):
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/views/default.py b/docs/tutorials/wiki2/src/installation/tutorial/views/default.py
index ad0c728d7..a404d4154 100644
--- a/docs/tutorials/wiki2/src/installation/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/installation/tutorial/views/default.py
@@ -13,7 +13,7 @@ def my_view(request):
one = query.filter(MyModel.name == 'one').first()
except DBAPIError:
return Response(db_err_msg, content_type='text/plain', status=500)
- return {'one': one, 'project': 'tutorial'}
+ return {'one': one, 'project': 'myproj'}
db_err_msg = """\
diff --git a/docs/tutorials/wiki2/src/models/.coveragerc b/docs/tutorials/wiki2/src/models/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki2/src/models/CHANGES.txt b/docs/tutorials/wiki2/src/models/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki2/src/models/CHANGES.txt
+++ b/docs/tutorials/wiki2/src/models/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki2/src/models/MANIFEST.in b/docs/tutorials/wiki2/src/models/MANIFEST.in
index 42cd299b5..05cc195d9 100644
--- a/docs/tutorials/wiki2/src/models/MANIFEST.in
+++ b/docs/tutorials/wiki2/src/models/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki2/src/models/README.txt b/docs/tutorials/wiki2/src/models/README.txt
index 5b0101e5f..81102a869 100644
--- a/docs/tutorials/wiki2/src/models/README.txt
+++ b/docs/tutorials/wiki2/src/models/README.txt
@@ -1,14 +1,33 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/initialize_tutorial_db development.ini
+- Create a Python virtual environment.
-- $VENV/bin/pserve development.ini
+ python3 -m venv env
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Configure the database.
+
+ env/bin/initialize_tutorial_db development.ini
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini
index 22b733e10..be80882a5 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,7 +13,6 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
@@ -27,12 +26,11 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 d2ecfe22a..c01ad9a7e 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,14 +14,17 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/models/pytest.ini b/docs/tutorials/wiki2/src/models/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py
index 742a7c59c..faf76aa27 100644
--- a/docs/tutorials/wiki2/src/models/setup.py
+++ b/docs/tutorials/wiki2/src/models/setup.py
@@ -18,39 +18,42 @@ requires = [
'transaction',
'zope.sqlalchemy',
'waitress',
- ]
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web wsgi bfg pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- [console_scripts]
- initialize_tutorial_db = tutorial.scripts.initializedb:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ 'console_scripts': [
+ 'initialize_tutorial_db = tutorial.scripts.initializedb:main',
+ ],
+ },
+)
diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2
index ab8c5ea3d..1f658c834 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2
@@ -8,7 +8,7 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>Alchemy Scaffold for The Pyramid Web Framework</title>
+ <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">
@@ -29,7 +29,7 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png') }}" alt="pyramid web framework">
</div>
<div class="col-md-10">
{% block content %}
@@ -40,10 +40,8 @@
<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><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-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>
</ul>
</div>
diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2
index 6b49869c4..359f55ffa 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2
+++ b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2
@@ -2,7 +2,7 @@
{% block content %}
<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+ <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>
</div>
{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/models/tutorial/tests.py b/docs/tutorials/wiki2/src/models/tutorial/tests.py
index 99e95efd3..ce650ca7c 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/tests.py
@@ -54,7 +54,7 @@ class TestMyViewSuccessCondition(BaseTest):
from .views.default import my_view
info = my_view(dummy_request(self.session))
self.assertEqual(info['one'].name, 'one')
- self.assertEqual(info['project'], 'tutorial')
+ self.assertEqual(info['project'], 'myproj')
class TestMyViewFailureCondition(BaseTest):
diff --git a/docs/tutorials/wiki2/src/models/tutorial/views/default.py b/docs/tutorials/wiki2/src/models/tutorial/views/default.py
index ad0c728d7..a404d4154 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/views/default.py
@@ -13,7 +13,7 @@ def my_view(request):
one = query.filter(MyModel.name == 'one').first()
except DBAPIError:
return Response(db_err_msg, content_type='text/plain', status=500)
- return {'one': one, 'project': 'tutorial'}
+ return {'one': one, 'project': 'myproj'}
db_err_msg = """\
diff --git a/docs/tutorials/wiki2/src/tests/.coveragerc b/docs/tutorials/wiki2/src/tests/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki2/src/tests/CHANGES.txt b/docs/tutorials/wiki2/src/tests/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki2/src/tests/CHANGES.txt
+++ b/docs/tutorials/wiki2/src/tests/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki2/src/tests/MANIFEST.in b/docs/tutorials/wiki2/src/tests/MANIFEST.in
index 42cd299b5..05cc195d9 100644
--- a/docs/tutorials/wiki2/src/tests/MANIFEST.in
+++ b/docs/tutorials/wiki2/src/tests/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki2/src/tests/README.txt b/docs/tutorials/wiki2/src/tests/README.txt
index 5b0101e5f..81102a869 100644
--- a/docs/tutorials/wiki2/src/tests/README.txt
+++ b/docs/tutorials/wiki2/src/tests/README.txt
@@ -1,14 +1,33 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/initialize_tutorial_db development.ini
+- Create a Python virtual environment.
-- $VENV/bin/pserve development.ini
+ python3 -m venv env
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Configure the database.
+
+ env/bin/initialize_tutorial_db development.ini
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini
index 4a6c9325c..0786c1f66 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,7 +13,6 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
@@ -29,12 +28,11 @@ auth.secret = seekrit
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 a13a0ca19..05d60feec 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -16,14 +16,17 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
auth.secret = real-seekrit
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/tests/pytest.ini b/docs/tutorials/wiki2/src/tests/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py
index def3ce1f6..cc1aa421c 100644
--- a/docs/tutorials/wiki2/src/tests/setup.py
+++ b/docs/tutorials/wiki2/src/tests/setup.py
@@ -19,39 +19,42 @@ requires = [
'transaction',
'zope.sqlalchemy',
'waitress',
- ]
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web wsgi bfg pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- [console_scripts]
- initialize_tutorial_db = tutorial.scripts.initializedb:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ 'console_scripts': [
+ 'initialize_tutorial_db = tutorial.scripts.initializedb:main',
+ ],
+ },
+)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py
index f3c0a6fef..c860ef8cf 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py
@@ -28,6 +28,7 @@ def usage(argv):
def main(argv=sys.argv):
if len(argv) < 2:
usage(argv)
+ return
config_uri = argv[1]
options = parse_vars(argv[2:])
setup_logging(config_uri)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py
index 715768b2e..0250e71c9 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py
@@ -11,6 +11,9 @@ class FunctionalTests(unittest.TestCase):
basic_wrong_login = (
'/login?login=basic&password=incorrect'
'&next=FrontPage&form.submitted=Login')
+ basic_login_no_next = (
+ '/login?login=basic&password=basic'
+ '&form.submitted=Login')
editor_login = (
'/login?login=editor&password=editor'
'&next=FrontPage&form.submitted=Login')
@@ -68,6 +71,10 @@ class FunctionalTests(unittest.TestCase):
res = self.testapp.get(self.basic_login, status=302)
self.assertEqual(res.location, 'http://localhost/FrontPage')
+ def test_successful_log_in_no_next(self):
+ res = self.testapp.get(self.basic_login_no_next, status=302)
+ self.assertEqual(res.location, 'http://localhost/')
+
def test_failed_log_in(self):
res = self.testapp.get(self.basic_wrong_login, status=200)
self.assertTrue(b'login' in res.body)
@@ -120,3 +127,8 @@ class FunctionalTests(unittest.TestCase):
self.testapp.get(self.editor_login, status=302)
res = self.testapp.get('/FrontPage', status=200)
self.assertTrue(b'FrontPage' in res.body)
+
+ def test_redirect_to_edit_for_existing_page(self):
+ self.testapp.get(self.editor_login, status=302)
+ res = self.testapp.get('/add_page/FrontPage', status=302)
+ self.assertTrue(b'FrontPage' in res.body)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_initdb.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_initdb.py
new file mode 100644
index 000000000..f5273456e
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_initdb.py
@@ -0,0 +1,16 @@
+import os
+import unittest
+
+
+class TestInitializeDB(unittest.TestCase):
+
+ def test_usage(self):
+ from ..scripts.initializedb import main
+ with self.assertRaises(SystemExit):
+ main(argv=['foo'])
+
+ def test_run(self):
+ from ..scripts.initializedb import main
+ main(argv=['foo', 'development.ini'])
+ self.assertTrue(os.path.exists('tutorial.sqlite'))
+ os.remove('tutorial.sqlite')
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_security.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_security.py
new file mode 100644
index 000000000..cbec6420d
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_security.py
@@ -0,0 +1,23 @@
+import unittest
+from pyramid.testing import DummyRequest
+
+
+class TestMyAuthenticationPolicy(unittest.TestCase):
+
+ def test_no_user(self):
+ request = DummyRequest()
+ request.user = None
+
+ from ..security import MyAuthenticationPolicy
+ policy = MyAuthenticationPolicy(None)
+ self.assertEqual(policy.authenticated_userid(request), None)
+
+ def test_authenticated_user(self):
+ from ..models import User
+ request = DummyRequest()
+ request.user = User()
+ request.user.id = 'foo'
+
+ from ..security import MyAuthenticationPolicy
+ policy = MyAuthenticationPolicy(None)
+ self.assertEqual(policy.authenticated_userid(request), 'foo')
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_user_model.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_user_model.py
new file mode 100644
index 000000000..9490ac990
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_user_model.py
@@ -0,0 +1,67 @@
+import unittest
+import transaction
+
+from pyramid import testing
+
+
+class BaseTest(unittest.TestCase):
+
+ def setUp(self):
+ from ..models import get_tm_session
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('..models')
+ self.config.include('..routes')
+
+ session_factory = self.config.registry['dbsession_factory']
+ self.session = get_tm_session(session_factory, transaction.manager)
+
+ self.init_database()
+
+ def init_database(self):
+ from ..models.meta import Base
+ session_factory = self.config.registry['dbsession_factory']
+ engine = session_factory.kw['bind']
+ Base.metadata.create_all(engine)
+
+ def tearDown(self):
+ testing.tearDown()
+ transaction.abort()
+
+ def makeUser(self, name, role):
+ from ..models import User
+ return User(name=name, role=role)
+
+
+class TestSetPassword(BaseTest):
+
+ def test_password_hash_saved(self):
+ user = self.makeUser(name='foo', role='bar')
+ self.assertFalse(user.password_hash)
+
+ user.set_password('secret')
+ self.assertTrue(user.password_hash)
+
+
+class TestCheckPassword(BaseTest):
+
+ def test_password_hash_not_set(self):
+ user = self.makeUser(name='foo', role='bar')
+ self.assertFalse(user.password_hash)
+
+ self.assertFalse(user.check_password('secret'))
+
+ def test_correct_password(self):
+ user = self.makeUser(name='foo', role='bar')
+ user.set_password('secret')
+ self.assertTrue(user.password_hash)
+
+ self.assertTrue(user.check_password('secret'))
+
+ def test_incorrect_password(self):
+ user = self.makeUser(name='foo', role='bar')
+ user.set_password('secret')
+ self.assertTrue(user.password_hash)
+
+ self.assertFalse(user.check_password('incorrect'))
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/.coveragerc b/docs/tutorials/wiki2/src/views/.coveragerc
new file mode 100644
index 000000000..a1d87d03d
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+source = tutorial
+omit = tutorial/test*
diff --git a/docs/tutorials/wiki2/src/views/CHANGES.txt b/docs/tutorials/wiki2/src/views/CHANGES.txt
index 35a34f332..14b902fd1 100644
--- a/docs/tutorials/wiki2/src/views/CHANGES.txt
+++ b/docs/tutorials/wiki2/src/views/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version.
diff --git a/docs/tutorials/wiki2/src/views/MANIFEST.in b/docs/tutorials/wiki2/src/views/MANIFEST.in
index 42cd299b5..05cc195d9 100644
--- a/docs/tutorials/wiki2/src/views/MANIFEST.in
+++ b/docs/tutorials/wiki2/src/views/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki2/src/views/README.txt b/docs/tutorials/wiki2/src/views/README.txt
index 5b0101e5f..81102a869 100644
--- a/docs/tutorials/wiki2/src/views/README.txt
+++ b/docs/tutorials/wiki2/src/views/README.txt
@@ -1,14 +1,33 @@
-tutorial README
-==================
+myproj
+======
Getting Started
---------------
-- cd <directory containing this file>
+- Change directory into your newly created project.
-- $VENV/bin/pip install -e .
+ cd tutorial
-- $VENV/bin/initialize_tutorial_db development.ini
+- Create a Python virtual environment.
-- $VENV/bin/pserve development.ini
+ python3 -m venv env
+- Upgrade packaging tools.
+
+ env/bin/pip install --upgrade pip setuptools wheel
+
+- Install the project in editable mode with its testing requirements.
+
+ env/bin/pip install -e ".[testing]"
+
+- Configure the database.
+
+ env/bin/initialize_tutorial_db development.ini
+
+- Run your project's tests.
+
+ env/bin/pytest
+
+- Run your project.
+
+ env/bin/pserve development.ini
diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini
index 22b733e10..be80882a5 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -13,7 +13,6 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
@@ -27,12 +26,11 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://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 d2ecfe22a..c01ad9a7e 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/1.7-branch/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
@@ -14,14 +14,17 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+###
+# wsgi server configuration
+###
+
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
diff --git a/docs/tutorials/wiki2/src/views/pytest.ini b/docs/tutorials/wiki2/src/views/pytest.ini
new file mode 100644
index 000000000..8b76bc410
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+testpaths = tutorial
+python_files = *.py
diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py
index def3ce1f6..cc1aa421c 100644
--- a/docs/tutorials/wiki2/src/views/setup.py
+++ b/docs/tutorials/wiki2/src/views/setup.py
@@ -19,39 +19,42 @@ requires = [
'transaction',
'zope.sqlalchemy',
'waitress',
- ]
+]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'pytest',
'pytest-cov',
- ]
+]
-setup(name='tutorial',
- version='0.0',
- description='tutorial',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
- author='',
- author_email='',
- url='',
- keywords='web wsgi bfg pylons pyramid',
- packages=find_packages(),
- include_package_data=True,
- zip_safe=False,
- extras_require={
- 'testing': tests_require,
- },
- install_requires=requires,
- entry_points="""\
- [paste.app_factory]
- main = tutorial:main
- [console_scripts]
- initialize_tutorial_db = tutorial.scripts.initializedb:main
- """,
- )
+setup(
+ name='tutorial',
+ version='0.0',
+ description='myproj',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ 'Programming Language :: Python',
+ 'Framework :: Pyramid',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pyramid pylons',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points={
+ 'paste.app_factory': [
+ 'main = tutorial:main',
+ ],
+ 'console_scripts': [
+ 'initialize_tutorial_db = tutorial.scripts.initializedb:main',
+ ],
+ },
+)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2
index 71785157f..7575de8a7 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2
+++ b/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2
@@ -29,7 +29,7 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png') }}" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div class="content">
diff --git a/docs/tutorials/wiki2/src/views/tutorial/tests.py b/docs/tutorials/wiki2/src/views/tutorial/tests.py
index 99e95efd3..ce650ca7c 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/tests.py
@@ -54,7 +54,7 @@ class TestMyViewSuccessCondition(BaseTest):
from .views.default import my_view
info = my_view(dummy_request(self.session))
self.assertEqual(info['one'].name, 'one')
- self.assertEqual(info['project'], 'tutorial')
+ self.assertEqual(info['project'], 'myproj')
class TestMyViewFailureCondition(BaseTest):
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 e923ff9cb..e1f83a755 100644
--- a/docs/tutorials/wiki2/tests.rst
+++ b/docs/tutorials/wiki2/tests.rst
@@ -8,7 +8,7 @@ We will now add tests for the models and views as well as a few functional
tests in a new ``tests`` subpackage. Tests ensure that an application works,
and that it continues to work when changes are made in the future.
-The file ``tests.py`` was generated as part of the ``alchemy`` scaffold, but it
+The file ``tests.py`` was generated as part of the ``alchemy`` cookiecutter, but it
is a common practice to put tests into a ``tests`` subpackage, especially as
projects grow in size and complexity. Each module in the test subpackage
should contain tests for its corresponding module in our application. Each
@@ -50,20 +50,36 @@ so on.
View the results of all our edits to ``tests`` subpackage
=========================================================
-Open ``tutorial/tests/test_views.py``, and edit it such that it appears as
-follows:
+Create ``tutorial/tests/test_views.py`` such that it appears as follows:
.. literalinclude:: src/tests/tutorial/tests/test_views.py
:linenos:
:language: python
-Open ``tutorial/tests/test_functional.py``, and edit it such that it appears as
-follows:
+Create ``tutorial/tests/test_functional.py`` such that it appears as follows:
.. literalinclude:: src/tests/tutorial/tests/test_functional.py
:linenos:
:language: python
+Create ``tutorial/tests/test_initdb.py`` such that it appears as follows:
+
+.. literalinclude:: src/tests/tutorial/tests/test_initdb.py
+ :linenos:
+ :language: python
+
+Create ``tutorial/tests/test_security.py`` such that it appears as follows:
+
+.. literalinclude:: src/tests/tutorial/tests/test_security.py
+ :linenos:
+ :language: python
+
+Create ``tutorial/tests/test_user_model.py`` such that it appears as follows:
+
+.. literalinclude:: src/tests/tutorial/tests/test_user_model.py
+ :linenos:
+ :language: python
+
.. note::
@@ -77,32 +93,27 @@ follows:
Running the tests
=================
-We can run these tests similarly to how we did in :ref:`running_tests`:
+We can run these tests similarly to how we did in :ref:`running_tests`, but first delete the SQLite database ``tutorial.sqlite``. If you do not delete the database, then you will see an integrity error when running the tests.
On UNIX:
.. code-block:: bash
+ $ rm tutorial.sqlite
$ $VENV/bin/py.test -q
On Windows:
.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q
+ c:\tutorial> del tutorial.sqlite
+ c:\tutorial> %VENV%\Scripts\py.test -q
The expected result should look like the following:
.. code-block:: text
- ......................
- 22 passed, 1 pytest-warnings in 5.81 seconds
-
-.. note:: If you use Python 3 during this tutorial, you will see deprecation
- warnings in the output, which we will choose to ignore. In making this
- tutorial run on both Python 2 and 3, the authors prioritized simplicity and
- focus for the learner over accommodating warnings. In your own app or as
- extra credit, you may choose to either drop Python 2 support or hack your
- code to work without warnings on both Python 2 and 3.
+ ................................
+ 32 passed in 9.90 seconds
.. _webtest: http://docs.pylonsproject.org/projects/webtest/en/latest/
diff --git a/docs/typographical-conventions.rst b/docs/typographical-conventions.rst
new file mode 100644
index 000000000..5efc49682
--- /dev/null
+++ b/docs/typographical-conventions.rst
@@ -0,0 +1,338 @@
+.. _typographical-conventions:
+
+Typographical Conventions
+=========================
+
+.. meta::
+ :description: This chapter describes typographical conventions used in the Pyramid documentation.
+ :keywords: Pyramid, Typographical Conventions
+
+
+.. _typographical-conventions-introduction:
+
+Introduction
+------------
+
+This chapter describes typographical conventions used in the Pyramid documentation.
+
+
+.. _typographical-conventions-glossary:
+
+Glossary
+--------
+
+A glossary defines terms used throughout the documentation. References to glossary terms appear as follows.
+
+:term:`request`
+
+Note it is hyperlinked, and when clicked it will take the user to the term in the Glossary and highlight the term.
+
+
+.. _typographical-conventions-links:
+
+Links
+-----
+
+Links are presented as follows, and may be clickable.
+
+`TryPyramid <https://TryPyramid.com>`_
+
+.. seealso:: See also :ref:`typographical-conventions-cross-references` for other links within the documentation.
+
+
+.. _typographical-conventions-topic:
+
+Topic
+-----
+
+A topic is similar to a block quote with a title, or a self-contained section with no subsections. A topic indicates a self-contained idea that is separate from the flow of the document. Topics may occur anywhere a section or transition may occur.
+
+.. topic:: Topic Title
+
+ Subsequent indented lines comprise
+ the body of the topic, and are
+ interpreted as body elements.
+
+
+.. _typographical-conventions-displaying-code:
+
+Code
+----
+
+Code may be displayed in blocks or inline. Blocks of code may use syntax highlighting, line numbering, and emphasis.
+
+
+.. _typographical-conventions-syntax-highlighting:
+
+Syntax highlighting
+^^^^^^^^^^^^^^^^^^^
+
+XML:
+
+.. code-block:: xml
+
+ <somesnippet>Some XML</somesnippet>
+
+Unix shell commands are prefixed with a ``$`` character. (See :term:`venv` for the meaning of ``$VENV``.)
+
+.. code-block:: bash
+
+ $ $VENV/bin/pip install -e .
+
+Windows commands are prefixed with a drive letter with an optional directory name. (See :term:`venv` for the meaning of ``%VENV%``.)
+
+.. code-block:: doscon
+
+ c:\> %VENV%\Scripts\pserve development.ini
+
+cfg:
+
+.. code-block:: cfg
+
+ [some-part]
+ # A random part in the buildout
+ recipe = collective.recipe.foo
+ option = value
+
+ini:
+
+.. code-block:: ini
+
+ [nosetests]
+ match=^test
+ where=pyramid
+ nocapture=1
+
+Interactive Python:
+
+.. code-block:: pycon
+
+ >>> class Foo:
+ ... bar = 100
+ ...
+ >>> f = Foo()
+ >>> f.bar
+ 100
+ >>> f.bar / 0
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ ZeroDivisionError: integer division or modulo by zero
+
+
+.. _typographical-conventions-long-commands:
+
+Displaying long commands
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+When a command that should be typed on one line is too long to fit on the displayed width of a page, the backslash character ``\`` is used to indicate that the subsequent printed line should be part of the command:
+
+.. code-block:: bash
+
+ $ $VENV/bin/py.test tutorial/tests.py --cov-report term-missing \
+ --cov=tutorial -q
+
+
+.. _typographical-conventions-code-block-options:
+
+Code block options
+^^^^^^^^^^^^^^^^^^
+
+To emphasize lines, we give the appearance that a highlighting pen has been used on the code.
+
+.. code-block:: python
+ :emphasize-lines: 1,3
+
+ if "foo" == "bar":
+ # This is Python code
+ pass
+
+A code block with line numbers.
+
+.. code-block:: python
+ :linenos:
+
+ if "foo" == "bar":
+ # This is Python code
+ pass
+
+Some code blocks may be given a caption.
+
+.. code-block:: python
+ :caption: sample.py
+ :name: sample-py-typographical-conventions
+
+ if "foo" == "bar":
+ # This is Python code
+ pass
+
+
+.. _typographical-conventions-inline-code:
+
+Inline code
+^^^^^^^^^^^
+
+Inline code is displayed as follows, where the inline code is 'pip install -e ".[docs]"'.
+
+Install requirements for building documentation: ``pip install -e ".[docs]"``
+
+
+.. _typographical-conventions-feature-versioning:
+
+Feature versioning
+------------------
+
+We designate the version in which something is added, changed, or deprecated in the project.
+
+
+.. _typographical-conventions-version-added:
+
+Version added
+^^^^^^^^^^^^^
+
+The version in which a feature is added to a project is displayed as follows.
+
+.. versionadded:: 1.1
+ :func:`pyramid.paster.bootstrap`
+
+
+.. _typographical-conventions-version-changed:
+
+Version changed
+^^^^^^^^^^^^^^^
+
+The version in which a feature is changed in a project is displayed as follows.
+
+.. versionchanged:: 1.8
+ Added the ability for ``bootstrap`` to cleanup automatically via the ``with`` statement.
+
+
+.. _typographical-conventions-deprecated:
+
+Deprecated
+^^^^^^^^^^
+
+The version in which a feature is deprecated in a project is displayed as follows.
+
+.. deprecated:: 1.7
+ Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised.
+
+
+.. _typographical-conventions-danger:
+
+Danger
+------
+
+Danger represents critical information related to a topic or concept, and should recommend to the user "don't do this dangerous thing".
+
+.. danger::
+
+ This is danger or an error.
+
+
+.. _typographical-conventions-warnings:
+
+Warnings
+--------
+
+Warnings represent limitations and advice related to a topic or concept.
+
+.. warning::
+
+ This is a warning.
+
+
+.. _typographical-conventions-notes:
+
+Notes
+-----
+
+Notes represent additional information related to a topic or concept.
+
+.. note::
+
+ This is a note.
+
+
+.. _typographical-conventions-see-also:
+
+See also
+--------
+
+"See also" messages refer to topics that are related to the current topic, but have a narrative tone to them instead of merely a link without explanation. "See also" is rendered in a block as well, so that it stands out for the reader's attention.
+
+.. seealso::
+
+ See :ref:`Quick Tutorial section on Requirements <qtut_requirements>`.
+
+
+.. _typographical-conventions-todo:
+
+Todo
+----
+
+Todo items designated tasks that require further work.
+
+.. todo::
+
+ This is a todo item.
+
+
+.. _typographical-conventions-cross-references:
+
+Cross-references
+----------------
+
+Cross-references are links that may be to a document, arbitrary location, object, or other items.
+
+
+.. _typographical-conventions-cross-referencing-documents:
+
+Cross-referencing documents
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Links to pages within this documentation display as follows.
+
+:doc:`quick_tour`
+
+
+.. _typographical-conventions-cross-referencing-arbitrary-locations:
+
+Cross-referencing arbitrary locations
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Links to sections, and tables and figures with captions, within this documentation display as follows.
+
+:ref:`i18n_chapter`
+
+
+.. _typographical-conventions-cross-referencing-python:
+
+Python modules, classes, methods, and functions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+All of the following are clickable links to Python modules, classes, methods, and functions.
+
+Python module names display as follows.
+
+:mod:`pyramid.config`
+
+Python class names display as follows.
+
+:class:`pyramid.config.Configurator`
+
+Python method names display as follows.
+
+:meth:`pyramid.config.Configurator.add_view`
+
+Python function names display as follows.
+
+:func:`pyramid.renderers.render_to_response`
+
+Sometimes we show only the last segment of a Python object's name, which displays as follows.
+
+:func:`~pyramid.renderers.render_to_response`
+
+The application "Pyramid" itself displays as follows.
+
+:app:`Pyramid`
+
diff --git a/docs/whatsnew-1.7.rst b/docs/whatsnew-1.7.rst
index 398b12f01..c5f611f04 100644
--- a/docs/whatsnew-1.7.rst
+++ b/docs/whatsnew-1.7.rst
@@ -126,7 +126,7 @@ Feature Additions
- The :attr:`pyramid.tweens.EXCVIEW` tween will now re-raise the original
exception if no exception view could be found to handle it. This allows
- the exception to be handled upstream by another tween or middelware.
+ the exception to be handled upstream by another tween or middleware.
See https://github.com/Pylons/pyramid/pull/2567
Deprecations
diff --git a/docs/whatsnew-1.8.rst b/docs/whatsnew-1.8.rst
new file mode 100644
index 000000000..ff16c1a4b
--- /dev/null
+++ b/docs/whatsnew-1.8.rst
@@ -0,0 +1,234 @@
+What's New in Pyramid 1.8
+=========================
+
+This article explains the new features in :app:`Pyramid` version 1.8 as
+compared to its predecessor, :app:`Pyramid` 1.7. It also documents backwards
+incompatibilities between the two versions and deprecations added to
+:app:`Pyramid` 1.8, as well as software dependency changes and notable
+documentation additions.
+
+Major Feature Additions
+-----------------------
+
+- Added :meth:`pyramid.config.Configurator.add_exception_view` and the
+ :func:`pyramid.view.exception_view_config` decorator. It is now possible
+ using these methods or via the new ``exception_only=True`` option to
+ :meth:`pyramid.config.Configurator.add_view` to add a view which will only
+ be matched when handling an exception. Previously, any exception views were
+ also registered for a traversal context that inherited from the exception
+ class which prevented any exception-only optimizations.
+ See https://github.com/Pylons/pyramid/pull/2660
+
+- ``pserve --reload`` now uses the
+ `hupper <http://docs.pylonsproject.org/projects/hupper/en/latest/>`_
+ library to monitor file changes. This comes with many improvements:
+
+ - If the `watchdog <http://pythonhosted.org/watchdog/>`_ package is
+ installed then monitoring will be done using inotify instead of
+ cpu and disk-intensive polling.
+
+ - The monitor is now a separate process that will not crash and starts up
+ before any of your code.
+
+ - The monitor will not restart the process after a crash until a file is
+ saved.
+
+ - The monitor works on windows.
+
+ - You can now trigger a reload manually from a pyramid view or any other
+ code via ``hupper.get_reloader().trigger_reload()``. Kind of neat.
+
+ - You can trigger a reload by issuing a ``SIGHUP`` to the monitor process.
+
+ See https://github.com/Pylons/pyramid/pull/2805
+
+Minor Feature Additions
+-----------------------
+
+- Python 3.6 compatibility.
+ https://github.com/Pylons/pyramid/issues/2835
+
+- The ``_get_credentials`` private method of
+ :class:`pyramid.authentication.BasicAuthAuthenticationPolicy`
+ has been extracted into standalone function
+ :func:`pyramid.authentication.extract_http_basic_credentials`, this function
+ extracts HTTP Basic credentials from a ``request`` object, and returns them
+ as a named tuple. See https://github.com/Pylons/pyramid/pull/2662
+
+- Pyramid 1.4 silently dropped a feature of the configurator that has been
+ restored. It's again possible for action discriminators to conflict across
+ different action orders.
+ See https://github.com/Pylons/pyramid/pull/2757
+
+- :func:`pyramid.paster.bootstrap` and its sibling
+ :func:`pyramid.scripting.prepare` can now be used as context managers to
+ automatically invoke the ``closer`` and pop threadlocals off of the stack
+ to prevent memory leaks. See https://github.com/Pylons/pyramid/pull/2760
+
+- Added the ``exception_only`` boolean to
+ :class:`pyramid.interfaces.IViewDeriverInfo` which can be used by view
+ derivers to determine if they are wrapping a view which only handles
+ exceptions. This means that it is no longer necessary to perform request-time
+ checks for ``request.exception`` to determine if the view is handling an
+ exception - the pipeline can be optimized at config-time.
+ See https://github.com/Pylons/pyramid/pull/2660
+
+- ``pcreate`` learned about ``--package-name`` to allow you to create a new
+ project in an existing folder with a different package name than the project
+ name. See https://github.com/Pylons/pyramid/pull/2783
+
+- ``pserve`` should now work with ``gevent`` and other workers that need
+ to monkeypatch the process, assuming the server and / or the app do so
+ as soon as possible before importing the rest of pyramid.
+ See https://github.com/Pylons/pyramid/pull/2797
+
+- Pyramid no longer copies the settings object passed to the
+ ``pyramid.config.Configurator(settings=)``. The original ``dict`` is kept.
+ See https://github.com/Pylons/pyramid/pull/2823
+
+- The csrf trusted origins setting may now be a whitespace-separated list of
+ domains. Previously only a python list was allowed. Also, it can now be set
+ using the ``PYRAMID_CSRF_TRUSTED_ORIGINS`` environment variable similar to
+ other settings. See https://github.com/Pylons/pyramid/pull/2823
+
+- A new ``[pserve]`` section is supported in your config files with a
+ ``watch_files`` key that can configure ``pserve --reload`` to monitor custom
+ file paths. See https://github.com/Pylons/pyramid/pull/2827
+
+- Allow streaming responses to be made from subclasses of
+ :class:`pyramid.httpexceptions.HTTPException`. Previously the response would
+ be unrolled while testing for a body, making it impossible to stream
+ a response.
+ See https://github.com/Pylons/pyramid/pull/2863
+
+- Update starter, alchemy and zodb scaffolds to support IPv6 by using the
+ new ``listen`` directives in waitress.
+ See https://github.com/Pylons/pyramid/pull/2853
+
+- All p* scripts now use argparse instead of optparse. This improves their
+ ``--help`` output as well as enabling nicer documentation of their options.
+ See https://github.com/Pylons/pyramid/pull/2864
+
+- Added an ``override`` option to
+ :meth:`pyramid.config.Configurator.add_translation_dirs` to allow
+ 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
+---------------------------
+
+- Following the Pyramid deprecation period (1.6 -> 1.8),
+ daemon support for pserve has been removed. This includes removing the
+ daemon commands (start, stop, restart, status) as well as the following
+ arguments: ``--daemon``, ``--pid-file``, ``--log-file``,
+ ``--monitor-restart``, ``--status``, ``--user``, ``--group``,
+ ``--stop-daemon``
+
+ To run your server as a daemon you should use a process manager instead of
+ pserve.
+
+ See https://github.com/Pylons/pyramid/pull/2615
+
+- Change static view to avoid setting the ``Content-Encoding`` response header
+ to an encoding guessed using Python's ``mimetypes`` module. This was causing
+ clients to decode the content of gzipped files when downloading them. The
+ client would end up with a ``foo.txt.gz`` file on disk that was already
+ decoded, thus should really be ``foo.txt``. Also, the ``Content-Encoding``
+ should only have been used if the client itself broadcast support for the
+ encoding via ``Accept-Encoding`` request headers.
+ See https://github.com/Pylons/pyramid/pull/2810
+
+- ``pcreate`` is now interactive by default. You will be prompted if a file
+ already exists with different content. Previously if there were similar
+ files it would silently skip them unless you specified ``--interactive``
+ or ``--overwrite``.
+ See https://github.com/Pylons/pyramid/pull/2775
+
+- Support for the ``IContextURL`` interface that was deprecated in Pyramid 1.3
+ has been removed. See https://github.com/Pylons/pyramid/pull/2822
+
+- Settings are no longer accessible as attributes on the settings object
+ (e.g. ``request.registry.settings.foo``). This was deprecated in Pyramid 1.2.
+ See https://github.com/Pylons/pyramid/pull/2823
+
+- Removed undocumented argument ``cachebust_match`` from
+ ``pyramid.static.static_view``. This argument was shipped accidentally
+ in Pyramid 1.6. See https://github.com/Pylons/pyramid/pull/2681
+
+Deprecations
+------------
+
+- The ``pcreate`` script and the core scaffolds (``starter``, ``alchemy`` and
+ ``zodb``) have been deprecated.
+
+ They have been replaced with the decision to embrace the popular
+ `cookiecutter <https://cookiecutter.readthedocs.io/en/latest/>`_ project
+ as a best-of-breed project templating solution.
+
+ ``pcreate`` was originally introduced when very few alternatives existed
+ that supported Python 3. Fortunately the situation has improved and
+ with possible tooling support for cookiecutters being discussed by major
+ IDEs, and the simplicity of the jinja2 syntax, it is exciting to embrace
+ the project moving forward!
+
+ All of Pyramid's official scaffolds as well as the tutorials have been
+ ported to cookiecutters:
+
+ - `pyramid-cookiecutter-starter
+ <https://github.com/Pylons/pyramid-cookiecutter-starter>`_
+
+ - `pyramid-cookiecutter-alchemy
+ <https://github.com/Pylons/pyramid-cookiecutter-alchemy>`_
+
+ - `pyramid-cookiecutter-zodb
+ <https://github.com/Pylons/pyramid-cookiecutter-zodb>`_
+
+ See https://github.com/Pylons/pyramid/pull/2780
+
+Documentation Enhancements
+--------------------------
+
+- Update Typographical Conventions.
+ https://github.com/Pylons/pyramid/pull/2838
+
+- Add `pyramid_nacl_session
+ <http://docs.pylonsproject.org/projects/pyramid-nacl-session/en/latest/>`_
+ to session factories. See https://github.com/Pylons/pyramid/issues/2791
+
+- Update HACKING.txt from stale branch that was never merged to master.
+ See https://github.com/Pylons/pyramid/pull/2782
+
+- Updated Windows installation instructions and related bits.
+ See https://github.com/Pylons/pyramid/issues/2661
+
+- Fix an inconsistency in the documentation between view predicates and
+ route predicates and highlight the differences in their APIs.
+ See https://github.com/Pylons/pyramid/pull/2764
+
+- Clarify a possible misuse of the ``headers`` kwarg to subclasses of
+ :class:`pyramid.httpexceptions.HTTPException` in which more appropriate
+ kwargs from the parent class :class:`pyramid.response.Response` should be
+ used instead. See https://github.com/Pylons/pyramid/pull/2750
+
+- The SQLAlchemy + URL Dispatch + Jinja2 (``wiki2``) and
+ ZODB + Traversal + Chameleon (``wiki``) tutorials have been updated to
+ utilize the new cookiecutters and drop support for the ``pcreate``
+ scaffolds.
+
+ See https://github.com/Pylons/pyramid/pull/2881 and
+ https://github.com/Pylons/pyramid/pull/2883.
+
+- Quick Tour, Quick Tutorial, and most files throughout the documentation have
+ been updated to use cookiecutters instead of pcreate and scaffolds.
+ See https://github.com/Pylons/pyramid/pull/2888 and
+ https://github.com/Pylons/pyramid/pull/2889
+
+- Updated the ``mod_wsgi`` tutorial to use cookiecutters and Apache 2.4+.
+ See https://github.com/Pylons/pyramid/pull/2901
diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst
new file mode 100644
index 000000000..0ba29625c
--- /dev/null
+++ b/docs/whatsnew-1.9.rst
@@ -0,0 +1,61 @@
+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 <http://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 <http://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 :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
+
+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`.
+
+ 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/asset.py b/pyramid/asset.py
index e6a145341..9d7a3ee63 100644
--- a/pyramid/asset.py
+++ b/pyramid/asset.py
@@ -33,7 +33,7 @@ def asset_spec_from_abspath(abspath, package):
relpath.replace(os.path.sep, '/'))
return abspath
-# bw compat only; use pyramid.path.AssetDescriptor.abspath() instead
+# bw compat only; use pyramid.path.AssetResolver().resolve(spec).abspath()
def abspath_from_asset_spec(spec, pname='__main__'):
if pname is None:
return spec
diff --git a/pyramid/authentication.py b/pyramid/authentication.py
index 2ee5576d9..03b204e1a 100644
--- a/pyramid/authentication.py
+++ b/pyramid/authentication.py
@@ -1098,7 +1098,7 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy):
""" The userid parsed from the ``Authorization`` request header."""
credentials = extract_http_basic_credentials(request)
if credentials:
- return credentials[0]
+ return credentials.username
def remember(self, request, userid, **kw):
""" A no-op. Basic authentication does not provide a protocol for
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index d4064dc78..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)
@@ -451,9 +468,6 @@ class Configurator(
return filename # absolute filename
return '%s:%s' % (package, filename)
- def _split_spec(self, path_or_spec):
- return resolve_asset_spec(path_or_spec, self.package_name)
-
def _fix_registry(self):
""" Fix up a ZCA component registry that is not a
pyramid.registry.Registry by adding analogues of ``has_listeners``,
@@ -606,11 +620,15 @@ class Configurator(
if autocommit:
# callables can depend on the side effects of resolving a
# deferred discriminator
- undefer(discriminator)
- if callable is not None:
- callable(*args, **kw)
- for introspectable in introspectables:
- introspectable.register(self.introspector, action_info)
+ self.begin()
+ try:
+ undefer(discriminator)
+ if callable is not None:
+ callable(*args, **kw)
+ for introspectable in introspectables:
+ introspectable.register(self.introspector, action_info)
+ finally:
+ self.end()
else:
action = extra
@@ -645,13 +663,27 @@ 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."""
- self.action_state.execute_actions(introspector=self.introspector)
+ 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)
+ finally:
+ self.end()
self.action_state = ActionState() # old actions have been processed
def include(self, callable, route_prefix=None):
@@ -748,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
@@ -797,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):
"""
@@ -885,14 +926,30 @@ class Configurator(
absolute_resource_spec = absolute_asset_spec # b/w compat forever
- def begin(self, request=None):
+ def begin(self, request=_marker):
""" Indicate that application or test configuration has begun.
This pushes a dictionary containing the :term:`application
registry` implied by ``registry`` attribute of this
configurator and the :term:`request` implied by the
``request`` argument onto the :term:`thread local` stack
consulted by various :mod:`pyramid.threadlocal` API
- functions."""
+ functions.
+
+ If ``request`` is not specified and the registry owned by the
+ configurator is already pushed as the current threadlocal registry
+ then this method will keep the current threadlocal request unchanged.
+
+ .. versionchanged:: 1.8
+ The current threadlocal request is propagated if the current
+ threadlocal registry remains unchanged.
+
+ """
+ if request is _marker:
+ current = self.manager.get()
+ if current['registry'] == self.registry:
+ request = current['request']
+ else:
+ request = None
self.manager.push({'registry':self.registry, 'request':request})
def end(self):
@@ -903,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):
@@ -992,11 +1059,11 @@ class Configurator(
# Push the registry onto the stack in case any code that depends on
# the registry threadlocal APIs used in listeners subscribed to the
# IApplicationCreated event.
- self.manager.push({'registry': self.registry, 'request': None})
+ self.begin()
try:
self.registry.notify(ApplicationCreated(app))
finally:
- self.manager.pop()
+ self.end()
return app
diff --git a/pyramid/config/assets.py b/pyramid/config/assets.py
index d05314384..6eafc1eb1 100644
--- a/pyramid/config/assets.py
+++ b/pyramid/config/assets.py
@@ -4,7 +4,10 @@ import sys
from zope.interface import implementer
-from pyramid.interfaces import IPackageOverrides
+from pyramid.interfaces import (
+ IPackageOverrides,
+ PHASE1_CONFIG,
+)
from pyramid.exceptions import ConfigurationError
from pyramid.threadlocal import get_current_registry
@@ -387,6 +390,7 @@ class AssetsConfiguratorMixin(object):
)
intr['to_override'] = to_override
intr['override_with'] = override_with
- self.action(None, register, introspectables=(intr,))
+ self.action(None, register, introspectables=(intr,),
+ order=PHASE1_CONFIG)
override_resource = override_asset # bw compat
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py
index f0b6252ae..c8633cc47 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 (
@@ -231,6 +233,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/i18n.py b/pyramid/config/i18n.py
index 69af0f9bc..9c5b7e682 100644
--- a/pyramid/config/i18n.py
+++ b/pyramid/config/i18n.py
@@ -1,13 +1,10 @@
-import os
-import sys
-
from pyramid.interfaces import (
ILocaleNegotiator,
ITranslationDirectories,
)
from pyramid.exceptions import ConfigurationError
-from pyramid.path import package_path
+from pyramid.path import AssetResolver
from pyramid.util import action_method
class I18NConfiguratorMixin(object):
@@ -45,7 +42,7 @@ class I18NConfiguratorMixin(object):
self.registry.registerUtility(locale_negotiator, ILocaleNegotiator)
@action_method
- def add_translation_dirs(self, *specs):
+ def add_translation_dirs(self, *specs, **kw):
""" Add one or more :term:`translation directory` paths to the
current configuration state. The ``specs`` argument is a
sequence that may contain absolute directory paths
@@ -60,48 +57,63 @@ class I18NConfiguratorMixin(object):
config.add_translation_dirs('/usr/share/locale',
'some.package:locale')
- Later calls to ``add_translation_dir`` insert directories into the
- beginning of the list of translation directories created by earlier
- calls. This means that the same translation found in a directory
- added later in the configuration process will be found before one
- added earlier in the configuration process. However, if multiple
- specs are provided in a single call to ``add_translation_dirs``, the
- directories will be inserted into the beginning of the directory list
- in the order they're provided in the ``*specs`` list argument (items
- earlier in the list trump ones later in the list).
- """
- directories = []
- introspectables = []
+ The translation directories are defined as a list in which
+ translations defined later have precedence over translations defined
+ earlier.
- for spec in specs[::-1]: # reversed
- package_name, filename = self._split_spec(spec)
- if package_name is None: # absolute filename
- directory = filename
- else:
- __import__(package_name)
- package = sys.modules[package_name]
- directory = os.path.join(package_path(package), filename)
-
- if not os.path.isdir(os.path.realpath(directory)):
- raise ConfigurationError('"%s" is not a directory' %
- directory)
- intr = self.introspectable('translation directories', directory,
- spec, 'translation directory')
- intr['directory'] = directory
- intr['spec'] = spec
- introspectables.append(intr)
- directories.append(directory)
+ By default, consecutive calls to ``add_translation_dirs`` will add
+ directories to the start of the list. This means later calls to
+ ``add_translation_dirs`` will have their translations trumped by
+ earlier calls. If you explicitly need this call to trump an earlier
+ call then you may set ``override`` to ``True``.
- def register():
- for directory in directories:
+ If multiple specs are provided in a single call to
+ ``add_translation_dirs``, the directories will be inserted in the
+ order they're provided (earlier items are trumped by later items).
+
+ .. versionchanged:: 1.8
- tdirs = self.registry.queryUtility(ITranslationDirectories)
- if tdirs is None:
- tdirs = []
- self.registry.registerUtility(tdirs,
- ITranslationDirectories)
+ The ``override`` parameter was added to allow a later call
+ to ``add_translation_dirs`` to override an earlier call, inserting
+ folders at the beginning of the translation directory list.
- tdirs.insert(0, directory)
+ """
+ introspectables = []
+ override = kw.pop('override', False)
+ if kw:
+ raise TypeError('invalid keyword arguments: %s', sorted(kw.keys()))
+
+ def register():
+ directories = []
+ resolver = AssetResolver(self.package_name)
+
+ # defer spec resolution until register to allow for asset
+ # overrides to take place in an earlier config phase
+ for spec in specs:
+ # the trailing slash helps match asset overrides for folders
+ if not spec.endswith('/'):
+ spec += '/'
+ asset = resolver.resolve(spec)
+ directory = asset.abspath()
+ if not asset.isdir():
+ raise ConfigurationError('"%s" is not a directory' %
+ directory)
+ intr = self.introspectable('translation directories', directory,
+ spec, 'translation directory')
+ intr['directory'] = directory
+ intr['spec'] = spec
+ introspectables.append(intr)
+ directories.append(directory)
+
+ tdirs = self.registry.queryUtility(ITranslationDirectories)
+ if tdirs is None:
+ tdirs = []
+ self.registry.registerUtility(tdirs, ITranslationDirectories)
+ if override:
+ tdirs.extend(directories)
+ else:
+ for directory in reversed(directories):
+ tdirs.insert(0, directory)
self.action(None, register, introspectables=introspectables)
diff --git a/pyramid/config/predicates.py b/pyramid/config/predicates.py
index 0b76bbd70..5d99d6564 100644
--- a/pyramid/config/predicates.py
+++ b/pyramid/config/predicates.py
@@ -1,301 +1,2 @@
-import re
-
-from pyramid.exceptions import ConfigurationError
-
-from pyramid.compat import is_nonstr_iter
-
-from pyramid.traversal import (
- find_interface,
- traversal_path,
- resource_path_tuple
- )
-
-from pyramid.urldispatch import _compile_route
-from pyramid.util import object_description
-from pyramid.session import check_csrf_token
-
-from .util import as_sorted_tuple
-
-_marker = object()
-
-class XHRPredicate(object):
- def __init__(self, val, config):
- self.val = bool(val)
-
- def text(self):
- return 'xhr = %s' % self.val
-
- phash = text
-
- def __call__(self, context, request):
- return bool(request.is_xhr) is self.val
-
-class RequestMethodPredicate(object):
- def __init__(self, val, config):
- request_method = as_sorted_tuple(val)
- if 'GET' in request_method and 'HEAD' not in request_method:
- # GET implies HEAD too
- request_method = as_sorted_tuple(request_method + ('HEAD',))
- self.val = request_method
-
- def text(self):
- return 'request_method = %s' % (','.join(self.val))
-
- phash = text
-
- def __call__(self, context, request):
- return request.method in self.val
-
-class PathInfoPredicate(object):
- def __init__(self, val, config):
- self.orig = val
- try:
- val = re.compile(val)
- except re.error as why:
- raise ConfigurationError(why.args[0])
- self.val = val
-
- def text(self):
- return 'path_info = %s' % (self.orig,)
-
- phash = text
-
- def __call__(self, context, request):
- return self.val.match(request.upath_info) is not None
-
-class RequestParamPredicate(object):
- def __init__(self, val, config):
- val = as_sorted_tuple(val)
- reqs = []
- for p in val:
- k = p
- v = None
- if p.startswith('='):
- if '=' in p[1:]:
- k, v = p[1:].split('=', 1)
- k = '=' + k
- k, v = k.strip(), v.strip()
- elif '=' in p:
- k, v = p.split('=', 1)
- k, v = k.strip(), v.strip()
- reqs.append((k, v))
- self.val = val
- self.reqs = reqs
-
- def text(self):
- return 'request_param %s' % ','.join(
- ['%s=%s' % (x,y) if y else x for x, y in self.reqs]
- )
-
- phash = text
-
- def __call__(self, context, request):
- for k, v in self.reqs:
- actual = request.params.get(k)
- if actual is None:
- return False
- if v is not None and actual != v:
- return False
- return True
-
-class HeaderPredicate(object):
- def __init__(self, val, config):
- name = val
- v = None
- if ':' in name:
- name, val_str = name.split(':', 1)
- try:
- v = re.compile(val_str)
- except re.error as why:
- raise ConfigurationError(why.args[0])
- if v is None:
- self._text = 'header %s' % (name,)
- else:
- self._text = 'header %s=%s' % (name, val_str)
- self.name = name
- self.val = v
-
- def text(self):
- return self._text
-
- phash = text
-
- def __call__(self, context, request):
- if self.val is None:
- return self.name in request.headers
- val = request.headers.get(self.name)
- if val is None:
- return False
- return self.val.match(val) is not None
-
-class AcceptPredicate(object):
- def __init__(self, val, config):
- self.val = val
-
- def text(self):
- return 'accept = %s' % (self.val,)
-
- phash = text
-
- def __call__(self, context, request):
- return self.val in request.accept
-
-class ContainmentPredicate(object):
- def __init__(self, val, config):
- self.val = config.maybe_dotted(val)
-
- def text(self):
- return 'containment = %s' % (self.val,)
-
- phash = text
-
- def __call__(self, context, request):
- ctx = getattr(request, 'context', context)
- return find_interface(ctx, self.val) is not None
-
-class RequestTypePredicate(object):
- def __init__(self, val, config):
- self.val = val
-
- def text(self):
- return 'request_type = %s' % (self.val,)
-
- phash = text
-
- def __call__(self, context, request):
- return self.val.providedBy(request)
-
-class MatchParamPredicate(object):
- def __init__(self, val, config):
- val = as_sorted_tuple(val)
- self.val = val
- reqs = [ p.split('=', 1) for p in val ]
- self.reqs = [ (x.strip(), y.strip()) for x, y in reqs ]
-
- def text(self):
- return 'match_param %s' % ','.join(
- ['%s=%s' % (x,y) for x, y in self.reqs]
- )
-
- phash = text
-
- def __call__(self, context, request):
- if not request.matchdict:
- # might be None
- return False
- for k, v in self.reqs:
- if request.matchdict.get(k) != v:
- return False
- return True
-
-class CustomPredicate(object):
- def __init__(self, func, config):
- self.func = func
-
- def text(self):
- return getattr(
- self.func,
- '__text__',
- 'custom predicate: %s' % object_description(self.func)
- )
-
- def phash(self):
- # using hash() here rather than id() is intentional: we
- # want to allow custom predicates that are part of
- # frameworks to be able to define custom __hash__
- # functions for custom predicates, so that the hash output
- # of predicate instances which are "logically the same"
- # may compare equal.
- return 'custom:%r' % hash(self.func)
-
- def __call__(self, context, request):
- return self.func(context, request)
-
-
-class TraversePredicate(object):
- # Can only be used as a *route* "predicate"; it adds 'traverse' to the
- # matchdict if it's specified in the routing args. This causes the
- # ResourceTreeTraverser to use the resolved traverse pattern as the
- # traversal path.
- def __init__(self, val, config):
- _, self.tgenerate = _compile_route(val)
- self.val = val
-
- def text(self):
- return 'traverse matchdict pseudo-predicate'
-
- def phash(self):
- # This isn't actually a predicate, it's just a infodict modifier that
- # injects ``traverse`` into the matchdict. As a result, we don't
- # need to update the hash.
- return ''
-
- def __call__(self, context, request):
- if 'traverse' in context:
- return True
- m = context['match']
- tvalue = self.tgenerate(m) # tvalue will be urlquoted string
- m['traverse'] = traversal_path(tvalue)
- # This isn't actually a predicate, it's just a infodict modifier that
- # injects ``traverse`` into the matchdict. As a result, we just
- # return True.
- return True
-
-class CheckCSRFTokenPredicate(object):
-
- check_csrf_token = staticmethod(check_csrf_token) # testing
-
- def __init__(self, val, config):
- self.val = val
-
- def text(self):
- return 'check_csrf = %s' % (self.val,)
-
- phash = text
-
- def __call__(self, context, request):
- val = self.val
- if val:
- if val is True:
- val = 'csrf_token'
- return self.check_csrf_token(request, val, raises=False)
- return True
-
-class PhysicalPathPredicate(object):
- def __init__(self, val, config):
- if is_nonstr_iter(val):
- self.val = tuple(val)
- else:
- val = tuple(filter(None, val.split('/')))
- self.val = ('',) + val
-
- def text(self):
- return 'physical_path = %s' % (self.val,)
-
- phash = text
-
- def __call__(self, context, request):
- if getattr(context, '__name__', _marker) is not _marker:
- return resource_path_tuple(context) == self.val
- return False
-
-class EffectivePrincipalsPredicate(object):
- def __init__(self, val, config):
- if is_nonstr_iter(val):
- self.val = set(val)
- else:
- self.val = set((val,))
-
- def text(self):
- return 'effective_principals = %s' % sorted(list(self.val))
-
- phash = text
-
- def __call__(self, context, request):
- req_principals = request.effective_principals
- if is_nonstr_iter(req_principals):
- rpset = set(req_principals)
- if self.val.issubset(rpset):
- return True
- return False
-
+import zope.deprecation
+zope.deprecation.moved('pyramid.predicates', 'Pyramid 2.0')
diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py
index 90d4d47d2..203baa128 100644
--- a/pyramid/config/routes.py
+++ b/pyramid/config/routes.py
@@ -13,12 +13,10 @@ from pyramid.registry import predvalseq
from pyramid.request import route_request_iface
from pyramid.urldispatch import RoutesMapper
-from pyramid.config.util import (
- action_method,
- as_sorted_tuple,
- )
+from pyramid.config.util import action_method
+from pyramid.util import as_sorted_tuple
-import pyramid.config.predicates
+import pyramid.predicates
class RoutesConfiguratorMixin(object):
@action_method
@@ -446,7 +444,7 @@ class RoutesConfiguratorMixin(object):
)
def add_default_route_predicates(self):
- p = pyramid.config.predicates
+ p = pyramid.predicates
for (name, factory) in (
('xhr', p.XHRPredicate),
('request_method', p.RequestMethodPredicate),
diff --git a/pyramid/config/security.py b/pyramid/config/security.py
index 02732c042..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.config.util import as_sorted_tuple
+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 f9dbd752e..52b30db81 100644
--- a/pyramid/config/settings.py
+++ b/pyramid/config/settings.py
@@ -1,15 +1,10 @@
import os
-import warnings
-from zope.interface import implementer
-
-from pyramid.interfaces import ISettings
-
-from pyramid.settings import asbool
+from pyramid.settings import asbool, aslist
class SettingsConfiguratorMixin(object):
def _set_settings(self, mapping):
- if not mapping:
+ if mapping is None:
mapping = {}
settings = Settings(mapping)
self.registry.settings = settings
@@ -54,118 +49,59 @@ class SettingsConfiguratorMixin(object):
return self.registry.settings
-@implementer(ISettings)
-class Settings(dict):
+def Settings(d=None, _environ_=os.environ, **kw):
""" Deployment settings. Update application settings (usually
from PasteDeploy keywords) with framework-specific key/value pairs
(e.g. find ``PYRAMID_DEBUG_AUTHORIZATION`` in os.environ and jam into
keyword args)."""
- # _environ_ is dep inj for testing
- def __init__(self, d=None, _environ_=os.environ, **kw):
- if d is None:
- d = {}
- dict.__init__(self, d, **kw)
- eget = _environ_.get
- config_debug_all = self.get('debug_all', '')
- config_debug_all = self.get('pyramid.debug_all', config_debug_all)
- eff_debug_all = asbool(eget('PYRAMID_DEBUG_ALL', config_debug_all))
- config_reload_all = self.get('reload_all', '')
- config_reload_all = self.get('pyramid.reload_all', config_reload_all)
- eff_reload_all = asbool(eget('PYRAMID_RELOAD_ALL', config_reload_all))
- config_debug_auth = self.get('debug_authorization', '')
- config_debug_auth = self.get('pyramid.debug_authorization',
- config_debug_auth)
- eff_debug_auth = asbool(eget('PYRAMID_DEBUG_AUTHORIZATION',
- config_debug_auth))
- config_debug_notfound = self.get('debug_notfound', '')
- config_debug_notfound = self.get('pyramid.debug_notfound',
- config_debug_notfound)
- eff_debug_notfound = asbool(eget('PYRAMID_DEBUG_NOTFOUND',
- config_debug_notfound))
- config_debug_routematch = self.get('debug_routematch', '')
- config_debug_routematch = self.get('pyramid.debug_routematch',
- config_debug_routematch)
- eff_debug_routematch = asbool(eget('PYRAMID_DEBUG_ROUTEMATCH',
- config_debug_routematch))
- config_debug_templates = self.get('debug_templates', '')
- config_debug_templates = self.get('pyramid.debug_templates',
- config_debug_templates)
- eff_debug_templates = asbool(eget('PYRAMID_DEBUG_TEMPLATES',
- config_debug_templates))
- config_reload_templates = self.get('reload_templates', '')
- config_reload_templates = self.get('pyramid.reload_templates',
- config_reload_templates)
- eff_reload_templates = asbool(eget('PYRAMID_RELOAD_TEMPLATES',
- config_reload_templates))
- config_reload_assets = self.get('reload_assets', '')
- config_reload_assets = self.get('pyramid.reload_assets',
- config_reload_assets)
- reload_assets = asbool(eget('PYRAMID_RELOAD_ASSETS',
- config_reload_assets))
- config_reload_resources = self.get('reload_resources', '')
- config_reload_resources = self.get('pyramid.reload_resources',
- config_reload_resources)
- reload_resources = asbool(eget('PYRAMID_RELOAD_RESOURCES',
- config_reload_resources))
- # reload_resources is an older alias for reload_assets
- eff_reload_assets = reload_assets or reload_resources
- locale_name = self.get('default_locale_name', 'en')
- locale_name = self.get('pyramid.default_locale_name', locale_name)
- eff_locale_name = eget('PYRAMID_DEFAULT_LOCALE_NAME', locale_name)
- config_prevent_http_cache = self.get('prevent_http_cache', '')
- config_prevent_http_cache = self.get('pyramid.prevent_http_cache',
- config_prevent_http_cache)
- eff_prevent_http_cache = asbool(eget('PYRAMID_PREVENT_HTTP_CACHE',
- config_prevent_http_cache))
- config_prevent_cachebust = self.get('prevent_cachebust', '')
- config_prevent_cachebust = self.get('pyramid.prevent_cachebust',
- config_prevent_cachebust)
- eff_prevent_cachebust = asbool(eget('PYRAMID_PREVENT_CACHEBUST',
- config_prevent_cachebust))
- csrf_trusted_origins = self.get("pyramid.csrf_trusted_origins", [])
- eff_csrf_trusted_origins = csrf_trusted_origins
-
- update = {
- 'debug_authorization': eff_debug_all or eff_debug_auth,
- 'debug_notfound': eff_debug_all or eff_debug_notfound,
- 'debug_routematch': eff_debug_all or eff_debug_routematch,
- 'debug_templates': eff_debug_all or eff_debug_templates,
- 'reload_templates': eff_reload_all or eff_reload_templates,
- 'reload_resources':eff_reload_all or eff_reload_assets,
- 'reload_assets':eff_reload_all or eff_reload_assets,
- 'default_locale_name':eff_locale_name,
- 'prevent_http_cache':eff_prevent_http_cache,
- 'prevent_cachebust':eff_prevent_cachebust,
- 'csrf_trusted_origins':eff_csrf_trusted_origins,
-
- 'pyramid.debug_authorization': eff_debug_all or eff_debug_auth,
- 'pyramid.debug_notfound': eff_debug_all or eff_debug_notfound,
- 'pyramid.debug_routematch': eff_debug_all or eff_debug_routematch,
- 'pyramid.debug_templates': eff_debug_all or eff_debug_templates,
- 'pyramid.reload_templates': eff_reload_all or eff_reload_templates,
- 'pyramid.reload_resources':eff_reload_all or eff_reload_assets,
- 'pyramid.reload_assets':eff_reload_all or eff_reload_assets,
- 'pyramid.default_locale_name':eff_locale_name,
- 'pyramid.prevent_http_cache':eff_prevent_http_cache,
- 'pyramid.prevent_cachebust':eff_prevent_cachebust,
- 'pyramid.csrf_trusted_origins':eff_csrf_trusted_origins,
- }
-
- self.update(update)
-
- def __getattr__(self, name):
- try:
- val = self[name]
- # only deprecate on success; a probing getattr/hasattr should not
- # print this warning
- warnings.warn(
- 'Obtaining settings via attributes of the settings dictionary '
- 'is deprecated as of Pyramid 1.2; use settings["foo"] instead '
- 'of settings.foo',
- DeprecationWarning,
- 2
- )
- return val
- except KeyError:
- raise AttributeError(name)
-
+ if d is None:
+ d = {}
+ d = dict(d)
+ d.update(**kw)
+
+ eget = _environ_.get
+ def expand_key(key):
+ keys = [key]
+ if not key.startswith('pyramid.'):
+ keys.append('pyramid.' + key)
+ return keys
+ def S(settings_key, env_key=None, type_=str, default=False):
+ value = default
+ keys = expand_key(settings_key)
+ for key in keys:
+ value = d.get(key, value)
+ if env_key:
+ value = eget(env_key, value)
+ value = type_(value)
+ d.update({k: value for k in keys})
+ def O(settings_key, override_key):
+ for key in expand_key(settings_key):
+ d[key] = d[key] or d[override_key]
+
+ S('debug_all', 'PYRAMID_DEBUG_ALL', asbool)
+ S('debug_authorization', 'PYRAMID_DEBUG_AUTHORIZATION', asbool)
+ O('debug_authorization', 'debug_all')
+ S('debug_notfound', 'PYRAMID_DEBUG_NOTFOUND', asbool)
+ O('debug_notfound', 'debug_all')
+ S('debug_routematch', 'PYRAMID_DEBUG_ROUTEMATCH', asbool)
+ O('debug_routematch', 'debug_all')
+ S('debug_templates', 'PYRAMID_DEBUG_TEMPLATES', asbool)
+ O('debug_templates', 'debug_all')
+
+ S('reload_all', 'PYRAMID_RELOAD_ALL', asbool)
+ S('reload_templates', 'PYRAMID_RELOAD_TEMPLATES', asbool)
+ O('reload_templates', 'reload_all')
+ S('reload_assets', 'PYRAMID_RELOAD_ASSETS', asbool)
+ O('reload_assets', 'reload_all')
+ S('reload_resources', 'PYRAMID_RELOAD_RESOURCES', asbool)
+ O('reload_resources', 'reload_all')
+ # reload_resources is an older alias for reload_assets
+ for k in expand_key('reload_assets') + expand_key('reload_resources'):
+ d[k] = d['reload_assets'] or d['reload_resources']
+
+ S('default_locale_name', 'PYRAMID_DEFAULT_LOCALE_NAME', str, 'en')
+ S('prevent_http_cache', 'PYRAMID_PREVENT_HTTP_CACHE', asbool)
+ S('prevent_cachebust', 'PYRAMID_PREVENT_CACHEBUST', asbool)
+ S('csrf_trusted_origins', 'PYRAMID_CSRF_TRUSTED_ORIGINS', aslist, [])
+
+ return d
diff --git a/pyramid/config/tweens.py b/pyramid/config/tweens.py
index 0aeb01fe3..16712ab16 100644
--- a/pyramid/config/tweens.py
+++ b/pyramid/config/tweens.py
@@ -17,9 +17,9 @@ from pyramid.tweens import (
from pyramid.config.util import (
action_method,
- is_string_or_iterable,
TopologicalSorter,
)
+from pyramid.util import is_string_or_iterable
class TweensConfiguratorMixin(object):
def add_tween(self, tween_factory, under=None, over=None):
diff --git a/pyramid/config/util.py b/pyramid/config/util.py
index 626e8d5fe..67bba9593 100644
--- a/pyramid/config/util.py
+++ b/pyramid/config/util.py
@@ -4,8 +4,7 @@ import inspect
from pyramid.compat import (
bytes_,
getargspec,
- is_nonstr_iter,
- string_types,
+ is_nonstr_iter
)
from pyramid.compat import im_func
@@ -24,18 +23,6 @@ ActionInfo = ActionInfo # support bw compat imports
MAX_ORDER = 1 << 30
DEFAULT_PHASH = md5().hexdigest()
-def is_string_or_iterable(v):
- if isinstance(v, string_types):
- return True
- if hasattr(v, '__iter__'):
- return True
-
-def as_sorted_tuple(val):
- if not is_nonstr_iter(val):
- val = (val,)
- val = tuple(sorted(val))
- return val
-
class not_(object):
"""
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index acdc00704..48c4e3437 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -70,10 +70,11 @@ import pyramid.util
from pyramid.util import (
viewdefaults,
action_method,
+ as_sorted_tuple,
TopologicalSorter,
)
-import pyramid.config.predicates
+import pyramid.predicates
import pyramid.viewderivers
from pyramid.viewderivers import (
@@ -89,7 +90,6 @@ from pyramid.viewderivers import (
from pyramid.config.util import (
DEFAULT_PHASH,
MAX_ORDER,
- as_sorted_tuple,
)
urljoin = urlparse.urljoin
@@ -444,9 +444,11 @@ class ViewsConfiguratorMixin(object):
think about preserving function attributes such as ``__name__`` and
``__module__`` within decorator logic).
- All view callables in the decorator chain must return a response
- object implementing :class:`pyramid.interfaces.IResponse` or raise
- an exception:
+ An important distinction is that each decorator will receive a
+ response object implementing :class:`pyramid.interfaces.IResponse`
+ instead of the raw value returned from the view callable. All
+ decorators in the chain must return a response object or raise an
+ exception:
.. code-block:: python
@@ -639,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
@@ -970,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
@@ -1030,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:
@@ -1141,7 +1147,7 @@ class ViewsConfiguratorMixin(object):
)
def add_default_view_predicates(self):
- p = pyramid.config.predicates
+ p = pyramid.predicates
for (name, factory) in (
('xhr', p.XHRPredicate),
('request_method', p.RequestMethodPredicate),
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/httpexceptions.py b/pyramid/httpexceptions.py
index 054917dfa..1f3934fdc 100644
--- a/pyramid/httpexceptions.py
+++ b/pyramid/httpexceptions.py
@@ -238,7 +238,7 @@ ${body}''')
del self.content_length
def __str__(self):
- return self.detail or self.explanation
+ return str(self.detail) if self.detail else self.explanation
def _json_formatter(self, status, body, title, environ):
return {'message': body,
@@ -246,7 +246,7 @@ ${body}''')
'title': self.title}
def prepare(self, environ):
- if not self.body and not self.empty_body:
+ if not self.has_body and not self.empty_body:
html_comment = ''
comment = self.comment or ''
accept_value = environ.get('HTTP_ACCEPT', '')
diff --git a/pyramid/i18n.py b/pyramid/i18n.py
index 79209d342..1d11adfe3 100644
--- a/pyramid/i18n.py
+++ b/pyramid/i18n.py
@@ -22,6 +22,7 @@ from pyramid.threadlocal import get_current_registry
TranslationString = TranslationString # PyFlakes
TranslationStringFactory = TranslationStringFactory # PyFlakes
+DEFAULT_PLURAL = lambda n: int(n != 1)
class Localizer(object):
"""
@@ -233,7 +234,13 @@ class Translations(gettext.GNUTranslations, object):
# GNUTranslations._parse (called as a side effect if fileobj is
# passed to GNUTranslations.__init__) with a "real" self.plural for
# this domain; see https://github.com/Pylons/pyramid/issues/235
- self.plural = lambda n: int(n != 1)
+ # It is only overridden the first time a new message file is found
+ # for a given domain, so all message files must have matching plural
+ # rules if they are in the same domain. We keep track of if we have
+ # overridden so we can special case the default domain, which is always
+ # instantiated before a message file is read.
+ # See also https://github.com/Pylons/pyramid/pull/2102
+ self.plural = DEFAULT_PLURAL
gettext.GNUTranslations.__init__(self, fp=fileobj)
self.files = list(filter(None, [getattr(fileobj, 'name', None)]))
self.domain = domain
@@ -285,6 +292,9 @@ class Translations(gettext.GNUTranslations, object):
:rtype: `Translations`
"""
domain = getattr(translations, 'domain', self.DEFAULT_DOMAIN)
+ if domain == self.DEFAULT_DOMAIN and self.plural is DEFAULT_PLURAL:
+ self.plural = translations.plural
+
if merge and domain == self.domain:
return self.merge(translations)
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index c1ddea63f..ab83813c8 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -682,7 +682,48 @@ class IRouter(Interface):
registry = Attribute(
"""Component architecture registry local to this application.""")
-class ISettings(Interface):
+ def make_request(environ):
+ """
+ Create a new request object.
+
+ This method initializes a new :class:`pyramid.interfaces.IRequest`
+ object using the application's
+ :class:`pyramid.interfaces.IRequestFactory`.
+ """
+
+ def invoke_request(request):
+ """
+ Invoke the :app:`Pyramid` request pipeline.
+
+ See :ref:`router_chapter` for information on the request pipeline.
+ """
+
+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 simple creates a request and sends it
+ through the pipeline:
+
+ .. code-block:: python
+
+ def simple_execution_policy(environ, router):
+ request = router.make_request(environ)
+ return router.invoke_request(request)
+ """
+
+class ISettings(IDict):
""" Runtime settings utility for pyramid; represents the
deployment settings for the application. Implements a mapping
interface."""
@@ -799,58 +840,6 @@ class IResourceURL(Interface):
'The physical url path of the resource as a tuple. (New in 1.5)'
)
-class IContextURL(IResourceURL):
- """
- .. deprecated:: 1.3
- An adapter which deals with URLs related to a context. Use
- :class:`pyramid.interfaces.IResourceURL` instead.
- """
- # this class subclasses IResourceURL because request.resource_url looks
- # for IResourceURL via queryAdapter. queryAdapter will find a deprecated
- # IContextURL registration if no registration for IResourceURL exists.
- # In reality, however, IContextURL objects were never required to have
- # the virtual_path or physical_path attributes spelled in IResourceURL.
- # The inheritance relationship is purely to benefit adapter lookup,
- # not to imply an inheritance relationship of interface attributes
- # and methods.
- #
- # Mechanics:
- #
- # class Fudge(object):
- # def __init__(self, one, two):
- # print(one, two)
- # class Another(object):
- # def __init__(self, one, two):
- # print(one, two)
- # ob = object()
- # r.registerAdapter(Fudge, (Interface, Interface), IContextURL)
- # print(r.queryMultiAdapter((ob, ob), IResourceURL))
- # r.registerAdapter(Another, (Interface, Interface), IResourceURL)
- # print(r.queryMultiAdapter((ob, ob), IResourceURL))
- #
- # prints
- #
- # <object object at 0x7fa678f3e2a0> <object object at 0x7fa678f3e2a0>
- # <__main__.Fudge object at 0x1cda890>
- # <object object at 0x7fa678f3e2a0> <object object at 0x7fa678f3e2a0>
- # <__main__.Another object at 0x1cda850>
-
- def virtual_root():
- """ Return the virtual root related to a request and the
- current context"""
-
- def __call__():
- """ Return a URL that points to the context. """
-
-deprecated(
- 'IContextURL',
- 'As of Pyramid 1.3 the, "pyramid.interfaces.IContextURL" interface is '
- 'scheduled to be removed. Use the '
- '"pyramid.config.Configurator.add_resource_url_adapter" method to register '
- 'a class that implements "pyramid.interfaces.IResourceURL" instead. '
- 'See the "What\'s new In Pyramid 1.3" document for more details.'
- )
-
class IPEP302Loader(Interface):
""" See http://www.python.org/dev/peps/pep-0302/#id30.
"""
@@ -938,6 +927,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
@@ -992,19 +988,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/predicates.py b/pyramid/predicates.py
new file mode 100644
index 000000000..3d7bb1b4b
--- /dev/null
+++ b/pyramid/predicates.py
@@ -0,0 +1,300 @@
+import re
+
+from pyramid.exceptions import ConfigurationError
+
+from pyramid.compat import is_nonstr_iter
+
+from pyramid.csrf import check_csrf_token
+from pyramid.traversal import (
+ find_interface,
+ traversal_path,
+ resource_path_tuple
+ )
+
+from pyramid.urldispatch import _compile_route
+from pyramid.util import object_description
+from pyramid.util import as_sorted_tuple
+
+_marker = object()
+
+class XHRPredicate(object):
+ def __init__(self, val, config):
+ self.val = bool(val)
+
+ def text(self):
+ return 'xhr = %s' % self.val
+
+ phash = text
+
+ def __call__(self, context, request):
+ return bool(request.is_xhr) is self.val
+
+class RequestMethodPredicate(object):
+ def __init__(self, val, config):
+ request_method = as_sorted_tuple(val)
+ if 'GET' in request_method and 'HEAD' not in request_method:
+ # GET implies HEAD too
+ request_method = as_sorted_tuple(request_method + ('HEAD',))
+ self.val = request_method
+
+ def text(self):
+ return 'request_method = %s' % (','.join(self.val))
+
+ phash = text
+
+ def __call__(self, context, request):
+ return request.method in self.val
+
+class PathInfoPredicate(object):
+ def __init__(self, val, config):
+ self.orig = val
+ try:
+ val = re.compile(val)
+ except re.error as why:
+ raise ConfigurationError(why.args[0])
+ self.val = val
+
+ def text(self):
+ return 'path_info = %s' % (self.orig,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ return self.val.match(request.upath_info) is not None
+
+class RequestParamPredicate(object):
+ def __init__(self, val, config):
+ val = as_sorted_tuple(val)
+ reqs = []
+ for p in val:
+ k = p
+ v = None
+ if p.startswith('='):
+ if '=' in p[1:]:
+ k, v = p[1:].split('=', 1)
+ k = '=' + k
+ k, v = k.strip(), v.strip()
+ elif '=' in p:
+ k, v = p.split('=', 1)
+ k, v = k.strip(), v.strip()
+ reqs.append((k, v))
+ self.val = val
+ self.reqs = reqs
+
+ def text(self):
+ return 'request_param %s' % ','.join(
+ ['%s=%s' % (x,y) if y else x for x, y in self.reqs]
+ )
+
+ phash = text
+
+ def __call__(self, context, request):
+ for k, v in self.reqs:
+ actual = request.params.get(k)
+ if actual is None:
+ return False
+ if v is not None and actual != v:
+ return False
+ return True
+
+class HeaderPredicate(object):
+ def __init__(self, val, config):
+ name = val
+ v = None
+ if ':' in name:
+ name, val_str = name.split(':', 1)
+ try:
+ v = re.compile(val_str)
+ except re.error as why:
+ raise ConfigurationError(why.args[0])
+ if v is None:
+ self._text = 'header %s' % (name,)
+ else:
+ self._text = 'header %s=%s' % (name, val_str)
+ self.name = name
+ self.val = v
+
+ def text(self):
+ return self._text
+
+ phash = text
+
+ def __call__(self, context, request):
+ if self.val is None:
+ return self.name in request.headers
+ val = request.headers.get(self.name)
+ if val is None:
+ return False
+ return self.val.match(val) is not None
+
+class AcceptPredicate(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'accept = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ return self.val in request.accept
+
+class ContainmentPredicate(object):
+ def __init__(self, val, config):
+ self.val = config.maybe_dotted(val)
+
+ def text(self):
+ return 'containment = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ ctx = getattr(request, 'context', context)
+ return find_interface(ctx, self.val) is not None
+
+class RequestTypePredicate(object):
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'request_type = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ return self.val.providedBy(request)
+
+class MatchParamPredicate(object):
+ def __init__(self, val, config):
+ val = as_sorted_tuple(val)
+ self.val = val
+ reqs = [ p.split('=', 1) for p in val ]
+ self.reqs = [ (x.strip(), y.strip()) for x, y in reqs ]
+
+ def text(self):
+ return 'match_param %s' % ','.join(
+ ['%s=%s' % (x,y) for x, y in self.reqs]
+ )
+
+ phash = text
+
+ def __call__(self, context, request):
+ if not request.matchdict:
+ # might be None
+ return False
+ for k, v in self.reqs:
+ if request.matchdict.get(k) != v:
+ return False
+ return True
+
+class CustomPredicate(object):
+ def __init__(self, func, config):
+ self.func = func
+
+ def text(self):
+ return getattr(
+ self.func,
+ '__text__',
+ 'custom predicate: %s' % object_description(self.func)
+ )
+
+ def phash(self):
+ # using hash() here rather than id() is intentional: we
+ # want to allow custom predicates that are part of
+ # frameworks to be able to define custom __hash__
+ # functions for custom predicates, so that the hash output
+ # of predicate instances which are "logically the same"
+ # may compare equal.
+ return 'custom:%r' % hash(self.func)
+
+ def __call__(self, context, request):
+ return self.func(context, request)
+
+
+class TraversePredicate(object):
+ # Can only be used as a *route* "predicate"; it adds 'traverse' to the
+ # matchdict if it's specified in the routing args. This causes the
+ # ResourceTreeTraverser to use the resolved traverse pattern as the
+ # traversal path.
+ def __init__(self, val, config):
+ _, self.tgenerate = _compile_route(val)
+ self.val = val
+
+ def text(self):
+ return 'traverse matchdict pseudo-predicate'
+
+ def phash(self):
+ # This isn't actually a predicate, it's just a infodict modifier that
+ # injects ``traverse`` into the matchdict. As a result, we don't
+ # need to update the hash.
+ return ''
+
+ def __call__(self, context, request):
+ if 'traverse' in context:
+ return True
+ m = context['match']
+ tvalue = self.tgenerate(m) # tvalue will be urlquoted string
+ m['traverse'] = traversal_path(tvalue)
+ # This isn't actually a predicate, it's just a infodict modifier that
+ # injects ``traverse`` into the matchdict. As a result, we just
+ # return True.
+ return True
+
+class CheckCSRFTokenPredicate(object):
+
+ check_csrf_token = staticmethod(check_csrf_token) # testing
+
+ def __init__(self, val, config):
+ self.val = val
+
+ def text(self):
+ return 'check_csrf = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ val = self.val
+ if val:
+ if val is True:
+ val = 'csrf_token'
+ return self.check_csrf_token(request, val, raises=False)
+ return True
+
+class PhysicalPathPredicate(object):
+ def __init__(self, val, config):
+ if is_nonstr_iter(val):
+ self.val = tuple(val)
+ else:
+ val = tuple(filter(None, val.split('/')))
+ self.val = ('',) + val
+
+ def text(self):
+ return 'physical_path = %s' % (self.val,)
+
+ phash = text
+
+ def __call__(self, context, request):
+ if getattr(context, '__name__', _marker) is not _marker:
+ return resource_path_tuple(context) == self.val
+ return False
+
+class EffectivePrincipalsPredicate(object):
+ def __init__(self, val, config):
+ if is_nonstr_iter(val):
+ self.val = set(val)
+ else:
+ self.val = set((val,))
+
+ def text(self):
+ return 'effective_principals = %s' % sorted(list(self.val))
+
+ phash = text
+
+ def __call__(self, context, request):
+ req_principals = request.effective_principals
+ if is_nonstr_iter(req_principals):
+ rpset = set(req_principals)
+ if self.val.issubset(rpset):
+ return True
+ return False
+
diff --git a/pyramid/registry.py b/pyramid/registry.py
index df9a10822..7589dfcac 100644
--- a/pyramid/registry.py
+++ b/pyramid/registry.py
@@ -9,28 +9,44 @@ from pyramid.compat import text_
from pyramid.decorator import reify
from pyramid.interfaces import (
- ISettings,
IIntrospector,
IIntrospectable,
+ ISettings,
)
+from pyramid.path import (
+ CALLER_PACKAGE,
+ caller_package,
+)
+
empty = text_('')
class Registry(Components, dict):
- """ A registry object is an :term:`application registry`. It is used by
- the framework itself to perform mappings of URLs to view callables, as
- well as servicing other various framework duties. A registry has its own
- internal API, but this API is rarely used by Pyramid application
- developers (it's usually only used by developers of the Pyramid
- framework). But it has a number of attributes that may be useful to
- application developers within application code, such as ``settings``,
- which is a dictionary containing application deployment settings.
+ """ A registry object is an :term:`application registry`.
+
+ It is used by the framework itself to perform mappings of URLs to view
+ callables, as well as servicing other various framework duties. A registry
+ has its own internal API, but this API is rarely used by Pyramid
+ application developers (it's usually only used by developers of the
+ Pyramid framework and Pyramid addons). But it has a number of attributes
+ that may be useful to application developers within application code,
+ such as ``settings``, which is a dictionary containing application
+ deployment settings.
For information about the purpose and usage of the application registry,
see :ref:`zca_chapter`.
+ The registry may be used both as an :class:`pyramid.interfaces.IDict` and
+ as a Zope component registry.
+ These two ways of storing configuration are independent.
+ Applications will tend to prefer to store information as key-values
+ whereas addons may prefer to use the component registry to avoid naming
+ conflicts and to provide more complex lookup mechanisms.
+
The application registry is usually accessed as ``request.registry`` in
- application code.
+ application code. By the time a registry is used to handle requests it
+ should be considered frozen and read-only. Any changes to its internal
+ state should be done with caution and concern for thread-safety.
"""
@@ -40,13 +56,16 @@ class Registry(Components, dict):
_settings = None
- def __init__(self, *arg, **kw):
+ def __init__(self, package_name=CALLER_PACKAGE, *args, **kw):
# add a registry-instance-specific lock, which is used when the lookup
# cache is mutated
self._lock = threading.Lock()
# add a view lookup cache
self._clear_view_lookup_cache()
- Components.__init__(self, *arg, **kw)
+ if package_name is CALLER_PACKAGE:
+ package_name = caller_package().__name__
+ Components.__init__(self, package_name, *args, **kw)
+ dict.__init__(self)
def _clear_view_lookup_cache(self):
self._view_lookup_cache = {}
@@ -257,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/response.py b/pyramid/response.py
index 892e5dfff..1d9daae7d 100644
--- a/pyramid/response.py
+++ b/pyramid/response.py
@@ -54,16 +54,7 @@ class FileResponse(Response):
def __init__(self, path, request=None, cache_max_age=None,
content_type=None, content_encoding=None):
if content_type is None:
- content_type, content_encoding = mimetypes.guess_type(
- path,
- strict=False
- )
- if content_type is None:
- content_type = 'application/octet-stream'
- # str-ifying content_type is a workaround for a bug in Python 2.7.7
- # on Windows where mimetypes.guess_type returns unicode for the
- # content_type.
- content_type = str(content_type)
+ content_type, content_encoding = _guess_type(path)
super(FileResponse, self).__init__(
conditional_response=True,
content_type=content_type,
@@ -180,3 +171,17 @@ def _get_response_factory(registry):
)
return response_factory
+
+
+def _guess_type(path):
+ content_type, content_encoding = mimetypes.guess_type(
+ path,
+ strict=False
+ )
+ if content_type is None:
+ content_type = 'application/octet-stream'
+ # str-ifying content_type is a workaround for a bug in Python 2.7.7
+ # on Windows where mimetypes.guess_type returns unicode for the
+ # content_type.
+ content_type = str(content_type)
+ return content_type, content_encoding
diff --git a/pyramid/router.py b/pyramid/router.py
index fd11925e9..8b7b7b6bc 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,
@@ -49,6 +50,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,19 +185,36 @@ 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.
"""
+ request.registry = self.registry
+ request.invoke_subrequest = self.invoke_subrequest
+ return self.invoke_request(
+ request,
+ _use_tweens=use_tweens,
+ _apply_extensions=True,
+ )
+
+ def make_request(self, environ):
+ 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 request
+
+ def invoke_request(self, request,
+ _use_tweens=True, _apply_extensions=False):
registry = self.registry
has_listeners = self.registry.has_listeners
notify = self.registry.notify
- threadlocals = {'registry':registry, 'request':request}
+ threadlocals = {'registry': registry, 'request': request}
manager = self.threadlocal_manager
manager.push(threadlocals)
- request.registry = registry
- request.invoke_subrequest = self.invoke_subrequest
-
- if use_tweens:
+
+ if _use_tweens:
handle_request = self.handle_request
else:
handle_request = self.orig_handle_request
@@ -203,7 +223,7 @@ class Router(object):
try:
extensions = self.request_extensions
- if extensions is not None:
+ if _apply_extensions and extensions is not None:
apply_request_extensions(request, extensions=extensions)
response = handle_request(request)
@@ -211,7 +231,7 @@ class Router(object):
request._process_response_callbacks(response)
has_listeners and notify(NewResponse(request, response))
-
+
return response
finally:
@@ -229,6 +249,10 @@ 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):
+ request = router.make_request(environ)
+ return router.invoke_request(request)
diff --git a/pyramid/scaffolds/__init__.py b/pyramid/scaffolds/__init__.py
index 841dc403e..e06c5a930 100644
--- a/pyramid/scaffolds/__init__.py
+++ b/pyramid/scaffolds/__init__.py
@@ -37,7 +37,7 @@ class PyramidTemplate(Template):
%(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
+ Twitter: https://twitter.com/PylonsProject
Mailing List: https://groups.google.com/forum/#!forum/pylons-discuss
Welcome to Pyramid. Sorry for the convenience.
@@ -52,7 +52,7 @@ class PyramidTemplate(Template):
class StarterProjectTemplate(PyramidTemplate):
_template_dir = 'starter'
- summary = 'Pyramid starter project using URL dispatch and Chameleon'
+ summary = 'Pyramid starter project using URL dispatch and Jinja2'
class ZODBProjectTemplate(PyramidTemplate):
_template_dir = 'zodb'
diff --git a/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl
index 51e382654..b5cfdc94d 100644
--- a/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl
+++ b/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl
@@ -43,7 +43,7 @@
<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-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-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>
</ul>
</div>
diff --git a/pyramid/scaffolds/alchemy/development.ini_tmpl b/pyramid/scaffolds/alchemy/development.ini_tmpl
index f8ee290be..6efde1d82 100644
--- a/pyramid/scaffolds/alchemy/development.ini_tmpl
+++ b/pyramid/scaffolds/alchemy/development.ini_tmpl
@@ -26,8 +26,7 @@ sqlalchemy.url = sqlite:///%(here)s/{{project}}.sqlite
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
diff --git a/pyramid/scaffolds/alchemy/production.ini_tmpl b/pyramid/scaffolds/alchemy/production.ini_tmpl
index 4d9f835d4..afc1c8f0a 100644
--- a/pyramid/scaffolds/alchemy/production.ini_tmpl
+++ b/pyramid/scaffolds/alchemy/production.ini_tmpl
@@ -16,8 +16,7 @@ sqlalchemy.url = sqlite:///%(here)s/{{project}}.sqlite
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
diff --git a/pyramid/scaffolds/starter/+package+/__init__.py b/pyramid/scaffolds/starter/+package+/__init__.py
index ad5ecbc6f..49dde36d4 100644
--- a/pyramid/scaffolds/starter/+package+/__init__.py
+++ b/pyramid/scaffolds/starter/+package+/__init__.py
@@ -5,7 +5,7 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(settings=settings)
- config.include('pyramid_chameleon')
+ config.include('pyramid_jinja2')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.scan()
diff --git a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl
index 87fae3817..a784c741b 100644
--- a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl
+++ b/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl
@@ -1,12 +1,12 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="\{\{request.locale_name\}\}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('{{package}}:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="\{\{request.static_url('{{package}}:static/pyramid-16x16.png')\}\}">
<title>Starter Scaffold for The Pyramid Web Framework</title>
@@ -14,7 +14,7 @@
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('{{package}}:static/theme.css')}" rel="stylesheet">
+ <link href="\{\{request.static_url('{{package}}:static/theme.css')\}\}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -29,13 +29,12 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('{{package}}:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="\{\{request.static_url('{{package}}:static/pyramid.png')\}\}" alt="pyramid web framework">
</div>
<div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework {{pyramid_version}}</span>.</p>
- </div>
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
</div>
</div>
<div class="row">
@@ -44,7 +43,7 @@
<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-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-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>
</ul>
</div>
diff --git a/pyramid/scaffolds/starter/+package+/templates/mytemplate.jinja2_tmpl b/pyramid/scaffolds/starter/+package+/templates/mytemplate.jinja2_tmpl
new file mode 100644
index 000000000..f826ff9e7
--- /dev/null
+++ b/pyramid/scaffolds/starter/+package+/templates/mytemplate.jinja2_tmpl
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content%}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">\{\{project\}\}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework {{pyramid_version}}</span>.</p>
+</div>
+{% endblock content %}
diff --git a/pyramid/scaffolds/starter/+package+/views.py_tmpl b/pyramid/scaffolds/starter/+package+/views.py_tmpl
index ad9af7292..01b9d0130 100644
--- a/pyramid/scaffolds/starter/+package+/views.py_tmpl
+++ b/pyramid/scaffolds/starter/+package+/views.py_tmpl
@@ -1,6 +1,6 @@
from pyramid.view import view_config
-@view_config(route_name='home', renderer='templates/mytemplate.pt')
+@view_config(route_name='home', renderer='templates/mytemplate.jinja2')
def my_view(request):
return {'project': '{{project}}'}
diff --git a/pyramid/scaffolds/starter/MANIFEST.in_tmpl b/pyramid/scaffolds/starter/MANIFEST.in_tmpl
index 0ff6eb7a0..4d1c86b44 100644
--- a/pyramid/scaffolds/starter/MANIFEST.in_tmpl
+++ b/pyramid/scaffolds/starter/MANIFEST.in_tmpl
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include {{package}} *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include {{package}} *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/pyramid/scaffolds/starter/development.ini_tmpl b/pyramid/scaffolds/starter/development.ini_tmpl
index ae9460b11..79302bd0a 100644
--- a/pyramid/scaffolds/starter/development.ini_tmpl
+++ b/pyramid/scaffolds/starter/development.ini_tmpl
@@ -24,8 +24,7 @@ pyramid.includes =
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
diff --git a/pyramid/scaffolds/starter/production.ini_tmpl b/pyramid/scaffolds/starter/production.ini_tmpl
index b2681c71d..8f0ae66ed 100644
--- a/pyramid/scaffolds/starter/production.ini_tmpl
+++ b/pyramid/scaffolds/starter/production.ini_tmpl
@@ -18,8 +18,7 @@ pyramid.default_locale_name = en
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
diff --git a/pyramid/scaffolds/starter/setup.py_tmpl b/pyramid/scaffolds/starter/setup.py_tmpl
index 2e5ce92c7..7f50bbbc2 100644
--- a/pyramid/scaffolds/starter/setup.py_tmpl
+++ b/pyramid/scaffolds/starter/setup.py_tmpl
@@ -10,7 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
- 'pyramid_chameleon',
+ 'pyramid_jinja2',
'pyramid_debugtoolbar',
'waitress',
]
diff --git a/pyramid/scaffolds/template.py b/pyramid/scaffolds/template.py
index d88f5b2a6..e5098e815 100644
--- a/pyramid/scaffolds/template.py
+++ b/pyramid/scaffolds/template.py
@@ -81,7 +81,7 @@ class Template(object):
template_dir = self.template_dir()
if not self.exists(output_dir):
self.out("Creating directory %s" % output_dir)
- if not command.options.simulate:
+ if not command.args.simulate:
# Don't let copydir create this top-level directory,
# since copydir will svn add it sometimes:
self.makedirs(output_dir)
@@ -90,9 +90,9 @@ class Template(object):
output_dir,
vars,
verbosity=command.verbosity,
- simulate=command.options.simulate,
- interactive=command.options.interactive,
- overwrite=command.options.overwrite,
+ simulate=command.args.simulate,
+ interactive=command.args.interactive,
+ overwrite=command.args.overwrite,
indent=1,
template_renderer=self.render_template,
)
diff --git a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl
index e109ab829..72b480249 100644
--- a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl
+++ b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl
@@ -44,7 +44,7 @@
<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-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-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>
</ul>
</div>
diff --git a/pyramid/scaffolds/zodb/development.ini_tmpl b/pyramid/scaffolds/zodb/development.ini_tmpl
index a44b61686..453b87e49 100644
--- a/pyramid/scaffolds/zodb/development.ini_tmpl
+++ b/pyramid/scaffolds/zodb/development.ini_tmpl
@@ -29,8 +29,7 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 127.0.0.1
-port = 6543
+listen = localhost:6543
###
# logging configuration
diff --git a/pyramid/scaffolds/zodb/production.ini_tmpl b/pyramid/scaffolds/zodb/production.ini_tmpl
index 522ff7651..dbfc634f8 100644
--- a/pyramid/scaffolds/zodb/production.ini_tmpl
+++ b/pyramid/scaffolds/zodb/production.ini_tmpl
@@ -24,8 +24,7 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
-port = 6543
+listen = *:6543
###
# logging configuration
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/pcreate.py b/pyramid/scripts/pcreate.py
index a954d3be6..a6db520ce 100644
--- a/pyramid/scripts/pcreate.py
+++ b/pyramid/scripts/pcreate.py
@@ -2,7 +2,7 @@
# (http://pythonpaste.org) Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license.php
-import optparse
+import argparse
import os
import os.path
import pkg_resources
@@ -12,83 +12,96 @@ from pyramid.compat import input_
_bad_chars_re = re.compile('[^a-zA-Z0-9_]')
+
def main(argv=sys.argv, quiet=False):
command = PCreateCommand(argv, quiet)
try:
return command.run()
- except KeyboardInterrupt: # pragma: no cover
+ except KeyboardInterrupt: # pragma: no cover
return 1
class PCreateCommand(object):
- verbosity = 1 # required
- description = "Render Pyramid scaffolding to an output directory"
- usage = "usage: %prog [options] -s <scaffold> output_directory"
- parser = optparse.OptionParser(usage, description=description)
- parser.add_option('-s', '--scaffold',
- dest='scaffold_name',
- action='append',
- help=("Add a scaffold to the create process "
- "(multiple -s args accepted)"))
- parser.add_option('-t', '--template',
- dest='scaffold_name',
- action='append',
- help=('A backwards compatibility alias for '
- '-s/--scaffold. Add a scaffold to the '
- 'create process (multiple -t args accepted)'))
- parser.add_option('-l', '--list',
- dest='list',
- action='store_true',
- help="List all available scaffold names")
- parser.add_option('--list-templates',
- dest='list',
- action='store_true',
- help=("A backwards compatibility alias for -l/--list. "
- "List all available scaffold names."))
- parser.add_option('--package-name',
- dest='package_name',
- action='store',
- type='string',
- help='Package name to use. The name provided is assumed '
- 'to be a valid Python package name, and will not '
- 'be validated. By default the package name is '
- 'derived from the value of output_directory.')
- parser.add_option('--simulate',
- dest='simulate',
- action='store_true',
- help='Simulate but do no work')
- parser.add_option('--overwrite',
- dest='overwrite',
- action='store_true',
- help='Always overwrite')
- parser.add_option('--interactive',
- dest='interactive',
- action='store_true',
- help='When a file would be overwritten, interrogate '
- '(this is the default, but you may specify it to '
- 'override --overwrite)')
- parser.add_option('--ignore-conflicting-name',
- dest='force_bad_name',
- action='store_true',
- default=False,
- help='Do create a project even if the chosen name '
- 'is the name of an already existing / importable '
- 'package.')
+ verbosity = 1 # required
+ parser = argparse.ArgumentParser(
+ description="""\
+Render Pyramid scaffolding to an output directory.
+
+Note: As of Pyramid 1.8, this command is deprecated. Use a specific
+cookiecutter instead:
+https://github.com/Pylons/?q=cookiecutter
+""",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+ parser.add_argument('-s', '--scaffold',
+ dest='scaffold_name',
+ action='append',
+ help=("Add a scaffold to the create process "
+ "(multiple -s args accepted)"))
+ parser.add_argument('-t', '--template',
+ dest='scaffold_name',
+ action='append',
+ help=('A backwards compatibility alias for '
+ '-s/--scaffold. Add a scaffold to the '
+ 'create process (multiple -t args accepted)'))
+ parser.add_argument('-l', '--list',
+ dest='list',
+ action='store_true',
+ help="List all available scaffold names")
+ parser.add_argument('--list-templates',
+ dest='list',
+ action='store_true',
+ help=("A backwards compatibility alias for -l/--list. "
+ "List all available scaffold names."))
+ parser.add_argument('--package-name',
+ dest='package_name',
+ action='store',
+ help='Package name to use. The name provided is '
+ 'assumed to be a valid Python package name, and '
+ 'will not be validated. By default the package '
+ 'name is derived from the value of '
+ 'output_directory.')
+ parser.add_argument('--simulate',
+ dest='simulate',
+ action='store_true',
+ help='Simulate but do no work')
+ parser.add_argument('--overwrite',
+ dest='overwrite',
+ action='store_true',
+ help='Always overwrite')
+ parser.add_argument('--interactive',
+ dest='interactive',
+ action='store_true',
+ help='When a file would be overwritten, interrogate '
+ '(this is the default, but you may specify it to '
+ 'override --overwrite)')
+ parser.add_argument('--ignore-conflicting-name',
+ dest='force_bad_name',
+ action='store_true',
+ default=False,
+ help='Do create a project even if the chosen name '
+ 'is the name of an already existing / importable '
+ 'package.')
+ parser.add_argument('output_directory',
+ nargs='?',
+ default=None,
+ help='The directory where the project will be '
+ 'created.')
pyramid_dist = pkg_resources.get_distribution("pyramid")
def __init__(self, argv, quiet=False):
self.quiet = quiet
- self.options, self.args = self.parser.parse_args(argv[1:])
- if not self.options.interactive and not self.options.overwrite:
- self.options.interactive = True
+ self.args = self.parser.parse_args(argv[1:])
+ if not self.args.interactive and not self.args.overwrite:
+ self.args.interactive = True
self.scaffolds = self.all_scaffolds()
def run(self):
- if self.options.list:
+ if self.args.list:
return self.show_scaffolds()
- if not self.options.scaffold_name and not self.args:
- if not self.quiet: # pragma: no cover
+ if not self.args.scaffold_name and not self.args.output_directory:
+ if not self.quiet: # pragma: no cover
self.parser.print_help()
self.out('')
self.show_scaffolds()
@@ -96,30 +109,31 @@ class PCreateCommand(object):
if not self.validate_input():
return 2
+ self._warn_pcreate_deprecated()
return self.render_scaffolds()
@property
def output_path(self):
- return os.path.abspath(os.path.normpath(self.args[0]))
+ return os.path.abspath(os.path.normpath(self.args.output_directory))
@property
def project_vars(self):
output_dir = self.output_path
project_name = os.path.basename(os.path.split(output_dir)[1])
- if self.options.package_name is None:
+ if self.args.package_name is None:
pkg_name = _bad_chars_re.sub(
'', project_name.lower().replace('-', '_'))
safe_name = pkg_resources.safe_name(project_name)
else:
- pkg_name = self.options.package_name
+ pkg_name = self.args.package_name
safe_name = pkg_name
egg_name = pkg_resources.to_filename(safe_name)
# get pyramid package version
pyramid_version = self.pyramid_dist.version
- ## map pyramid package version of the documentation branch ##
+ # map pyramid package version of the documentation branch ##
# if version ends with 'dev' then docs version is 'master'
if self.pyramid_dist.version[-3:] == 'dev':
pyramid_docs_branch = 'master'
@@ -141,11 +155,10 @@ class PCreateCommand(object):
'pyramid_docs_branch': pyramid_docs_branch,
}
-
def render_scaffolds(self):
props = self.project_vars
output_dir = self.output_path
- for scaffold_name in self.options.scaffold_name:
+ for scaffold_name in self.args.scaffold_name:
for scaffold in self.scaffolds:
if scaffold.name == scaffold_name:
scaffold.run(self, output_dir, props)
@@ -172,57 +185,67 @@ class PCreateCommand(object):
scaffold_class = entry.load()
scaffold = scaffold_class(entry.name)
scaffolds.append(scaffold)
- except Exception as e: # pragma: no cover
+ except Exception as e: # pragma: no cover
self.out('Warning: could not load entry point %s (%s: %s)' % (
entry.name, e.__class__.__name__, e))
return scaffolds
- def out(self, msg): # pragma: no cover
+ def out(self, msg): # pragma: no cover
if not self.quiet:
print(msg)
def validate_input(self):
- if not self.options.scaffold_name:
- self.out('You must provide at least one scaffold name: -s <scaffold name>')
+ if not self.args.scaffold_name:
+ self.out('You must provide at least one scaffold name: '
+ '-s <scaffold name>')
self.out('')
self.show_scaffolds()
return False
- if not self.args:
+ if not self.args.output_directory:
self.out('You must provide a project name')
return False
available = [x.name for x in self.scaffolds]
- diff = set(self.options.scaffold_name).difference(available)
+ diff = set(self.args.scaffold_name).difference(available)
if diff:
self.out('Unavailable scaffolds: %s' % ", ".join(sorted(diff)))
return False
pkg_name = self.project_vars['package']
- if pkg_name == 'site' and not self.options.force_bad_name:
+ if pkg_name == 'site' and not self.args.force_bad_name:
self.out('The package name "site" has a special meaning in '
'Python. Are you sure you want to use it as your '
'project\'s name?')
- return self.confirm_bad_name('Really use "{0}"?: '.format(pkg_name))
+ return self.confirm_bad_name('Really use "{0}"?: '.format(
+ pkg_name))
# check if pkg_name can be imported (i.e. already exists in current
# $PYTHON_PATH, if so - let the user confirm
pkg_exists = True
try:
- __import__(pkg_name, globals(), locals(), [], 0) # use absolute imports
+ # use absolute imports
+ __import__(pkg_name, globals(), locals(), [], 0)
except ImportError as error:
pkg_exists = False
if not pkg_exists:
return True
- if self.options.force_bad_name:
+ if self.args.force_bad_name:
return True
self.out('A package named "{0}" already exists, are you sure you want '
'to use it as your project\'s name?'.format(pkg_name))
return self.confirm_bad_name('Really use "{0}"?: '.format(pkg_name))
- def confirm_bad_name(self, prompt): # pragma: no cover
+ def confirm_bad_name(self, prompt): # pragma: no cover
answer = input_('{0} [y|N]: '.format(prompt))
return answer.strip().lower() == 'y'
-if __name__ == '__main__': # pragma: no cover
+ def _warn_pcreate_deprecated(self):
+ self.out('''\
+Note: As of Pyramid 1.8, this command is deprecated. Use a specific
+cookiecutter instead:
+https://github.com/pylons/?query=cookiecutter
+''')
+
+if __name__ == '__main__': # pragma: no cover
sys.exit(main() or 0)
diff --git a/pyramid/scripts/pdistreport.py b/pyramid/scripts/pdistreport.py
index 61098dc27..1952e5d39 100644
--- a/pyramid/scripts/pdistreport.py
+++ b/pyramid/scripts/pdistreport.py
@@ -1,7 +1,7 @@
import sys
import platform
import pkg_resources
-import optparse
+import argparse
from operator import itemgetter
def out(*args): # pragma: no cover
@@ -10,12 +10,15 @@ def out(*args): # pragma: no cover
sys.stdout.write(' ')
sys.stdout.write('\n')
+def get_parser():
+ parser = argparse.ArgumentParser(
+ description="Show Python distribution versions and locations in use")
+ return parser
+
def main(argv=sys.argv, pkg_resources=pkg_resources, platform=platform.platform,
out=out):
# all args except argv are for unit testing purposes only
- description = "Show Python distribution versions and locations in use"
- usage = "usage: %prog"
- parser = optparse.OptionParser(usage, description=description)
+ parser = get_parser()
parser.parse_args(argv[1:])
packages = []
for distribution in pkg_resources.working_set:
diff --git a/pyramid/scripts/prequest.py b/pyramid/scripts/prequest.py
index 14a132bdb..f0681afd7 100644
--- a/pyramid/scripts/prequest.py
+++ b/pyramid/scripts/prequest.py
@@ -1,13 +1,12 @@
import base64
-import optparse
+import argparse
import sys
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)
@@ -39,80 +38,100 @@ class PRequestCommand(object):
If the path is relative (doesn't begin with "/") it is interpreted as
relative to "/". The path passed to this script should be URL-quoted.
- The path can be succeeded with a query string (e.g. `/path?a=1&=b2').
+ The path can be succeeded with a query string (e.g. '/path?a=1&=b2').
The variable "environ['paste.command_request']" will be set to "True" in
the request's WSGI environment, so your application can distinguish these
calls from normal requests.
"""
- usage = "usage: %prog config_uri path_info [args/options]"
- parser = optparse.OptionParser(
- usage=usage,
- description=textwrap.dedent(description)
+
+ parser = argparse.ArgumentParser(
+ description=textwrap.dedent(description),
+ formatter_class=argparse.RawDescriptionHelpFormatter,
)
- parser.add_option(
+ parser.add_argument(
'-n', '--app-name',
dest='app_name',
metavar='NAME',
help=(
"Load the named application from the config file (default 'main')"
),
- type="string",
)
- parser.add_option(
+ parser.add_argument(
'--header',
dest='headers',
metavar='NAME:VALUE',
- type='string',
action='append',
help=(
"Header to add to request (you can use this option multiple times)"
),
)
- parser.add_option(
+ parser.add_argument(
'-d', '--display-headers',
dest='display_headers',
action='store_true',
help='Display status and headers before the response body'
)
- parser.add_option(
+ parser.add_argument(
'-m', '--method',
dest='method',
choices=['GET', 'HEAD', 'POST', 'PUT', 'PATCH','DELETE',
'PROPFIND', 'OPTIONS'],
- type='choice',
help='Request method type (GET, POST, PUT, PATCH, DELETE, '
'PROPFIND, OPTIONS)',
)
- parser.add_option(
+ parser.add_argument(
'-l', '--login',
dest='login',
- type='string',
help='HTTP basic auth username:password pair',
)
- get_app = staticmethod(get_app)
+ parser.add_argument(
+ 'config_uri',
+ nargs='?',
+ default=None,
+ help='The URI to the configuration file.',
+ )
+
+ parser.add_argument(
+ 'path_info',
+ nargs='?',
+ default=None,
+ help='The path of the request.',
+ )
+
+ parser.add_argument(
+ 'config_vars',
+ nargs='*',
+ default=(),
+ help="Variables required by the config file. For example, "
+ "`http_port=%%(http_port)s` would expect `http_port=8080` to be "
+ "passed here.",
+ )
+
+ _get_config_loader = staticmethod(get_config_loader)
stdin = sys.stdin
def __init__(self, argv, quiet=False):
self.quiet = quiet
- self.options, self.args = self.parser.parse_args(argv[1:])
+ self.args = self.parser.parse_args(argv[1:])
def out(self, msg): # pragma: no cover
if not self.quiet:
print(msg)
- def configure_logging(self, app_spec):
- setup_logging(app_spec)
-
def run(self):
- if not len(self.args) >= 2:
+ 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[0]
- path = self.args[1]
+ 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
@@ -125,12 +144,12 @@ class PRequestCommand(object):
path = url_unquote(path)
headers = {}
- if self.options.login:
- enc = base64.b64encode(self.options.login.encode('ascii'))
+ if self.args.login:
+ enc = base64.b64encode(self.args.login.encode('ascii'))
headers['Authorization'] = 'Basic ' + enc.decode('ascii')
- if self.options.headers:
- for item in self.options.headers:
+ if self.args.headers:
+ for item in self.args.headers:
if ':' not in item:
self.out(
"Bad --header=%s option, value must be in the form "
@@ -139,10 +158,7 @@ class PRequestCommand(object):
name, value = item.split(':', 1)
headers[name] = value.strip()
- app = self.get_app(app_spec, self.options.app_name,
- options=parse_vars(self.args[2:]))
-
- request_method = (self.options.method or 'GET').upper()
+ request_method = (self.args.method or 'GET').upper()
environ = {
'REQUEST_METHOD': request_method,
@@ -177,7 +193,7 @@ class PRequestCommand(object):
request = Request.blank(path, environ=environ)
response = request.get_response(app)
- if self.options.display_headers:
+ if self.args.display_headers:
self.out(response.status)
for name, value in response.headerlist:
self.out('%s: %s' % (name, value))
diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py
index f75810c06..69d61ae8f 100644
--- a/pyramid/scripts/proutes.py
+++ b/pyramid/scripts/proutes.py
@@ -1,5 +1,5 @@
import fnmatch
-import optparse
+import argparse
import sys
import textwrap
import re
@@ -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,26 +245,45 @@ 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
- usage = '%prog config_uri'
- ConfigParser = configparser.ConfigParser # testing
- parser = optparse.OptionParser(
- usage,
- description=textwrap.dedent(description)
- )
- parser.add_option('-g', '--glob',
- action='store', type='string', dest='glob',
- default='', help='Display routes matching glob pattern')
+ parser = argparse.ArgumentParser(
+ description=textwrap.dedent(description),
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+ parser.add_argument('-g', '--glob',
+ action='store',
+ dest='glob',
+ default='',
+ help='Display routes matching glob pattern')
+
+ parser.add_argument('-f', '--format',
+ action='store',
+ dest='format',
+ default='',
+ help=('Choose which columns to display, this will '
+ 'override the format key in the [proutes] ini '
+ 'section'))
+
+ parser.add_argument(
+ 'config_uri',
+ nargs='?',
+ default=None,
+ help='The URI to the configuration file.',
+ )
- parser.add_option('-f', '--format',
- action='store', type='string', dest='format',
- default='', help=('Choose which columns to display, this '
- 'will override the format key in the '
- '[proutes] ini section'))
+ parser.add_argument(
+ 'config_vars',
+ nargs='*',
+ default=(),
+ help="Variables required by the config file. For example, "
+ "`http_port=%%(http_port)s` would expect `http_port=8080` to be "
+ "passed here.",
+ )
def __init__(self, argv, quiet=False):
- self.options, self.args = self.parser.parse_args(argv[1:])
+ self.args = self.parser.parse_args(argv[1:])
self.quiet = quiet
self.available_formats = [
'name', 'pattern', 'view', 'method'
@@ -289,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:
@@ -312,19 +325,22 @@ class PRoutesCommand(object):
return config.get_routes_mapper()
def run(self, quiet=False):
- if not self.args:
+ if not self.args.config_uri:
self.out('requires a config file argument')
return 2
- config_uri = self.args[0]
- env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:]))
+ config_uri = self.args.config_uri
+ 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.options.format:
- columns = self.options.format.split(',')
+ if self.args.format:
+ columns = self.args.format.split(',')
self.column_format = [x.strip() for x in columns]
is_valid = self.validate_formats(self.column_format)
@@ -361,9 +377,9 @@ class PRoutesCommand(object):
route_data = get_route_data(route, registry)
for name, pattern, view, method in route_data:
- if self.options.glob:
- match = (fnmatch.fnmatch(name, self.options.glob) or
- fnmatch.fnmatch(pattern, self.options.glob))
+ if self.args.glob:
+ match = (fnmatch.fnmatch(name, self.args.glob) or
+ fnmatch.fnmatch(pattern, self.args.glob))
if not match:
continue
diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py
index 0d22c9f3f..f7d094980 100644
--- a/pyramid/scripts/pserve.py
+++ b/pyramid/scripts/pserve.py
@@ -8,57 +8,32 @@
# Code taken also from QP: http://www.mems-exchange.org/software/qp/ From
# lib/site.py
-import atexit
-import ctypes
-import optparse
+import argparse
import os
-import py_compile
import re
-import subprocess
import sys
-import tempfile
import textwrap
import threading
import time
-import traceback
import webbrowser
-from paste.deploy import loadserver
-from paste.deploy import loadapp
-from paste.deploy.loadwsgi import loadcontext, SERVER
+import hupper
from pyramid.compat import PY2
-from pyramid.compat import WIN
+from pyramid.scripts.common import get_config_loader
from pyramid.scripts.common import parse_vars
-from pyramid.scripts.common import setup_logging
-
-MAXFD = 1024
-
-try:
- import termios
-except ImportError: # pragma: no cover
- termios = None
-
-if WIN and not hasattr(os, 'kill'): # pragma: no cover
- # py 2.6 on windows
- def kill(pid, sig=None):
- """kill function for Win32"""
- # signal is ignored, semibogus raise message
- kernel32 = ctypes.windll.kernel32
- handle = kernel32.OpenProcess(1, 0, pid)
- if (0 == kernel32.TerminateProcess(handle, 0)):
- raise OSError('No such process %s' % pid)
-else:
- kill = os.kill
+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):
- usage = '%prog config_uri [var=value]'
description = """\
This command serves a web application that uses a PasteDeploy
configuration file for the server and application.
@@ -68,548 +43,212 @@ class PServeCommand(object):
"""
default_verbosity = 1
- parser = optparse.OptionParser(
- usage,
- description=textwrap.dedent(description)
+ parser = argparse.ArgumentParser(
+ description=textwrap.dedent(description),
+ formatter_class=argparse.RawDescriptionHelpFormatter,
)
- parser.add_option(
+ parser.add_argument(
'-n', '--app-name',
dest='app_name',
metavar='NAME',
help="Load the named application (default main)")
- parser.add_option(
+ parser.add_argument(
'-s', '--server',
dest='server',
metavar='SERVER_TYPE',
help="Use the named server.")
- parser.add_option(
+ parser.add_argument(
'--server-name',
dest='server_name',
metavar='SECTION_NAME',
help=("Use the named server as defined in the configuration file "
"(default: main)"))
- parser.add_option(
+ parser.add_argument(
'--reload',
dest='reload',
action='store_true',
help="Use auto-restart file monitor")
- parser.add_option(
+ parser.add_argument(
'--reload-interval',
dest='reload_interval',
default=1,
help=("Seconds between checking files (low number can cause "
"significant CPU usage)"))
- parser.add_option(
+ parser.add_argument(
'-b', '--browser',
dest='browser',
action='store_true',
- help="Open a web browser to server url")
- parser.add_option(
+ 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,
dest='verbose',
action='count',
help="Set verbose level (default " + str(default_verbosity) + ")")
- parser.add_option(
+ parser.add_argument(
'-q', '--quiet',
action='store_const',
const=0,
dest='verbose',
help="Suppress verbose output")
+ parser.add_argument(
+ 'config_uri',
+ nargs='?',
+ default=None,
+ help='The URI to the configuration file.',
+ )
+ parser.add_argument(
+ 'config_vars',
+ nargs='*',
+ default=(),
+ help="Variables required by the config file. For example, "
+ "`http_port=%%(http_port)s` would expect `http_port=8080` to be "
+ "passed here.",
+ )
- _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
+ _get_config_loader = staticmethod(get_config_loader) # for testing
+
+ open_url = None
- _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
- _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN'
+ _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
def __init__(self, argv, quiet=False):
- self.options, self.args = self.parser.parse_args(argv[1:])
+ self.args = self.parser.parse_args(argv[1:])
if quiet:
- self.options.verbose = 0
+ self.args.verbose = 0
+ if self.args.reload:
+ self.worker_kwargs = {'argv': argv, "quiet": quiet}
+ self.watch_files = set()
- def out(self, msg): # pragma: no cover
- if self.options.verbose > 0:
+ def out(self, msg): # pragma: no cover
+ if self.args.verbose > 0:
print(msg)
- def get_options(self):
- restvars = self.args[1:]
- return parse_vars(restvars)
+ def get_config_path(self, loader):
+ return os.path.abspath(loader.uri.path)
+
+ 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)
+ for file in watch_files:
+ if ':' in file:
+ file = resolver.resolve(file).abspath()
+ elif not os.path.isabs(file):
+ file = os.path.join(here, 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:
+ if not self.args.config_uri:
self.out('You must give a config file')
return 2
- app_spec = self.args[0]
-
- if self.options.reload:
- if os.environ.get(self._reloader_environ_key):
- if self.options.verbose > 1:
- self.out('Running reloading file monitor')
- install_reloader(int(self.options.reload_interval), [app_spec])
- # if self.requires_config_file:
- # watch_file(self.args[0])
- else:
- return self.restart_with_reloader()
+ config_uri = self.args.config_uri
+ config_vars = parse_vars(self.args.config_vars)
+ app_spec = self.args.config_uri
+ app_name = self.args.app_name
- app_name = self.options.app_name
+ loader = self._get_config_loader(config_uri)
+ loader.setup_logging(config_vars)
- vars = self.get_options()
+ self.pserve_file_config(loader, global_conf=config_vars)
- if not self._scheme_re.search(app_spec):
- app_spec = 'config:' + app_spec
- server_name = self.options.server_name
- if self.options.server:
+ server_name = self.args.server_name
+ if self.args.server:
server_spec = 'egg:pyramid'
assert server_name is None
- server_name = self.options.server
+ server_name = self.args.server
else:
server_spec = app_spec
- base = os.getcwd()
- log_fn = app_spec
- if log_fn.startswith('config:'):
- log_fn = app_spec[len('config:'):]
- elif log_fn.startswith('egg:'):
- log_fn = None
- if log_fn:
- log_fn = os.path.join(base, log_fn)
- setup_logging(log_fn, global_conf=vars)
+ server_loader = loader
+ if server_spec != app_spec:
+ server_loader = self.get_config_loader(server_spec)
- server = self.loadserver(server_spec, name=server_name,
- relative_to=base, global_conf=vars)
+ # do not open the browser on each reload so check hupper first
+ if self.args.browser and not hupper.is_active():
+ url = self.open_url
- app = self.loadapp(app_spec, name=app_name, relative_to=base,
- global_conf=vars)
+ if not url:
+ url = self.guess_server_url(
+ server_loader, server_name, config_vars)
- if self.options.verbose > 0:
+ 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:
+ self.out('Running reloading file monitor')
+ hupper.start_reloader(
+ 'pyramid.scripts.pserve.main',
+ reload_interval=int(self.args.reload_interval),
+ verbose=self.args.verbose,
+ worker_kwargs=self.worker_kwargs
+ )
+ return 0
+
+ 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(list(self.watch_files))
+
+ server = server_loader.get_wsgi_server(server_name, config_vars)
+
+ app = loader.get_wsgi_app(app_name, config_vars)
+
+ if self.args.verbose > 0:
if hasattr(os, 'getpid'):
msg = 'Starting server in PID %i.' % os.getpid()
else:
msg = 'Starting server.'
self.out(msg)
- def serve():
- try:
- server(app)
- except (SystemExit, KeyboardInterrupt) as e:
- if self.options.verbose > 1:
- raise
- if str(e):
- msg = ' ' + str(e)
- else:
- msg = ''
- self.out('Exiting%s (-v to see traceback)' % msg)
-
- if self.options.browser:
- 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()
-
- serve()
-
- def loadapp(self, app_spec, name, relative_to, **kw): # pragma: no cover
- return loadapp(app_spec, name=name, relative_to=relative_to, **kw)
-
- def loadserver(self, server_spec, name, relative_to, **kw):# pragma:no cover
- return loadserver(
- server_spec, name=name, relative_to=relative_to, **kw)
-
- def quote_first_command_arg(self, arg): # pragma: no cover
- """
- There's a bug in Windows when running an executable that's
- located inside a path with a space in it. This method handles
- that case, or on non-Windows systems or an executable with no
- spaces, it just leaves well enough alone.
- """
- if (sys.platform != 'win32' or ' ' not in arg):
- # Problem does not apply:
- return arg
try:
- import win32api
- except ImportError:
- raise ValueError(
- "The executable %r contains a space, and in order to "
- "handle this issue you must have the win32api module "
- "installed" % arg)
- arg = win32api.GetShortPathName(arg)
- return arg
-
- def find_script_path(self, name): # pragma: no cover
- """
- Return the path to the script being invoked by the python interpreter.
-
- There's an issue on Windows when running the executable from
- a console_script causing the script name (sys.argv[0]) to
- not end with .exe or .py and thus cannot be run via popen.
- """
- if sys.platform == 'win32':
- if not name.endswith('.exe') and not name.endswith('.py'):
- name += '.exe'
- return name
-
- def restart_with_reloader(self): # pragma: no cover
- self.restart_with_monitor(reloader=True)
-
- def restart_with_monitor(self, reloader=False): # pragma: no cover
- if self.options.verbose > 0:
- if reloader:
- self.out('Starting subprocess with file monitor')
+ server(app)
+ except (SystemExit, KeyboardInterrupt) as e:
+ if self.args.verbose > 1:
+ raise
+ if str(e):
+ msg = ' ' + str(e)
else:
- self.out('Starting subprocess with monitor parent')
- while 1:
- args = [
- self.quote_first_command_arg(sys.executable),
- self.find_script_path(sys.argv[0]),
- ] + sys.argv[1:]
- new_environ = os.environ.copy()
- if reloader:
- new_environ[self._reloader_environ_key] = 'true'
- else:
- new_environ[self._monitor_environ_key] = 'true'
- proc = None
- try:
- try:
- _turn_sigterm_into_systemexit()
- proc = subprocess.Popen(args, env=new_environ)
- exit_code = proc.wait()
- proc = None
- except KeyboardInterrupt:
- self.out('^C caught in monitor process')
- if self.options.verbose > 1:
- raise
- return 1
- finally:
- if proc is not None:
- import signal
- try:
- kill(proc.pid, signal.SIGTERM)
- except (OSError, IOError):
- pass
-
- if reloader:
- # Reloader always exits with code 3; but if we are
- # a monitor, any exit code will restart
- if exit_code != 3:
- return exit_code
- if self.options.verbose > 0:
- self.out('%s %s %s' % ('-' * 20, 'Restarting', '-' * 20))
-
-class LazyWriter(object):
-
- """
- File-like object that opens a file lazily when it is first written
- to.
- """
-
- def __init__(self, filename, mode='w'):
- self.filename = filename
- self.fileobj = None
- self.lock = threading.Lock()
- self.mode = mode
-
- def open(self):
- if self.fileobj is None:
- with self.lock:
- self.fileobj = open(self.filename, self.mode)
- return self.fileobj
-
- def close(self):
- fileobj = self.fileobj
- if fileobj is not None:
- fileobj.close()
-
- def __del__(self):
- self.close()
-
- def write(self, text):
- fileobj = self.open()
- fileobj.write(text)
- fileobj.flush()
-
- def writelines(self, text):
- fileobj = self.open()
- fileobj.writelines(text)
- fileobj.flush()
-
- def flush(self):
- self.open().flush()
-
-def ensure_port_cleanup(
- bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover
- """
- This makes sure any open ports are closed.
-
- Does this by connecting to them until they give connection
- refused. Servers should call like::
-
- ensure_port_cleanup([80, 443])
- """
- atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries,
- sleeptime=sleeptime)
-
-def _cleanup_ports(
- bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover
- # Wait for the server to bind to the port.
- import socket
- import errno
- for bound_address in bound_addresses:
- for attempt in range(maxtries):
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- try:
- sock.connect(bound_address)
- except socket.error as e:
- if e.args[0] != errno.ECONNREFUSED:
- raise
- break
- else:
- time.sleep(sleeptime)
- else:
- raise SystemExit('Timeout waiting for port.')
- sock.close()
-
-def _turn_sigterm_into_systemexit(): # pragma: no cover
- """
- Attempts to turn a SIGTERM exception into a SystemExit exception.
- """
- try:
- import signal
- except ImportError:
- return
- def handle_term(signo, frame):
- raise SystemExit
- signal.signal(signal.SIGTERM, handle_term)
-
-def ensure_echo_on(): # pragma: no cover
- if termios:
- fd = sys.stdin
- if fd.isatty():
- attr_list = termios.tcgetattr(fd)
- if not attr_list[3] & termios.ECHO:
- attr_list[3] |= termios.ECHO
- termios.tcsetattr(fd, termios.TCSANOW, attr_list)
-
-def install_reloader(poll_interval=1, extra_files=None): # pragma: no cover
- """
- Install the reloading monitor.
-
- On some platforms server threads may not terminate when the main
- thread does, causing ports to remain open/locked.
- """
- ensure_echo_on()
- mon = Monitor(poll_interval=poll_interval)
- if extra_files is None:
- extra_files = []
- mon.extra_files.extend(extra_files)
- t = threading.Thread(target=mon.periodic_reload)
- t.setDaemon(True)
- t.start()
-
-class classinstancemethod(object):
- """
- Acts like a class method when called from a class, like an
- instance method when called by an instance. The method should
- take two arguments, 'self' and 'cls'; one of these will be None
- depending on how the method was called.
- """
-
- def __init__(self, func):
- self.func = func
- self.__doc__ = func.__doc__
+ msg = ''
+ self.out('Exiting%s (-v to see traceback)' % msg)
- def __get__(self, obj, type=None):
- return _methodwrapper(self.func, obj=obj, type=type)
-
-class _methodwrapper(object):
-
- def __init__(self, func, obj, type):
- self.func = func
- self.obj = obj
- self.type = type
-
- def __call__(self, *args, **kw):
- assert 'self' not in kw and 'cls' not in kw, (
- "You cannot use 'self' or 'cls' arguments to a "
- "classinstancemethod")
- return self.func(*((self.obj, self.type) + args), **kw)
-
-
-class Monitor(object): # pragma: no cover
- """
- A file monitor and server restarter.
-
- Use this like:
-
- ..code-block:: Python
-
- install_reloader()
-
- Then make sure your server is installed with a shell script like::
-
- err=3
- while test "$err" -eq 3 ; do
- python server.py
- err="$?"
- done
-
- or is run from this .bat file (if you use Windows)::
-
- @echo off
- :repeat
- python server.py
- if %errorlevel% == 3 goto repeat
-
- or run a monitoring process in Python (``pserve --reload`` does
- this).
-
- Use the ``watch_file(filename)`` function to cause a reload/restart for
- other non-Python files (e.g., configuration files). If you have
- a dynamic set of files that grows over time you can use something like::
-
- def watch_config_files():
- return CONFIG_FILE_CACHE.keys()
- add_file_callback(watch_config_files)
-
- Then every time the reloader polls files it will call
- ``watch_config_files`` and check all the filenames it returns.
- """
- instances = []
- global_extra_files = []
- global_file_callbacks = []
-
- def __init__(self, poll_interval):
- self.module_mtimes = {}
- self.keep_running = True
- self.poll_interval = poll_interval
- self.extra_files = list(self.global_extra_files)
- self.instances.append(self)
- self.syntax_error_files = set()
- self.pending_reload = False
- self.file_callbacks = list(self.global_file_callbacks)
- temp_pyc_fp = tempfile.NamedTemporaryFile(delete=False)
- self.temp_pyc = temp_pyc_fp.name
- temp_pyc_fp.close()
-
- def _exit(self):
- try:
- os.unlink(self.temp_pyc)
- except IOError:
- # not worried if the tempfile can't be removed
- pass
- # use os._exit() here and not sys.exit() since within a
- # thread sys.exit() just closes the given thread and
- # won't kill the process; note os._exit does not call
- # any atexit callbacks, nor does it do finally blocks,
- # flush open files, etc. In otherwords, it is rude.
- os._exit(3)
-
- def periodic_reload(self):
- while True:
- if not self.check_reload():
- self._exit()
- break
- time.sleep(self.poll_interval)
-
- def check_reload(self):
- filenames = list(self.extra_files)
- for file_callback in self.file_callbacks:
- try:
- filenames.extend(file_callback())
- except:
- print(
- "Error calling reloader callback %r:" % file_callback)
- traceback.print_exc()
- for module in list(sys.modules.values()):
- try:
- filename = module.__file__
- except (AttributeError, ImportError):
- continue
- if filename is not None:
- filenames.append(filename)
- new_changes = False
- for filename in filenames:
- try:
- stat = os.stat(filename)
- if stat:
- mtime = stat.st_mtime
- else:
- mtime = 0
- except (OSError, IOError):
- continue
- if filename.endswith('.pyc') and os.path.exists(filename[:-1]):
- mtime = max(os.stat(filename[:-1]).st_mtime, mtime)
- pyc = True
- else:
- pyc = False
- old_mtime = self.module_mtimes.get(filename)
- self.module_mtimes[filename] = mtime
- if old_mtime is not None and old_mtime < mtime:
- new_changes = True
- if pyc:
- filename = filename[:-1]
- is_valid = True
- if filename.endswith('.py'):
- is_valid = self.check_syntax(filename)
- if is_valid:
- print("%s changed ..." % filename)
- if new_changes:
- self.pending_reload = True
- if self.syntax_error_files:
- for filename in sorted(self.syntax_error_files):
- print("%s has a SyntaxError; NOT reloading." % filename)
- if self.pending_reload and not self.syntax_error_files:
- self.pending_reload = False
- return False
- return True
-
- def check_syntax(self, filename):
- # check if a file has syntax errors.
- # If so, track it until it's fixed.
- try:
- py_compile.compile(filename, cfile=self.temp_pyc, doraise=True)
- except py_compile.PyCompileError as ex:
- print(ex.msg)
- self.syntax_error_files.add(filename)
- return False
- else:
- if filename in self.syntax_error_files:
- self.syntax_error_files.remove(filename)
- return True
-
- def watch_file(self, cls, filename):
- """Watch the named file for changes"""
- filename = os.path.abspath(filename)
- if self is None:
- for instance in cls.instances:
- instance.watch_file(filename)
- cls.global_extra_files.append(filename)
- else:
- self.extra_files.append(filename)
-
- watch_file = classinstancemethod(watch_file)
-
- def add_file_callback(self, cls, callback):
- """Add a callback -- a function that takes no parameters -- that will
- return a list of filenames to watch for changes."""
- if self is None:
- for instance in cls.instances:
- instance.add_file_callback(callback)
- cls.global_file_callbacks.append(callback)
- else:
- self.file_callbacks.append(callback)
-
- add_file_callback = classinstancemethod(add_file_callback)
-
-watch_file = Monitor.watch_file
-add_file_callback = Monitor.add_file_callback
# 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))
@@ -617,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
@@ -730,5 +370,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 56b1a15fa..bb201dbc2 100644
--- a/pyramid/scripts/pshell.py
+++ b/pyramid/scripts/pshell.py
@@ -1,19 +1,18 @@
from code import interact
-import optparse
+import argparse
import os
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)
@@ -28,13 +27,12 @@ def python_shell_runner(env, help, interact=interact):
class PShellCommand(object):
- usage = '%prog config_uri'
description = """\
Open an interactive shell with a Pyramid app loaded. This command
accepts one positional argument named "config_uri" which specifies the
PasteDeploy config file to use for the interactive shell. The format is
"inifile#name". If the name is left off, the Pyramid default application
- will be assumed. Example: "pshell myapp.ini#main"
+ will be assumed. Example: "pshell myapp.ini#main".
If you do not point the loader directly at the section of the ini file
containing your Pyramid application, the command will attempt to
@@ -42,31 +40,44 @@ 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 = optparse.OptionParser(
- usage,
- description=textwrap.dedent(description)
+ parser = argparse.ArgumentParser(
+ description=textwrap.dedent(description),
+ formatter_class=argparse.RawDescriptionHelpFormatter,
)
- parser.add_option('-p', '--python-shell',
- action='store', type='string', dest='python_shell',
- default='',
- help=('Select the shell to use. A list of possible '
- 'shells is available using the --list-shells '
- 'option.'))
- parser.add_option('-l', '--list-shells',
- dest='list',
- action='store_true',
- help='List all available shells.')
- parser.add_option('--setup',
- dest='setup',
- help=("A callable that will be passed the environment "
- "before it is made available to the shell. This "
- "option will override the 'setup' key in the "
- "[pshell] ini section."))
-
- ConfigParser = configparser.ConfigParser # testing
+ parser.add_argument('-p', '--python-shell',
+ action='store',
+ dest='python_shell',
+ default='',
+ help=('Select the shell to use. A list of possible '
+ 'shells is available using the --list-shells '
+ 'option.'))
+ parser.add_argument('-l', '--list-shells',
+ dest='list',
+ action='store_true',
+ help='List all available shells.')
+ parser.add_argument('--setup',
+ dest='setup',
+ help=("A callable that will be passed the environment "
+ "before it is made available to the shell. This "
+ "option will override the 'setup' key in the "
+ "[pshell] ini section."))
+ parser.add_argument('config_uri',
+ nargs='?',
+ default=None,
+ help='The URI to the configuration file.')
+ parser.add_argument(
+ 'config_vars',
+ nargs='*',
+ default=(),
+ help="Variables required by the config file. For example, "
+ "`http_port=%%(http_port)s` would expect `http_port=8080` to be "
+ "passed here.",
+ )
+
default_runner = python_shell_runner # testing
loaded_objects = {}
@@ -77,22 +88,15 @@ class PShellCommand(object):
def __init__(self, argv, quiet=False):
self.quiet = quiet
- self.options, 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
+ self.args = self.parser.parse_args(argv[1:])
+ 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':
@@ -106,18 +110,18 @@ class PShellCommand(object):
print(msg)
def run(self, shell=None):
- if self.options.list:
+ if self.args.list:
return self.show_shells()
- if not self.args:
+ if not self.args.config_uri:
self.out('Requires a config file argument')
return 2
- config_uri = self.args[0]
- config_file = config_uri.split('#', 1)[0]
- setup_logging(config_file)
- self.pshell_file_config(config_file)
+ config_uri = self.args.config_uri
+ 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[1:]))
+ env = self.bootstrap(config_uri, options=config_vars)
# remove the closer from the env
self.closer = env.pop('closer')
@@ -132,8 +136,8 @@ class PShellCommand(object):
'Default root factory used to create `root`.')
# override use_script with command-line options
- if self.options.setup:
- self.setup = self.options.setup
+ if self.args.setup:
+ self.setup = self.args.setup
if self.setup:
# store the env before muddling it with the script
@@ -214,7 +218,7 @@ class PShellCommand(object):
shells = self.find_all_shells()
shell = None
- user_shell = self.options.python_shell.lower()
+ user_shell = self.args.python_shell.lower()
if not user_shell:
preferred_shells = self.preferred_shells
diff --git a/pyramid/scripts/ptweens.py b/pyramid/scripts/ptweens.py
index a7aa009da..d5cbebe12 100644
--- a/pyramid/scripts/ptweens.py
+++ b/pyramid/scripts/ptweens.py
@@ -1,4 +1,4 @@
-import optparse
+import argparse
import sys
import textwrap
@@ -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):
@@ -14,7 +15,6 @@ def main(argv=sys.argv, quiet=False):
return command.run()
class PTweensCommand(object):
- usage = '%prog config_uri'
description = """\
Print all implicit and explicit tween objects used by a Pyramid
application. The handler output includes whether the system is using an
@@ -28,17 +28,32 @@ class PTweensCommand(object):
will be assumed. Example: "ptweens myapp.ini#main".
"""
- parser = optparse.OptionParser(
- usage,
+ parser = argparse.ArgumentParser(
description=textwrap.dedent(description),
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+
+ parser.add_argument('config_uri',
+ nargs='?',
+ default=None,
+ help='The URI to the configuration file.')
+
+ parser.add_argument(
+ 'config_vars',
+ nargs='*',
+ default=(),
+ help="Variables required by the config file. For example, "
+ "`http_port=%%(http_port)s` would expect `http_port=8080` to be "
+ "passed here.",
)
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
- self.options, self.args = self.parser.parse_args(argv[1:])
+ self.args = self.parser.parse_args(argv[1:])
def _get_tweens(self, registry):
from pyramid.config import Configurator
@@ -59,11 +74,13 @@ class PTweensCommand(object):
self.out(fmt % ('-', MAIN))
def run(self):
- if not self.args:
+ if not self.args.config_uri:
self.out('Requires a config file argument')
return 2
- config_uri = self.args[0]
- env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:]))
+ config_uri = self.args.config_uri
+ 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 9018eddb4..c0df2f078 100644
--- a/pyramid/scripts/pviews.py
+++ b/pyramid/scripts/pviews.py
@@ -1,9 +1,10 @@
-import optparse
+import argparse
import sys
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
@@ -13,7 +14,6 @@ def main(argv=sys.argv, quiet=False):
return command.run()
class PViewsCommand(object):
- usage = '%prog config_uri url'
description = """\
Print, for a given URL, the views that might match. Underneath each
potentially matching route, list the predicates required. Underneath
@@ -28,16 +28,36 @@ class PViewsCommand(object):
"""
stdout = sys.stdout
- parser = optparse.OptionParser(
- usage,
- description=textwrap.dedent(description)
+ parser = argparse.ArgumentParser(
+ description=textwrap.dedent(description),
+ formatter_class=argparse.RawDescriptionHelpFormatter,
)
- bootstrap = (bootstrap,) # testing
+ parser.add_argument('config_uri',
+ nargs='?',
+ default=None,
+ help='The URI to the configuration file.')
+
+ parser.add_argument('url',
+ nargs='?',
+ default=None,
+ help='The path info portion of the URL.')
+ parser.add_argument(
+ 'config_vars',
+ nargs='*',
+ default=(),
+ help="Variables required by the config file. For example, "
+ "`http_port=%%(http_port)s` would expect `http_port=8080` to be "
+ "passed here.",
+ )
+
+
+ bootstrap = staticmethod(bootstrap) # testing
+ setup_logging = staticmethod(setup_logging) # testing
def __init__(self, argv, quiet=False):
self.quiet = quiet
- self.options, self.args = self.parser.parse_args(argv[1:])
+ self.args = self.parser.parse_args(argv[1:])
def out(self, msg): # pragma: no cover
if not self.quiet:
@@ -230,17 +250,19 @@ class PViewsCommand(object):
self.out("%sview predicates (%s)" % (indent, predicate_text))
def run(self):
- if len(self.args) < 2:
+ if not self.args.config_uri or not self.args.url:
self.out('Command requires a config file arg and a url arg')
return 2
- config_uri = self.args[0]
- url = self.args[1]
+ 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[2:]),
- 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/session.py b/pyramid/session.py
index a3cbe5172..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 check does succeed 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 0965be95c..a8088129e 100644
--- a/pyramid/static.py
+++ b/pyramid/static.py
@@ -32,7 +32,12 @@ from pyramid.httpexceptions import (
)
from pyramid.path import caller_package
-from pyramid.response import FileResponse
+
+from pyramid.response import (
+ _guess_type,
+ FileResponse,
+)
+
from pyramid.traversal import traversal_path_info
slash = text_('/')
@@ -83,7 +88,7 @@ class static_view(object):
"""
def __init__(self, root_dir, cache_max_age=3600, package_name=None,
- use_subpath=False, index='index.html', cachebust_match=None):
+ use_subpath=False, index='index.html'):
# package_name is for bw compat; it is preferred to pass in a
# package-relative path as root_dir
# (e.g. ``anotherpackage:foo/static``).
@@ -96,15 +101,12 @@ class static_view(object):
self.docroot = docroot
self.norm_docroot = normcase(normpath(docroot))
self.index = index
- self.cachebust_match = cachebust_match
def __call__(self, context, request):
if self.use_subpath:
path_tuple = request.subpath
else:
path_tuple = traversal_path_info(request.environ['PATH_INFO'])
- if self.cachebust_match:
- path_tuple = self.cachebust_match(path_tuple)
path = _secure_path(path_tuple)
if path is None:
@@ -134,7 +136,10 @@ class static_view(object):
if not exists(filepath):
raise HTTPNotFound(request.url)
- return FileResponse(filepath, request, self.cache_max_age)
+ content_type, content_encoding = _guess_type(filepath)
+ return FileResponse(
+ filepath, request, self.cache_max_age,
+ content_type, content_encoding=None)
def add_slash_redirect(self, request):
url = request.path_url + '/'
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 877b351db..69b30e83f 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:
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_i18n.py b/pyramid/tests/test_config/test_i18n.py
index 71c68af8a..c10ab6bdb 100644
--- a/pyramid/tests/test_config/test_i18n.py
+++ b/pyramid/tests/test_config/test_i18n.py
@@ -36,9 +36,8 @@ class TestI18NConfiguratorMixin(unittest.TestCase):
def test_add_translation_dirs_missing_dir(self):
from pyramid.exceptions import ConfigurationError
config = self._makeOne()
- self.assertRaises(ConfigurationError,
- config.add_translation_dirs,
- '/wont/exist/on/my/system')
+ config.add_translation_dirs('/wont/exist/on/my/system')
+ self.assertRaises(ConfigurationError, config.commit)
def test_add_translation_dirs_no_specs(self):
from pyramid.interfaces import ITranslationDirectories
@@ -80,6 +79,23 @@ class TestI18NConfiguratorMixin(unittest.TestCase):
self.assertEqual(config.registry.getUtility(ITranslationDirectories),
[locale3, locale, locale2])
+ def test_add_translation_dirs_override_multiple_specs_multiple_calls(self):
+ from pyramid.interfaces import ITranslationDirectories
+ config = self._makeOne(autocommit=True)
+ config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale',
+ 'pyramid.tests.pkgs.localeapp:locale2')
+ config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale3',
+ override=True)
+ self.assertEqual(config.registry.getUtility(ITranslationDirectories),
+ [locale, locale2, locale3])
+
+ def test_add_translation_dirs_invalid_kwargs(self):
+ from pyramid.interfaces import ITranslationDirectories
+ config = self._makeOne(autocommit=True)
+ with self.assertRaises(TypeError):
+ config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale',
+ foo=1)
+
def test_add_translation_dirs_abspath(self):
from pyramid.interfaces import ITranslationDirectories
config = self._makeOne(autocommit=True)
@@ -87,3 +103,30 @@ class TestI18NConfiguratorMixin(unittest.TestCase):
self.assertEqual(config.registry.getUtility(ITranslationDirectories),
[locale])
+ def test_add_translation_dirs_uses_override_out_of_order(self):
+ from pyramid.interfaces import ITranslationDirectories
+ config = self._makeOne()
+ config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale')
+ config.override_asset('pyramid.tests.pkgs.localeapp:locale/',
+ 'pyramid.tests.pkgs.localeapp:locale2/')
+ config.commit()
+ self.assertEqual(config.registry.getUtility(ITranslationDirectories),
+ [locale2])
+
+ def test_add_translation_dirs_doesnt_use_override_w_autocommit(self):
+ from pyramid.interfaces import ITranslationDirectories
+ config = self._makeOne(autocommit=True)
+ config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale')
+ config.override_asset('pyramid.tests.pkgs.localeapp:locale/',
+ 'pyramid.tests.pkgs.localeapp:locale2/')
+ self.assertEqual(config.registry.getUtility(ITranslationDirectories),
+ [locale])
+
+ def test_add_translation_dirs_uses_override_w_autocommit(self):
+ from pyramid.interfaces import ITranslationDirectories
+ config = self._makeOne(autocommit=True)
+ config.override_asset('pyramid.tests.pkgs.localeapp:locale/',
+ 'pyramid.tests.pkgs.localeapp:locale2/')
+ config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale')
+ self.assertEqual(config.registry.getUtility(ITranslationDirectories),
+ [locale2])
diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py
index 7078d7e26..ab584cc3d 100644
--- a/pyramid/tests/test_config/test_init.py
+++ b/pyramid/tests/test_config/test_init.py
@@ -91,15 +91,72 @@ class ConfiguratorTests(unittest.TestCase):
{'registry':config.registry, 'request':request})
self.assertEqual(manager.popped, False)
+ def test_begin_overrides_request(self):
+ from pyramid.config import Configurator
+ config = Configurator()
+ manager = DummyThreadLocalManager()
+ req = object()
+ # set it up for auto-propagation
+ pushed = {'registry': config.registry, 'request': None}
+ manager.pushed = pushed
+ config.manager = manager
+ config.begin(req)
+ self.assertTrue(manager.pushed is not pushed)
+ self.assertEqual(manager.pushed['request'], req)
+ self.assertEqual(manager.pushed['registry'], config.registry)
+
+ def test_begin_propagates_request_for_same_registry(self):
+ from pyramid.config import Configurator
+ config = Configurator()
+ manager = DummyThreadLocalManager()
+ req = object()
+ pushed = {'registry': config.registry, 'request': req}
+ manager.pushed = pushed
+ config.manager = manager
+ config.begin()
+ self.assertTrue(manager.pushed is not pushed)
+ self.assertEqual(manager.pushed['request'], req)
+ self.assertEqual(manager.pushed['registry'], config.registry)
+
+ def test_begin_does_not_propagate_request_for_diff_registry(self):
+ from pyramid.config import Configurator
+ config = Configurator()
+ manager = DummyThreadLocalManager()
+ req = object()
+ pushed = {'registry': object(), 'request': req}
+ manager.pushed = pushed
+ config.manager = manager
+ config.begin()
+ self.assertTrue(manager.pushed is not pushed)
+ self.assertEqual(manager.pushed['request'], None)
+ self.assertEqual(manager.pushed['registry'], config.registry)
+
def test_end(self):
from pyramid.config import Configurator
config = Configurator()
manager = DummyThreadLocalManager()
+ pushed = manager.pushed
config.manager = manager
config.end()
- self.assertEqual(manager.pushed, None)
+ 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
@@ -776,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)
@@ -1940,10 +2007,13 @@ class DummyRequest:
self.cookies = {}
class DummyThreadLocalManager(object):
- pushed = None
- popped = False
+ def __init__(self):
+ self.pushed = {'registry': None, 'request': None}
+ self.popped = False
def push(self, d):
self.pushed = d
+ def get(self):
+ return self.pushed
def pop(self):
self.popped = True
diff --git a/pyramid/tests/test_config/test_settings.py b/pyramid/tests/test_config/test_settings.py
index d2a98b347..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,6 +12,13 @@ class TestSettingsConfiguratorMixin(unittest.TestCase):
settings = config._set_settings(None)
self.assertTrue(settings)
+ def test__set_settings_does_not_uses_original_dict(self):
+ config = self._makeOne()
+ dummy = {}
+ result = config._set_settings(dummy)
+ self.assertTrue(dummy is not result)
+ self.assertNotIn('pyramid.debug_all', dummy)
+
def test__set_settings_as_dictwithvalues(self):
config = self._makeOne()
settings = config._set_settings({'a':'1'})
@@ -56,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):
@@ -68,26 +94,6 @@ class TestSettings(unittest.TestCase):
klass = self._getTargetClass()
return klass(d, _environ_=environ)
- def test_getattr_success(self):
- import warnings
- with warnings.catch_warnings(record=True) as w:
- warnings.filterwarnings('always')
- settings = self._makeOne({'reload_templates':False})
- self.assertEqual(settings.reload_templates, False)
- self.assertEqual(len(w), 1)
-
- def test_getattr_fail(self):
- import warnings
- with warnings.catch_warnings(record=True) as w:
- warnings.filterwarnings('always')
- settings = self._makeOne({})
- self.assertRaises(AttributeError, settings.__getattr__, 'wontexist')
- self.assertEqual(len(w), 0)
-
- def test_getattr_raises_attribute_error(self):
- settings = self._makeOne()
- self.assertRaises(AttributeError, settings.__getattr__, 'mykey')
-
def test_noargs(self):
settings = self._makeOne()
self.assertEqual(settings['debug_authorization'], False)
@@ -557,6 +563,18 @@ class TestSettings(unittest.TestCase):
self.assertEqual(result['default_locale_name'], 'abc')
self.assertEqual(result['pyramid.default_locale_name'], 'abc')
+ def test_csrf_trusted_origins(self):
+ result = self._makeOne({})
+ self.assertEqual(result['pyramid.csrf_trusted_origins'], [])
+ result = self._makeOne({'pyramid.csrf_trusted_origins': 'example.com'})
+ self.assertEqual(result['pyramid.csrf_trusted_origins'], ['example.com'])
+ result = self._makeOne({'pyramid.csrf_trusted_origins': ['example.com']})
+ self.assertEqual(result['pyramid.csrf_trusted_origins'], ['example.com'])
+ result = self._makeOne({'pyramid.csrf_trusted_origins': (
+ 'example.com foo.example.com\nasdf.example.com')})
+ self.assertEqual(result['pyramid.csrf_trusted_origins'], [
+ 'example.com', 'foo.example.com', 'asdf.example.com'])
+
def test_originals_kept(self):
result = self._makeOne({'a':'i am so a'})
self.assertEqual(result['a'], 'i am so a')
diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py
index ccf7fa260..398b6fba8 100644
--- a/pyramid/tests/test_config/test_util.py
+++ b/pyramid/tests/test_config/test_util.py
@@ -5,7 +5,7 @@ class TestPredicateList(unittest.TestCase):
def _makeOne(self):
from pyramid.config.util import PredicateList
- from pyramid.config import predicates
+ from pyramid import predicates
inst = PredicateList()
for name, factory in (
('xhr', predicates.XHRPredicate),
@@ -594,6 +594,15 @@ class TestNotted(unittest.TestCase):
self.assertEqual(inst.phash(), '')
self.assertEqual(inst(None, None), True)
+
+class TestDeprecatedPredicates(unittest.TestCase):
+ def test_it(self):
+ import warnings
+ with warnings.catch_warnings(record=True) as w:
+ warnings.filterwarnings('always')
+ from pyramid.config.predicates import XHRPredicate
+ self.assertEqual(len(w), 1)
+
class DummyPredicate(object):
def __init__(self, result):
self.result = result
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index f020485de..0816d9958 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,
@@ -2309,9 +2310,9 @@ class TestViewsConfigurationMixin(unittest.TestCase):
# Since Python 3 has to be all cool and fancy and different...
def _assertBody(self, response, value):
from pyramid.compat import text_type
- if isinstance(value, text_type): # pragma: nocover
+ if isinstance(value, text_type): # pragma: no cover
self.assertEqual(response.text, value)
- else: # pragma: nocover
+ else: # pragma: no cover
self.assertEqual(response.body, value)
def test_add_notfound_view_with_renderer(self):
@@ -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']
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_httpexceptions.py b/pyramid/tests/test_httpexceptions.py
index 6c6e16d55..e2d463008 100644
--- a/pyramid/tests/test_httpexceptions.py
+++ b/pyramid/tests/test_httpexceptions.py
@@ -2,6 +2,7 @@ import unittest
from pyramid.compat import (
bytes_,
+ string_types,
text_,
)
@@ -348,7 +349,7 @@ class TestHTTPException(unittest.TestCase):
exc = cls(body_template='${REQUEST_METHOD}')
environ = _makeEnviron()
class Choke(object):
- def __str__(self): # pragma nocover
+ def __str__(self): # pragma no cover
raise ValueError
environ['gardentheory.user'] = Choke()
start_response = DummyStartResponse()
@@ -364,6 +365,11 @@ class TestHTTPException(unittest.TestCase):
body = list(exc(environ, start_response))[0]
self.assertEqual(body, b'200 OK\n\n/La Pe\xc3\xb1a')
+ def test_allow_detail_non_str(self):
+ exc = self._makeOne(detail={'error': 'This is a test'})
+ self.assertIsInstance(exc.__str__(), string_types)
+
+
class TestRenderAllExceptionsWithoutArguments(unittest.TestCase):
def _doit(self, content_type):
from pyramid.httpexceptions import status_map
diff --git a/pyramid/tests/test_i18n.py b/pyramid/tests/test_i18n.py
index 67b2ac356..d72d0d480 100644
--- a/pyramid/tests/test_i18n.py
+++ b/pyramid/tests/test_i18n.py
@@ -357,6 +357,36 @@ class TestTranslations(unittest.TestCase):
inst.add(inst2)
self.assertEqual(inst._catalog['a'], 'b')
+ def test_add_default_domain_replaces_plural_first_time(self):
+ # Create three empty message catalogs in the default domain
+ inst = self._getTargetClass()(None, domain='messages')
+ inst2 = self._getTargetClass()(None, domain='messages')
+ inst3 = self._getTargetClass()(None, domain='messages')
+ inst._catalog = {}
+ inst2._catalog = {}
+ inst3._catalog = {}
+
+ # The default plural scheme is the germanic one
+ self.assertEqual(inst.plural(0), 1)
+ self.assertEqual(inst.plural(1), 0)
+ self.assertEqual(inst.plural(2), 1)
+
+ # inst2 represents a message file that declares french plurals
+ inst2.plural = lambda n: n > 1
+ inst.add(inst2)
+ # that plural rule should now apply to inst
+ self.assertEqual(inst.plural(0), 0)
+ self.assertEqual(inst.plural(1), 0)
+ self.assertEqual(inst.plural(2), 1)
+
+ # We load a second message file with different plural rules
+ inst3.plural = lambda n: n > 0
+ inst.add(inst3)
+ # It doesn't override the previously loaded rule
+ self.assertEqual(inst.plural(0), 0)
+ self.assertEqual(inst.plural(1), 0)
+ self.assertEqual(inst.plural(2), 1)
+
def test_dgettext(self):
t = self._makeOne()
self.assertEqual(t.dgettext('messages', 'foo'), 'Voh')
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_config/test_predicates.py b/pyramid/tests/test_predicates.py
index 9cd8f2734..8a002c24e 100644
--- a/pyramid/tests/test_config/test_predicates.py
+++ b/pyramid/tests/test_predicates.py
@@ -6,7 +6,7 @@ from pyramid.compat import text_
class TestXHRPredicate(unittest.TestCase):
def _makeOne(self, val):
- from pyramid.config.predicates import XHRPredicate
+ from pyramid.predicates import XHRPredicate
return XHRPredicate(val, None)
def test___call___true(self):
@@ -33,7 +33,7 @@ class TestXHRPredicate(unittest.TestCase):
class TestRequestMethodPredicate(unittest.TestCase):
def _makeOne(self, val):
- from pyramid.config.predicates import RequestMethodPredicate
+ from pyramid.predicates import RequestMethodPredicate
return RequestMethodPredicate(val, None)
def test_ctor_get_but_no_head(self):
@@ -71,7 +71,7 @@ class TestRequestMethodPredicate(unittest.TestCase):
class TestPathInfoPredicate(unittest.TestCase):
def _makeOne(self, val):
- from pyramid.config.predicates import PathInfoPredicate
+ from pyramid.predicates import PathInfoPredicate
return PathInfoPredicate(val, None)
def test_ctor_compilefail(self):
@@ -102,7 +102,7 @@ class TestPathInfoPredicate(unittest.TestCase):
class TestRequestParamPredicate(unittest.TestCase):
def _makeOne(self, val):
- from pyramid.config.predicates import RequestParamPredicate
+ from pyramid.predicates import RequestParamPredicate
return RequestParamPredicate(val, None)
def test___call___true_exists(self):
@@ -174,7 +174,7 @@ class TestRequestParamPredicate(unittest.TestCase):
class TestMatchParamPredicate(unittest.TestCase):
def _makeOne(self, val):
- from pyramid.config.predicates import MatchParamPredicate
+ from pyramid.predicates import MatchParamPredicate
return MatchParamPredicate(val, None)
def test___call___true_single(self):
@@ -216,7 +216,7 @@ class TestMatchParamPredicate(unittest.TestCase):
class TestCustomPredicate(unittest.TestCase):
def _makeOne(self, val):
- from pyramid.config.predicates import CustomPredicate
+ from pyramid.predicates import CustomPredicate
return CustomPredicate(val, None)
def test___call___true(self):
@@ -255,7 +255,7 @@ class TestCustomPredicate(unittest.TestCase):
class TestTraversePredicate(unittest.TestCase):
def _makeOne(self, val):
- from pyramid.config.predicates import TraversePredicate
+ from pyramid.predicates import TraversePredicate
return TraversePredicate(val, None)
def test___call__traverse_has_remainder_already(self):
@@ -297,7 +297,7 @@ class TestTraversePredicate(unittest.TestCase):
class Test_CheckCSRFTokenPredicate(unittest.TestCase):
def _makeOne(self, val, config):
- from pyramid.config.predicates import CheckCSRFTokenPredicate
+ from pyramid.predicates import CheckCSRFTokenPredicate
return CheckCSRFTokenPredicate(val, config)
def test_text(self):
@@ -340,7 +340,7 @@ class Test_CheckCSRFTokenPredicate(unittest.TestCase):
class TestHeaderPredicate(unittest.TestCase):
def _makeOne(self, val):
- from pyramid.config.predicates import HeaderPredicate
+ from pyramid.predicates import HeaderPredicate
return HeaderPredicate(val, None)
def test___call___true_exists(self):
@@ -404,7 +404,7 @@ class TestHeaderPredicate(unittest.TestCase):
class Test_PhysicalPathPredicate(unittest.TestCase):
def _makeOne(self, val, config):
- from pyramid.config.predicates import PhysicalPathPredicate
+ from pyramid.predicates import PhysicalPathPredicate
return PhysicalPathPredicate(val, config)
def test_text(self):
@@ -468,7 +468,7 @@ class Test_EffectivePrincipalsPredicate(unittest.TestCase):
testing.tearDown()
def _makeOne(self, val, config):
- from pyramid.config.predicates import EffectivePrincipalsPredicate
+ from pyramid.predicates import EffectivePrincipalsPredicate
return EffectivePrincipalsPredicate(val, config)
def test_text(self):
diff --git a/pyramid/tests/test_registry.py b/pyramid/tests/test_registry.py
index c9dff5b22..aa44b5408 100644
--- a/pyramid/tests/test_registry.py
+++ b/pyramid/tests/test_registry.py
@@ -5,8 +5,8 @@ class TestRegistry(unittest.TestCase):
from pyramid.registry import Registry
return Registry
- def _makeOne(self):
- return self._getTargetClass()()
+ def _makeOne(self, *args, **kw):
+ return self._getTargetClass()(*args, **kw)
def test___nonzero__(self):
registry = self._makeOne()
@@ -24,9 +24,13 @@ class TestRegistry(unittest.TestCase):
def test_package_name(self):
package_name = 'testing'
- registry = self._getTargetClass()(package_name)
+ registry = self._makeOne(package_name)
self.assertEqual(registry.package_name, package_name)
+ def test_default_package_name(self):
+ registry = self._makeOne()
+ self.assertEqual(registry.package_name, 'pyramid.tests')
+
def test_registerHandler_and_notify(self):
registry = self._makeOne()
self.assertEqual(registry.has_listeners, False)
@@ -57,6 +61,25 @@ class TestRegistry(unittest.TestCase):
registry.settings = 'foo'
self.assertEqual(registry._settings, 'foo')
+ def test_init_forwards_args(self):
+ from zope.interface import Interface
+ from zope.interface.registry import Components
+ dummy = object()
+ c = Components()
+ c.registerUtility(dummy, Interface)
+ registry = self._makeOne('foo', (c,))
+ self.assertEqual(registry.__name__, 'foo')
+ self.assertEqual(registry.getUtility(Interface), dummy)
+
+ def test_init_forwards_kw(self):
+ from zope.interface import Interface
+ from zope.interface.registry import Components
+ dummy = object()
+ c = Components()
+ c.registerUtility(dummy, Interface)
+ registry = self._makeOne(bases=(c,))
+ self.assertEqual(registry.getUtility(Interface), dummy)
+
class TestIntrospector(unittest.TestCase):
def _getTargetClass(slf):
from pyramid.registry import Introspector
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_router.py b/pyramid/tests/test_router.py
index 7aa42804c..a5da5c627 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -1271,6 +1271,19 @@ 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')
+
class DummyPredicate(object):
def __call__(self, info, request):
return True
diff --git a/pyramid/tests/test_scaffolds/fixture_scaffold/development.ini_tmpl b/pyramid/tests/test_scaffolds/fixture_scaffold/development.ini_tmpl
index 4d3a80286..01c504f99 100644
--- a/pyramid/tests/test_scaffolds/fixture_scaffold/development.ini_tmpl
+++ b/pyramid/tests/test_scaffolds/fixture_scaffold/development.ini_tmpl
@@ -11,8 +11,7 @@ pyramid.includes = pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
-port = 6543
+listen = *:6543
# Begin logging configuration
diff --git a/pyramid/tests/test_scaffolds/fixture_scaffold/production.ini_tmpl b/pyramid/tests/test_scaffolds/fixture_scaffold/production.ini_tmpl
index 931cfa510..becd3aa76 100644
--- a/pyramid/tests/test_scaffolds/fixture_scaffold/production.ini_tmpl
+++ b/pyramid/tests/test_scaffolds/fixture_scaffold/production.ini_tmpl
@@ -10,8 +10,7 @@ pyramid.default_locale_name = en
[server:main]
use = egg:pyramid#wsgiref
-host = 0.0.0.0
-port = 6543
+listen = *:6543
# Begin logging configuration
diff --git a/pyramid/tests/test_scaffolds/test_template.py b/pyramid/tests/test_scaffolds/test_template.py
index 2e961c516..98f2daf73 100644
--- a/pyramid/tests/test_scaffolds/test_template.py
+++ b/pyramid/tests/test_scaffolds/test_template.py
@@ -143,13 +143,13 @@ class DummyCopydir(object):
self.vars = vars
self.kw = kw
-class DummyOptions(object):
+class DummyArgs(object):
simulate = False
overwrite = False
interactive = False
class DummyCommand(object):
- options = DummyOptions()
+ args = DummyArgs()
verbosity = 1
diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py
index f3aa20e7c..2d2b0549f 100644
--- a/pyramid/tests/test_scripts/dummy.py
+++ b/pyramid/tests/test_scripts/dummy.py
@@ -81,27 +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):
- self.result = result
-
- 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):
- self.parser = DummyConfigParser(self.items)
- return self.parser
-
class DummyCloser(object):
def __call__(self):
self.called = True
@@ -160,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_pcreate.py b/pyramid/tests/test_scripts/test_pcreate.py
index b7013bc73..0286614ce 100644
--- a/pyramid/tests/test_scripts/test_pcreate.py
+++ b/pyramid/tests/test_scripts/test_pcreate.py
@@ -26,7 +26,7 @@ class TestPCreateCommand(unittest.TestCase):
result = cmd.run()
self.assertEqual(result, 0)
out = self.out_.getvalue()
- self.assertTrue(out.startswith('Available scaffolds'))
+ self.assertTrue(out.count('Available scaffolds'))
def test_run_show_scaffolds_none_exist(self):
cmd = self._makeOne('-l')
@@ -34,7 +34,7 @@ class TestPCreateCommand(unittest.TestCase):
result = cmd.run()
self.assertEqual(result, 0)
out = self.out_.getvalue()
- self.assertTrue(out.startswith('No scaffolds available'))
+ self.assertTrue(out.count('No scaffolds available'))
def test_run_no_scaffold_no_args(self):
cmd = self._makeOne(quiet=True)
@@ -46,7 +46,7 @@ class TestPCreateCommand(unittest.TestCase):
result = cmd.run()
self.assertEqual(result, 2)
out = self.out_.getvalue()
- self.assertTrue(out.startswith(
+ self.assertTrue(out.count(
'You must provide at least one scaffold name'))
def test_no_project_name(self):
@@ -54,14 +54,14 @@ class TestPCreateCommand(unittest.TestCase):
result = cmd.run()
self.assertEqual(result, 2)
out = self.out_.getvalue()
- self.assertTrue(out.startswith('You must provide a project name'))
+ self.assertTrue(out.count('You must provide a project name'))
def test_unknown_scaffold_name(self):
cmd = self._makeOne('-s', 'dummyXX', 'distro')
result = cmd.run()
self.assertEqual(result, 2)
out = self.out_.getvalue()
- self.assertTrue(out.startswith('Unavailable scaffolds'))
+ self.assertTrue(out.count('Unavailable scaffolds'))
def test_known_scaffold_single_rendered(self):
import os
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 aeaa57060..fab5e163e 100644
--- a/pyramid/tests/test_scripts/test_proutes.py
+++ b/pyramid/tests/test_scripts/test_proutes.py
@@ -19,8 +19,9 @@ class TestPRoutesCommand(unittest.TestCase):
def _makeOne(self):
cmd = self._getTargetClass()([])
- cmd.bootstrap = (dummy.DummyBootstrap(),)
- cmd.args = ('/foo/bar/myapp.ini#myapp',)
+ cmd.bootstrap = dummy.DummyBootstrap()
+ cmd.get_config_loader = dummy.DummyLoader()
+ cmd.args.config_uri = '/foo/bar/myapp.ini#myapp'
return cmd
@@ -37,13 +38,15 @@ class TestPRoutesCommand(unittest.TestCase):
def test_good_args(self):
cmd = self._getTargetClass()([])
- cmd.bootstrap = (dummy.DummyBootstrap(),)
- cmd.args = ('/foo/bar/myapp.ini#myapp', 'a=1')
+ 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()
@@ -51,8 +54,10 @@ class TestPRoutesCommand(unittest.TestCase):
def test_bad_args(self):
cmd = self._getTargetClass()([])
- cmd.bootstrap = (dummy.DummyBootstrap(),)
- cmd.args = ('/foo/bar/myapp.ini#myapp', 'a')
+ 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')
mapper = dummy.DummyMapper(route)
cmd._get_mapper = lambda *arg: mapper
@@ -84,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
@@ -101,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)
@@ -122,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)
@@ -148,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)
@@ -188,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)
@@ -216,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)
@@ -250,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)
@@ -286,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)
@@ -325,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)
@@ -352,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)
@@ -380,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)
@@ -408,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)
@@ -434,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)
@@ -459,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)
@@ -489,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)
@@ -519,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)
@@ -549,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)
@@ -586,11 +591,11 @@ class TestPRoutesCommand(unittest.TestCase):
)
command = self._makeOne()
- command.options.glob = '*foo*'
+ command.args.glob = '*foo*'
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)
@@ -618,11 +623,11 @@ class TestPRoutesCommand(unittest.TestCase):
)
command = self._makeOne()
- command.options.glob = '*foo*'
- command.options.format = 'method,name'
+ command.args.glob = '*foo*'
+ 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)
@@ -648,11 +653,11 @@ class TestPRoutesCommand(unittest.TestCase):
)
command = self._makeOne()
- command.options.glob = '*foo*'
- command.options.format = 'predicates,name,pattern'
+ command.args.glob = '*foo*'
+ 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']"
@@ -680,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)
@@ -713,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)
@@ -746,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)
@@ -769,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 bf4763602..485cf38cb 100644
--- a/pyramid/tests/test_scripts/test_pserve.py
+++ b/pyramid/tests/test_scripts/test_pserve.py
@@ -1,6 +1,10 @@
import os
-import tempfile
import unittest
+from pyramid.tests.test_scripts import dummy
+
+
+here = os.path.abspath(os.path.dirname(__file__))
+
class TestPServeCommand(unittest.TestCase):
def setUp(self):
@@ -10,12 +14,6 @@ class TestPServeCommand(unittest.TestCase):
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
@@ -25,6 +23,8 @@ class TestPServeCommand(unittest.TestCase):
effargs.extend(args)
cmd = self._getTargetClass()(effargs)
cmd.out = self.out
+ self.loader = dummy.DummyLoader()
+ cmd._get_config_loader = self.loader
return cmd
def test_run_no_args(self):
@@ -33,34 +33,94 @@ class TestPServeCommand(unittest.TestCase):
self.assertEqual(result, 2)
self.assertEqual(self.out_.getvalue(), 'You must give a config file')
- def test_get_options_no_command(self):
- inst = self._makeOne()
- inst.args = ['foo', 'a=1', 'b=2']
- result = inst.get_options()
- self.assertEqual(result, {'a': '1', 'b': '2'})
-
def test_parse_vars_good(self):
- from pyramid.tests.test_scripts.dummy import DummyApp
-
inst = self._makeOne('development.ini', 'a=1', 'b=2')
- inst.loadserver = self._get_server
-
+ app = dummy.DummyApp()
- app = DummyApp()
+ 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
- def get_app(*args, **kwargs):
- app.global_conf = kwargs.get('global_conf', None)
-
- inst.loadapp = get_app
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')
+ 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',
+ })
+ 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):
from pyramid.scripts.pserve import main
@@ -69,71 +129,3 @@ class Test_main(unittest.TestCase):
def test_it(self):
result = self._callFUT(['pserve'])
self.assertEqual(result, 2)
-
-class TestLazyWriter(unittest.TestCase):
- def _makeOne(self, filename, mode='w'):
- from pyramid.scripts.pserve import LazyWriter
- return LazyWriter(filename, mode)
-
- def test_open(self):
- filename = tempfile.mktemp()
- try:
- inst = self._makeOne(filename)
- fp = inst.open()
- self.assertEqual(fp.name, filename)
- finally:
- fp.close()
- os.remove(filename)
-
- def test_write(self):
- filename = tempfile.mktemp()
- try:
- inst = self._makeOne(filename)
- inst.write('hello')
- finally:
- with open(filename) as f:
- data = f.read()
- self.assertEqual(data, 'hello')
- inst.close()
- os.remove(filename)
-
- def test_writeline(self):
- filename = tempfile.mktemp()
- try:
- inst = self._makeOne(filename)
- inst.writelines('hello')
- finally:
- with open(filename) as f:
- data = f.read()
- self.assertEqual(data, 'hello')
- inst.close()
- os.remove(filename)
-
- def test_flush(self):
- filename = tempfile.mktemp()
- try:
- inst = self._makeOne(filename)
- inst.flush()
- fp = inst.fileobj
- self.assertEqual(fp.name, filename)
- finally:
- fp.close()
- os.remove(filename)
-
-class Test__methodwrapper(unittest.TestCase):
- def _makeOne(self, func, obj, type):
- from pyramid.scripts.pserve import _methodwrapper
- return _methodwrapper(func, obj, type)
-
- def test___call__succeed(self):
- def foo(self, cls, a=1): return 1
- class Bar(object): pass
- wrapper = self._makeOne(foo, Bar, None)
- result = wrapper(a=1)
- self.assertEqual(result, 1)
-
- def test___call__fail(self):
- def foo(self, cls, a=1): return 1
- class Bar(object): pass
- wrapper = self._makeOne(foo, Bar, None)
- self.assertRaises(AssertionError, wrapper, cls=1)
diff --git a/pyramid/tests/test_scripts/test_pshell.py b/pyramid/tests/test_scripts/test_pshell.py
index f98ded6d3..ca9eb7af2 100644
--- a/pyramid/tests/test_scripts/test_pshell.py
+++ b/pyramid/tests/test_scripts/test_pshell.py
@@ -8,19 +8,21 @@ 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:
- self.args = ('/foo/bar/myapp.ini#myapp',)
- cmd.args = self.args
+ class Args(object): pass
+ self.args = Args()
+ self.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ cmd.args.config_uri = self.args.config_uri
if patch_options:
class Options(object): pass
self.options = Options()
@@ -44,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,
@@ -71,15 +70,12 @@ class TestPShellCommand(unittest.TestCase):
self._makeEntryPoints(command, {})
command.default_runner = shell
- command.options.python_shell = 'unknown_python_shell'
+ command.args.python_shell = 'unknown_python_shell'
result = command.run()
self.assertEqual(result, 1)
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)
@@ -95,12 +91,9 @@ class TestPShellCommand(unittest.TestCase):
}
)
- command.options.python_shell = 'ipython'
+ 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,
@@ -140,7 +133,7 @@ class TestPShellCommand(unittest.TestCase):
shell = command.make_shell()
self.assertEqual(shell, dshell)
- command.options.python_shell = 'ipython'
+ command.args.python_shell = 'ipython'
self.assertRaises(ValueError, command.make_shell)
self._makeEntryPoints(
@@ -152,15 +145,15 @@ class TestPShellCommand(unittest.TestCase):
}
)
- command.options.python_shell = 'ipython'
+ command.args.python_shell = 'ipython'
shell = command.make_shell()
self.assertEqual(shell, ipshell)
- command.options.python_shell = 'bpython'
+ command.args.python_shell = 'bpython'
shell = command.make_shell()
self.assertEqual(shell, bpshell)
- command.options.python_shell = 'python'
+ command.args.python_shell = 'python'
shell = command.make_shell()
self.assertEqual(shell, dshell)
@@ -197,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,
@@ -221,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',
@@ -250,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)
@@ -266,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',
@@ -289,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)]
- command.options.setup = setup
+ 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',
@@ -311,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_,
@@ -365,7 +340,7 @@ class TestPShellCommand(unittest.TestCase):
}
)
- command.options.list = True
+ command.args.list = True
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(out_calls, [
diff --git a/pyramid/tests/test_scripts/test_ptweens.py b/pyramid/tests/test_scripts/test_ptweens.py
index f39f84b68..6907b858d 100644
--- a/pyramid/tests/test_scripts/test_ptweens.py
+++ b/pyramid/tests/test_scripts/test_ptweens.py
@@ -8,8 +8,9 @@ class TestPTweensCommand(unittest.TestCase):
def _makeOne(self):
cmd = self._getTargetClass()([])
- cmd.bootstrap = (dummy.DummyBootstrap(),)
- cmd.args = ('/foo/bar/myapp.ini#myapp',)
+ cmd.bootstrap = dummy.DummyBootstrap()
+ cmd.setup_logging = dummy.dummy_setup_logging()
+ cmd.args.config_uri = '/foo/bar/myapp.ini#myapp'
return cmd
def test_command_no_tweens(self):
diff --git a/pyramid/tests/test_scripts/test_pviews.py b/pyramid/tests/test_scripts/test_pviews.py
index b162144a7..6ec9defbd 100644
--- a/pyramid/tests/test_scripts/test_pviews.py
+++ b/pyramid/tests/test_scripts/test_pviews.py
@@ -8,8 +8,9 @@ class TestPViewsCommand(unittest.TestCase):
def _makeOne(self, registry=None):
cmd = self._getTargetClass()([])
- cmd.bootstrap = (dummy.DummyBootstrap(registry=registry),)
- cmd.args = ('/foo/bar/myapp.ini#myapp',)
+ cmd.bootstrap = dummy.DummyBootstrap(registry=registry)
+ cmd.setup_logging = dummy.dummy_setup_logging()
+ cmd.args.config_uri = '/foo/bar/myapp.ini#myapp'
return cmd
def _makeRequest(self, url, registry):
@@ -242,7 +243,8 @@ class TestPViewsCommand(unittest.TestCase):
L = []
command.out = L.append
command._find_view = lambda arg1: None
- command.args = ('/foo/bar/myapp.ini#myapp', '/a')
+ command.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ command.args.url = '/a'
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(L[1], 'URL = /a')
@@ -255,7 +257,8 @@ class TestPViewsCommand(unittest.TestCase):
L = []
command.out = L.append
command._find_view = lambda arg1: None
- command.args = ('/foo/bar/myapp.ini#myapp', 'a')
+ command.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ command.args.url = 'a'
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(L[1], 'URL = /a')
@@ -269,7 +272,8 @@ class TestPViewsCommand(unittest.TestCase):
command.out = L.append
view = dummy.DummyView(context='context', view_name='a')
command._find_view = lambda arg1: view
- command.args = ('/foo/bar/myapp.ini#myapp', '/a')
+ command.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ command.args.url = '/a'
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(L[1], 'URL = /a')
@@ -287,7 +291,8 @@ class TestPViewsCommand(unittest.TestCase):
def view(): pass
view.__request_attrs__ = {'context': 'context', 'view_name': 'a'}
command._find_view = lambda arg1: view
- command.args = ('/foo/bar/myapp.ini#myapp', '/a')
+ command.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ command.args.url = '/a'
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(L[1], 'URL = /a')
@@ -305,7 +310,8 @@ class TestPViewsCommand(unittest.TestCase):
view = dummy.DummyView(context='context', view_name='a')
view.__permission__ = 'test'
command._find_view = lambda arg1: view
- command.args = ('/foo/bar/myapp.ini#myapp', '/a')
+ command.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ command.args.url = '/a'
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(L[1], 'URL = /a')
@@ -326,7 +332,8 @@ class TestPViewsCommand(unittest.TestCase):
view = dummy.DummyView(context='context', view_name='a')
view.__predicates__ = [predicate]
command._find_view = lambda arg1: view
- command.args = ('/foo/bar/myapp.ini#myapp', '/a')
+ command.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ command.args.url = '/a'
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(L[1], 'URL = /a')
@@ -346,7 +353,8 @@ class TestPViewsCommand(unittest.TestCase):
view = dummy.DummyView(context='context', view_name='a',
matched_route=route, subpath='')
command._find_view = lambda arg1: view
- command.args = ('/foo/bar/myapp.ini#myapp', '/a')
+ command.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ command.args.url = '/a'
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(L[1], 'URL = /a')
@@ -374,7 +382,8 @@ class TestPViewsCommand(unittest.TestCase):
multiview2 = dummy.DummyMultiView(multiview1, context='context',
view_name='a')
command._find_view = lambda arg1: multiview2
- command.args = ('/foo/bar/myapp.ini#myapp', '/a')
+ command.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ command.args.url = '/a'
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(L[1], 'URL = /a')
@@ -397,7 +406,8 @@ class TestPViewsCommand(unittest.TestCase):
view = dummy.DummyView(context='context', view_name='a',
matched_route=route, subpath='')
command._find_view = lambda arg1: view
- command.args = ('/foo/bar/myapp.ini#myapp', '/a')
+ command.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ command.args.url = '/a'
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(L[1], 'URL = /a')
@@ -423,7 +433,8 @@ class TestPViewsCommand(unittest.TestCase):
view.__view_attr__ = 'call'
multiview = dummy.DummyMultiView(view, context='context', view_name='a')
command._find_view = lambda arg1: multiview
- command.args = ('/foo/bar/myapp.ini#myapp', '/a')
+ command.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ command.args.url = '/a'
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(L[1], 'URL = /a')
@@ -444,7 +455,8 @@ class TestPViewsCommand(unittest.TestCase):
view.__permission__ = 'test'
multiview = dummy.DummyMultiView(view, context='context', view_name='a')
command._find_view = lambda arg1: multiview
- command.args = ('/foo/bar/myapp.ini#myapp', '/a')
+ command.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ command.args.url = '/a'
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(L[1], 'URL = /a')
@@ -468,7 +480,8 @@ class TestPViewsCommand(unittest.TestCase):
view.__predicates__ = [predicate]
multiview = dummy.DummyMultiView(view, context='context', view_name='a')
command._find_view = lambda arg1: multiview
- command.args = ('/foo/bar/myapp.ini#myapp', '/a')
+ command.args.config_uri = '/foo/bar/myapp.ini#myapp'
+ command.args.url = '/a'
result = command.run()
self.assertEqual(result, 0)
self.assertEqual(L[1], 'URL = /a')
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_static.py b/pyramid/tests/test_static.py
index 2ca86bc44..f76cc5067 100644
--- a/pyramid/tests/test_static.py
+++ b/pyramid/tests/test_static.py
@@ -113,14 +113,6 @@ class Test_static_view_use_subpath_False(unittest.TestCase):
response = inst(context, request)
self.assertTrue(b'<html>static</html>' in response.body)
- def test_cachebust_match(self):
- inst = self._makeOne('pyramid.tests:fixtures/static')
- inst.cachebust_match = lambda subpath: subpath[1:]
- request = self._makeRequest({'PATH_INFO':'/foo/index.html'})
- context = DummyContext()
- response = inst(context, request)
- self.assertTrue(b'<html>static</html>' in response.body)
-
def test_resource_is_file_with_wsgi_file_wrapper(self):
from pyramid.response import _BLOCK_SIZE
inst = self._makeOne('pyramid.tests:fixtures/static')
@@ -186,14 +178,14 @@ class Test_static_view_use_subpath_False(unittest.TestCase):
from pyramid.httpexceptions import HTTPNotFound
self.assertRaises(HTTPNotFound, inst, context, request)
- def test_resource_with_content_encoding(self):
+ def test_gz_resource_no_content_encoding(self):
inst = self._makeOne('pyramid.tests:fixtures/static')
request = self._makeRequest({'PATH_INFO':'/arcs.svg.tgz'})
context = DummyContext()
response = inst(context, request)
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/x-tar')
- self.assertEqual(response.content_encoding, 'gzip')
+ self.assertEqual(response.content_encoding, None)
response.app_iter.close()
def test_resource_no_content_encoding(self):
diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py
index 113f7e5f4..0b4619de4 100644
--- a/pyramid/tests/test_testing.py
+++ b/pyramid/tests/test_testing.py
@@ -1,4 +1,5 @@
import unittest
+from zope.component import getSiteManager
class TestDummyRootFactory(unittest.TestCase):
def _makeOne(self, environ):
@@ -320,22 +321,11 @@ class Test_setUp(unittest.TestCase):
def tearDown(self):
from pyramid.threadlocal import manager
manager.clear()
- getSiteManager = self._getSM()
- if getSiteManager is not None:
- getSiteManager.reset()
-
- def _getSM(self):
- try:
- from zope.component import getSiteManager
- except ImportError: # pragma: no cover
- getSiteManager = None
- return getSiteManager
+ getSiteManager.reset()
def _assertSMHook(self, hook):
- getSiteManager = self._getSM()
- if getSiteManager is not None:
- result = getSiteManager.sethook(None)
- self.assertEqual(result, hook)
+ result = getSiteManager.sethook(None)
+ self.assertEqual(result, hook)
def test_it_defaults(self):
from pyramid.threadlocal import manager
@@ -375,10 +365,8 @@ class Test_setUp(unittest.TestCase):
from pyramid.registry import Registry
registry = Registry()
self._callFUT(registry=registry, hook_zca=False)
- getSiteManager = self._getSM()
- if getSiteManager is not None:
- sm = getSiteManager()
- self.assertFalse(sm is registry)
+ sm = getSiteManager()
+ self.assertFalse(sm is registry)
def test_it_with_settings_passed_explicit_registry(self):
from pyramid.registry import Registry
@@ -403,27 +391,14 @@ class Test_tearDown(unittest.TestCase):
def tearDown(self):
from pyramid.threadlocal import manager
manager.clear()
- getSiteManager = self._getSM()
- if getSiteManager is not None:
- getSiteManager.reset()
-
- def _getSM(self):
- try:
- from zope.component import getSiteManager
- except ImportError: # pragma: no cover
- getSiteManager = None
- return getSiteManager
+ getSiteManager.reset()
def _assertSMHook(self, hook):
- getSiteManager = self._getSM()
- if getSiteManager is not None:
- result = getSiteManager.sethook(None)
- self.assertEqual(result, hook)
+ result = getSiteManager.sethook(None)
+ self.assertEqual(result, hook)
def _setSMHook(self, hook):
- getSiteManager = self._getSM()
- if getSiteManager is not None:
- getSiteManager.sethook(hook)
+ getSiteManager.sethook(hook)
def test_defaults(self):
from pyramid.threadlocal import manager
@@ -438,10 +413,8 @@ class Test_tearDown(unittest.TestCase):
self.assertNotEqual(current, old)
self.assertEqual(registry.inited, 2)
finally:
- getSiteManager = self._getSM()
- if getSiteManager is not None:
- result = getSiteManager.sethook(None)
- self.assertNotEqual(result, hook)
+ result = getSiteManager.sethook(None)
+ self.assertNotEqual(result, hook)
def test_registry_cannot_be_inited(self):
from pyramid.threadlocal import manager
diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py
index 0decd04d6..437fe46df 100644
--- a/pyramid/tests/test_traversal.py
+++ b/pyramid/tests/test_traversal.py
@@ -1,5 +1,5 @@
+# -*- coding: utf-8 -*-
import unittest
-import warnings
from pyramid.testing import cleanUp
@@ -11,11 +11,6 @@ from pyramid.compat import (
PY2,
)
-with warnings.catch_warnings(record=True) as w:
- warnings.filterwarnings('always')
- from pyramid.interfaces import IContextURL
- assert(len(w) == 1)
-
class TraversalPathTests(unittest.TestCase):
def _callFUT(self, path):
from pyramid.traversal import traversal_path
@@ -839,7 +834,7 @@ class QuotePathSegmentTests(unittest.TestCase):
def test_string(self):
s = '/ hello!'
result = self._callFUT(s)
- self.assertEqual(result, '%2F%20hello%21')
+ self.assertEqual(result, '%2F%20hello!')
def test_int(self):
s = 12345
@@ -870,182 +865,12 @@ class ResourceURLTests(unittest.TestCase):
from pyramid.traversal import ResourceURL
return ResourceURL
- def _registerTraverser(self, traverser):
- from pyramid.threadlocal import get_current_registry
- reg = get_current_registry()
- from pyramid.interfaces import ITraverser
- from zope.interface import Interface
- reg.registerAdapter(traverser, (Interface,), ITraverser)
-
- def test_class_conforms_to_IContextURL(self):
- # bw compat
- from zope.interface.verify import verifyClass
- verifyClass(IContextURL, self._getTargetClass())
-
- def test_instance_conforms_to_IContextURL(self):
- from zope.interface.verify import verifyObject
- context = DummyContext()
- request = DummyRequest()
- verifyObject(IContextURL, self._makeOne(context, request))
-
def test_instance_conforms_to_IResourceURL(self):
from pyramid.interfaces import IResourceURL
from zope.interface.verify import verifyObject
context = DummyContext()
request = DummyRequest()
verifyObject(IResourceURL, self._makeOne(context, request))
-
- def test_call_withlineage(self):
- baz = DummyContext()
- bar = DummyContext(baz)
- foo = DummyContext(bar)
- root = DummyContext(foo)
- root.__parent__ = None
- root.__name__ = None
- foo.__parent__ = root
- foo.__name__ = 'foo '
- bar.__parent__ = foo
- bar.__name__ = 'bar'
- baz.__parent__ = bar
- baz.__name__ = 'baz'
- request = DummyRequest()
- context_url = self._makeOne(baz, request)
- result = context_url()
- self.assertEqual(result, 'http://example.com:5432/foo%20/bar/baz/')
-
- def test_call_nolineage(self):
- context = DummyContext()
- context.__name__ = ''
- context.__parent__ = None
- request = DummyRequest()
- context_url = self._makeOne(context, request)
- result = context_url()
- self.assertEqual(result, 'http://example.com:5432/')
-
- def test_call_unicode_mixed_with_bytes_in_resource_names(self):
- root = DummyContext()
- root.__parent__ = None
- root.__name__ = None
- one = DummyContext()
- one.__parent__ = root
- one.__name__ = text_(b'La Pe\xc3\xb1a', 'utf-8')
- two = DummyContext()
- two.__parent__ = one
- two.__name__ = b'La Pe\xc3\xb1a'
- request = DummyRequest()
- context_url = self._makeOne(two, request)
- result = context_url()
- self.assertEqual(
- result,
- 'http://example.com:5432/La%20Pe%C3%B1a/La%20Pe%C3%B1a/')
-
- def test_call_with_virtual_root_path(self):
- from pyramid.interfaces import VH_ROOT_KEY
- root = DummyContext()
- root.__parent__ = None
- root.__name__ = None
- one = DummyContext()
- one.__parent__ = root
- one.__name__ = 'one'
- two = DummyContext()
- two.__parent__ = one
- two.__name__ = 'two'
- request = DummyRequest({VH_ROOT_KEY:'/one'})
- context_url = self._makeOne(two, request)
- result = context_url()
- self.assertEqual(result, 'http://example.com:5432/two/')
-
- request = DummyRequest({VH_ROOT_KEY:'/one/two'})
- context_url = self._makeOne(two, request)
- result = context_url()
- self.assertEqual(result, 'http://example.com:5432/')
-
- def test_call_with_virtual_root_path_physical_not_startwith_vroot(self):
- from pyramid.interfaces import VH_ROOT_KEY
- root = DummyContext()
- root.__parent__ = None
- root.__name__ = None
- one = DummyContext()
- one.__parent__ = root
- one.__name__ = 'one'
- two = DummyContext()
- two.__parent__ = one
- two.__name__ = 'two'
- request = DummyRequest({VH_ROOT_KEY:'/wrong'})
- context_url = self._makeOne(two, request)
- result = context_url()
- self.assertEqual(result, 'http://example.com:5432/one/two/')
-
- def test_call_empty_names_not_ignored(self):
- bar = DummyContext()
- empty = DummyContext(bar)
- root = DummyContext(empty)
- root.__parent__ = None
- root.__name__ = None
- empty.__parent__ = root
- empty.__name__ = ''
- bar.__parent__ = empty
- bar.__name__ = 'bar'
- request = DummyRequest()
- context_url = self._makeOne(bar, request)
- result = context_url()
- self.assertEqual(result, 'http://example.com:5432//bar/')
-
- def test_call_local_url_returns_None(self):
- resource = DummyContext()
- def resource_url(request, info):
- self.assertEqual(info['virtual_path'], '/')
- self.assertEqual(info['physical_path'], '/')
- return None
- resource.__resource_url__ = resource_url
- request = DummyRequest()
- context_url = self._makeOne(resource, request)
- result = context_url()
- self.assertEqual(result, 'http://example.com:5432/')
-
- def test_call_local_url_returns_url(self):
- resource = DummyContext()
- def resource_url(request, info):
- self.assertEqual(info['virtual_path'], '/')
- self.assertEqual(info['physical_path'], '/')
- return 'abc'
- resource.__resource_url__ = resource_url
- request = DummyRequest()
- context_url = self._makeOne(resource, request)
- result = context_url()
- self.assertEqual(result, 'abc')
-
- def test_virtual_root_no_virtual_root_path(self):
- root = DummyContext()
- root.__name__ = None
- root.__parent__ = None
- one = DummyContext()
- one.__name__ = 'one'
- one.__parent__ = root
- request = DummyRequest()
- context_url = self._makeOne(one, request)
- self.assertEqual(context_url.virtual_root(), root)
-
- def test_virtual_root_no_virtual_root_path_with_root_on_request(self):
- context = DummyContext()
- context.__parent__ = None
- request = DummyRequest()
- request.root = DummyContext()
- context_url = self._makeOne(context, request)
- self.assertEqual(context_url.virtual_root(), request.root)
-
- def test_virtual_root_with_virtual_root_path(self):
- from pyramid.interfaces import VH_ROOT_KEY
- context = DummyContext()
- context.__parent__ = None
- traversed_to = DummyContext()
- environ = {VH_ROOT_KEY:'/one'}
- request = DummyRequest(environ)
- traverser = make_traverser({'context':traversed_to, 'view_name':''})
- self._registerTraverser(traverser)
- context_url = self._makeOne(context, request)
- self.assertEqual(context_url.virtual_root(), traversed_to)
- self.assertEqual(context.request.environ['PATH_INFO'], '/one')
def test_IResourceURL_attributes_with_vroot(self):
from pyramid.interfaces import VH_ROOT_KEY
@@ -1114,14 +939,47 @@ class TestVirtualRoot(unittest.TestCase):
from pyramid.traversal import virtual_root
return virtual_root(resource, request)
- def test_registered(self):
+ def _registerTraverser(self, traverser):
+ from pyramid.threadlocal import get_current_registry
+ reg = get_current_registry()
+ from pyramid.interfaces import ITraverser
from zope.interface import Interface
- request = _makeRequest()
- request.registry.registerAdapter(DummyContextURL, (Interface,Interface),
- IContextURL)
+ reg.registerAdapter(traverser, (Interface,), ITraverser)
+
+ def test_virtual_root_no_virtual_root_path(self):
+ root = DummyContext()
+ root.__name__ = None
+ root.__parent__ = None
+ one = DummyContext()
+ one.__name__ = 'one'
+ one.__parent__ = root
+ request = DummyRequest()
+ result = self._callFUT(one, request)
+ self.assertEqual(result, root)
+
+ def test_virtual_root_no_virtual_root_path_with_root_on_request(self):
context = DummyContext()
+ context.__parent__ = None
+ request = DummyRequest()
+ request.root = DummyContext()
result = self._callFUT(context, request)
- self.assertEqual(result, '123')
+ self.assertEqual(result, request.root)
+
+ def test_virtual_root_with_virtual_root_path(self):
+ from pyramid.interfaces import VH_ROOT_KEY
+ root = DummyContext()
+ root.__parent__ = None
+ context = DummyContext()
+ context.__name__ = 'one'
+ context.__parent__ = root
+ traversed_to = DummyContext()
+ environ = {VH_ROOT_KEY:'/one'}
+ request = DummyRequest(environ)
+ traverser = make_traverser({'context':traversed_to, 'view_name':''})
+ self._registerTraverser(traverser)
+ result = self._callFUT(context, request)
+ self.assertEqual(result, traversed_to)
+ self.assertEqual(root.request.environ['PATH_INFO'], '/one')
def test_default(self):
context = DummyContext()
@@ -1299,6 +1157,15 @@ class Test__join_path_tuple(unittest.TestCase):
result = self._callFUT(('x',))
self.assertEqual(result, 'x')
+ def test_segments_with_unsafes(self):
+ safe_segments = tuple(u"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~!$&'()*+,;=:@")
+ result = self._callFUT(safe_segments)
+ self.assertEqual(result, u'/'.join(safe_segments))
+ unsafe_segments = tuple(chr(i) for i in range(0x20, 0x80) if not chr(i) in safe_segments) + (u'あ',)
+ result = self._callFUT(unsafe_segments)
+ self.assertEqual(result, u'/'.join(''.join('%%%02X' % (ord(c) if isinstance(c, str) else c) for c in unsafe_segment.encode('utf-8')) for unsafe_segment in unsafe_segments))
+
+
def make_traverser(result):
class DummyTraverser(object):
def __init__(self, context):
@@ -1347,13 +1214,6 @@ class DummyRequest:
path_info = property(_get_path_info, _set_path_info)
-class DummyContextURL:
- def __init__(self, context, request):
- pass
-
- def virtual_root(self):
- return '123'
-
def _makeRequest(environ=None):
from pyramid.registry import Registry
request = DummyRequest()
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 0a788ba97..ddf28e0b0 100644
--- a/pyramid/tests/test_url.py
+++ b/pyramid/tests/test_url.py
@@ -29,18 +29,6 @@ class TestURLMethodsMixin(unittest.TestCase):
request.registry = self.config.registry
return request
- def _registerContextURL(self, reg):
- with warnings.catch_warnings(record=True):
- from pyramid.interfaces import IContextURL
- from zope.interface import Interface
- class DummyContextURL(object):
- def __init__(self, context, request):
- pass
- def __call__(self):
- return 'http://example.com/context/'
- reg.registerAdapter(DummyContextURL, (Interface, Interface),
- IContextURL)
-
def _registerResourceURL(self, reg):
from pyramid.interfaces import IResourceURL
from zope.interface import Interface
@@ -186,16 +174,6 @@ class TestURLMethodsMixin(unittest.TestCase):
root = DummyContext()
result = request.resource_url(root)
self.assertEqual(result, 'http://example.com:5432/context/')
-
- def test_resource_url_finds_IContextURL(self):
- request = self._makeOne()
- self._registerContextURL(request.registry)
- root = DummyContext()
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter('always')
- result = request.resource_url(root)
- self.assertEqual(len(w), 1)
- self.assertEqual(result, 'http://example.com/context/')
def test_resource_url_with_app_url(self):
request = self._makeOne()
diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py
index 2d20b24c3..06f4ad793 100644
--- a/pyramid/tests/test_urldispatch.py
+++ b/pyramid/tests/test_urldispatch.py
@@ -485,11 +485,15 @@ class TestCompileRouteFunctional(unittest.TestCase):
def test_generator_functional_newstyle(self):
self.generates('/{x}', {'x':''}, '/')
self.generates('/{x}', {'x':'a'}, '/a')
+ self.generates('/{x}', {'x':'a/b/c'}, '/a/b/c')
+ self.generates('/{x}', {'x':':@&+$,'}, '/:@&+$,')
self.generates('zzz/{x}', {'x':'abc'}, '/zzz/abc')
self.generates('zzz/{x}*traverse', {'x':'abc', 'traverse':''},
'/zzz/abc')
self.generates('zzz/{x}*traverse', {'x':'abc', 'traverse':'/def/g'},
'/zzz/abc/def/g')
+ self.generates('zzz/{x}*traverse', {'x':':@&+$,', 'traverse':'/:@&+$,'},
+ '/zzz/:@&+$,/:@&+$,')
self.generates('/{x}', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8')},
'//La%20Pe%C3%B1a')
self.generates('/{x}*y', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8'),
diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py
index bbf6103f4..d64f0a73f 100644
--- a/pyramid/tests/test_util.py
+++ b/pyramid/tests/test_util.py
@@ -369,12 +369,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..2061515b3 100644
--- a/pyramid/tests/test_view.py
+++ b/pyramid/tests/test_view.py
@@ -778,11 +778,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()
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/traversal.py b/pyramid/traversal.py
index 963a76bb5..641445067 100644
--- a/pyramid/traversal.py
+++ b/pyramid/traversal.py
@@ -1,7 +1,3 @@
-import warnings
-
-from zope.deprecation import deprecated
-
from zope.interface import implementer
from zope.interface.interfaces import IInterface
@@ -31,9 +27,8 @@ from pyramid.exceptions import URLDecodeError
from pyramid.location import lineage
from pyramid.threadlocal import get_current_registry
-with warnings.catch_warnings():
- warnings.filterwarnings('ignore')
- from pyramid.interfaces import IContextURL
+PATH_SEGMENT_SAFE = "~!$&'()*+,;=:@" # from webob
+PATH_SAFE = PATH_SEGMENT_SAFE + "/"
empty = text_('')
@@ -97,10 +92,6 @@ def find_resource(resource, path):
object representing a resource name). Resource path tuples generated by
:func:`pyramid.traversal.resource_path_tuple` can always be
resolved by ``find_resource``.
-
- .. note:: For backwards compatibility purposes, this function can also
- be imported as :func:`pyramid.traversal.find_model`, although doing so
- will emit a deprecation warning.
"""
if isinstance(path, text_type):
path = ascii_native_(path)
@@ -171,12 +162,6 @@ def resource_path(resource, *elements):
properly. If the root resource has a non-null ``__name__`` attribute,
its name will be prepended to the generated path rather than a single
leading '/' character.
-
- .. note::
-
- For backwards compatibility purposes, this function can also
- be imported as ``model_path``, although doing so will cause
- a deprecation warning to be emitted.
"""
# joining strings is a bit expensive so we delegate to a function
# which caches the joined result for us
@@ -378,12 +363,6 @@ def resource_path_tuple(resource, *elements):
generated properly. If the root resource has a non-null ``__name__``
attribute, its name will be the first element in the generated path tuple
rather than the empty string.
-
- .. note::
-
- For backwards compatibility purposes, this function can also be imported
- as ``model_path_tuple``, although doing so will cause a deprecation
- warning to be emitted.
"""
return tuple(_resource_path_list(resource, *elements))
@@ -404,8 +383,8 @@ def virtual_root(resource, request):
the resource object representing the :term:`virtual root` of the
current :term:`request`. Using a virtual root in a
:term:`traversal` -based :app:`Pyramid` application permits
- rooting, for example, the resource at the traversal path ``/cms`` at
- ``http://example.com/`` instead of rooting it at
+ rooting. For example, the resource at the traversal path ``/cms`` will
+ be found at ``http://example.com/`` instead of rooting it at
``http://example.com/cms/``.
If the ``resource`` passed in is a context obtained via
@@ -427,11 +406,20 @@ def virtual_root(resource, request):
try:
reg = request.registry
except AttributeError:
- reg = get_current_registry() # b/c
- urlgenerator = reg.queryMultiAdapter((resource, request), IContextURL)
- if urlgenerator is None:
- urlgenerator = TraversalContextURL(resource, request)
- return urlgenerator.virtual_root()
+ reg = get_current_registry()
+ url_adapter = reg.queryMultiAdapter((resource, request), IResourceURL)
+ if url_adapter is None:
+ url_adapter = ResourceURL(resource, request)
+
+ vpath, rpath = url_adapter.virtual_path, url_adapter.physical_path
+ if rpath != vpath and rpath.endswith(vpath):
+ vroot_path = rpath[:-len(vpath)]
+ return find_resource(resource, vroot_path)
+
+ try:
+ return request.root
+ except AttributeError:
+ return find_root(resource)
def traversal_path(path):
""" Variant of :func:`pyramid.traversal.traversal_path_info` suitable for
@@ -577,7 +565,7 @@ the ``safe`` argument to this function. This corresponds to the
if PY2:
# special-case on Python 2 for speed? unchecked
- def quote_path_segment(segment, safe=''):
+ def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE):
""" %s """ % quote_path_segment_doc
# The bit of this code that deals with ``_segment_cache`` is an
# optimization: we cache all the computation of URL path segments
@@ -596,7 +584,7 @@ if PY2:
_segment_cache[(segment, safe)] = result
return result
else:
- def quote_path_segment(segment, safe=''):
+ def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE):
""" %s """ % quote_path_segment_doc
# The bit of this code that deals with ``_segment_cache`` is an
# optimization: we cache all the computation of URL path segments
@@ -624,6 +612,7 @@ class ResourceTreeTraverser(object):
:term:`location` aware) ."""
+ VH_ROOT_KEY = VH_ROOT_KEY
VIEW_SELECTOR = '@@'
def __init__(self, root):
@@ -662,9 +651,9 @@ class ResourceTreeTraverser(object):
raise URLDecodeError(e.encoding, e.object, e.start, e.end,
e.reason)
- if VH_ROOT_KEY in environ:
+ if self.VH_ROOT_KEY in environ:
# HTTP_X_VHM_ROOT
- vroot_path = decode_path_info(environ[VH_ROOT_KEY])
+ vroot_path = decode_path_info(environ[self.VH_ROOT_KEY])
vroot_tuple = split_path_info(vroot_path)
vpath = vroot_path + path # both will (must) be unicode or asciistr
vroot_idx = len(vroot_tuple) - 1
@@ -728,9 +717,9 @@ class ResourceTreeTraverser(object):
ModelGraphTraverser = ResourceTreeTraverser # b/w compat, not API, used in wild
-@implementer(IResourceURL, IContextURL)
+@implementer(IResourceURL)
class ResourceURL(object):
- vroot_varname = VH_ROOT_KEY
+ VH_ROOT_KEY = VH_ROOT_KEY
def __init__(self, resource, request):
physical_path_tuple = resource_path_tuple(resource)
@@ -744,7 +733,7 @@ class ResourceURL(object):
virtual_path_tuple = physical_path_tuple
environ = request.environ
- vroot_path = environ.get(self.vroot_varname)
+ vroot_path = environ.get(self.VH_ROOT_KEY)
# if the physical path starts with the virtual root path, trim it out
# of the virtual path
@@ -761,59 +750,6 @@ class ResourceURL(object):
self.virtual_path_tuple = virtual_path_tuple # IResourceURL attr (1.5)
self.physical_path_tuple = physical_path_tuple # IResourceURL attr (1.5)
- # bw compat for IContextURL methods
- self.resource = resource
- self.context = resource
- self.request = request
-
- # IContextURL method (deprecated in 1.3)
- def virtual_root(self):
- environ = self.request.environ
- vroot_varname = self.vroot_varname
- if vroot_varname in environ:
- return find_resource(self.context, environ[vroot_varname])
- # shortcut instead of using find_root; we probably already
- # have it on the request
- try:
- return self.request.root
- except AttributeError:
- return find_root(self.context)
-
- # IContextURL method (deprecated in 1.3)
- def __call__(self):
- """ Generate a URL based on the :term:`lineage` of a :term:`resource`
- object that is ``self.context``. If any resource in the context
- lineage has a Unicode name, it will be converted to a UTF-8 string
- before being attached to the URL. If a ``HTTP_X_VHM_ROOT`` key is
- present in the WSGI environment, its value will be treated as a
- 'virtual root path': the path of the URL generated by this will be
- left-stripped of this virtual root path value.
- """
- local_url = getattr(self.context, '__resource_url__', None)
- if local_url is not None:
- result = local_url(
- self.request,
- {'virtual_path':self.virtual_path,
- 'physical_path':self.physical_path},
- )
- if result is not None:
- # allow it to punt by returning ``None``
- return result
-
- app_url = self.request.application_url # never ends in a slash
- return app_url + self.virtual_path
-
-TraversalContextURL = ResourceURL # deprecated as of 1.3
-
-deprecated(
- 'TraversalContextURL',
- 'As of Pyramid 1.3 the, "pyramid.traversal.TraversalContextURL" class is '
- 'scheduled to be removed. Use the '
- '"pyramid.config.Configurator.add_resource_url_adapter" method to register '
- 'a class that implements "pyramid.interfaces.IResourceURL" instead. '
- 'See the "What\'s new In Pyramid 1.3" document for a further description.'
- )
-
@lru_cache(1000)
def _join_path_tuple(tuple):
return tuple and '/'.join([quote_path_segment(x) for x in tuple]) or '/'
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 fd62f0057..9634f61da 100644
--- a/pyramid/url.py
+++ b/pyramid/url.py
@@ -1,7 +1,6 @@
""" Utility functions for dealing with URLs in pyramid """
import os
-import warnings
from repoze.lru import lru_cache
@@ -25,10 +24,11 @@ from pyramid.threadlocal import get_current_registry
from pyramid.traversal import (
ResourceURL,
quote_path_segment,
+ PATH_SAFE,
+ PATH_SEGMENT_SAFE,
)
-PATH_SAFE = '/:@&+$,' # from webob
-QUERY_SAFE = '/?:@!$&\'()*+,;=' # RFC 3986
+QUERY_SAFE = "/?:@!$&'()*+,;=" # RFC 3986
ANCHOR_SAFE = QUERY_SAFE
def parse_url_overrides(kw):
@@ -364,7 +364,7 @@ class URLMethodsMixin(object):
of ``query`` may be a sequence of two-tuples *or* a data structure with
an ``.items()`` method that returns a sequence of two-tuples
(presumably a dictionary). This data structure will be turned into a
- query string per the documentation of :func:``pyramid.url.urlencode``
+ query string per the documentation of :func:`pyramid.url.urlencode`
function. This will produce a query string in the
``x-www-form-urlencoded`` encoding. A non-``x-www-form-urlencoded``
query string may be used by passing a *string* value as ``query`` in
@@ -454,7 +454,7 @@ class URLMethodsMixin(object):
``resource_url(someresource, 'element1', 'element2', query={'a':1},
route_name='blogentry')`` is roughly equivalent to doing::
- remainder_path = request.resource_path(someobject)
+ traversal_path = request.resource_path(someobject)
url = request.route_url(
'blogentry',
'element1',
@@ -486,7 +486,7 @@ class URLMethodsMixin(object):
'element2', route_name='blogentry', route_kw={'id':'4'},
_query={'a':'1'})`` is roughly equivalent to::
- remainder_path = request.resource_path_tuple(someobject)
+ traversal_path = request.resource_path_tuple(someobject)
kw = {'id':'4', '_query':{'a':'1'}, 'traverse':traversal_path}
url = request.route_url(
'blogentry',
@@ -533,89 +533,72 @@ class URLMethodsMixin(object):
virtual_path = getattr(url_adapter, 'virtual_path', None)
- if virtual_path is None:
- # old-style IContextURL adapter (Pyramid 1.2 and previous)
- warnings.warn(
- 'Pyramid is using an IContextURL adapter to generate a '
- 'resource URL; any "app_url", "host", "port", or "scheme" '
- 'arguments passed to resource_url are being ignored. To '
- 'avoid this behavior, as of Pyramid 1.3, register an '
- 'IResourceURL adapter instead of an IContextURL '
- 'adapter for the resource type(s). IContextURL adapters '
- 'will be ignored in a later major release of Pyramid.',
- DeprecationWarning,
- 2)
-
- resource_url = url_adapter()
+ app_url = None
+ scheme = None
+ host = None
+ port = None
+
+ if 'route_name' in kw:
+ newkw = {}
+ route_name = kw['route_name']
+ remainder = getattr(url_adapter, 'virtual_path_tuple', None)
+ if remainder is None:
+ # older user-supplied IResourceURL adapter without 1.5
+ # 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
+
+ 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)
+
+ if 'app_url' in kw:
+ app_url = kw['app_url']
+
+ if 'scheme' in kw:
+ scheme = kw['scheme']
+
+ if 'host' in kw:
+ host = kw['host']
+
+ if 'port' in kw:
+ port = kw['port']
- else:
- # IResourceURL adapter (Pyramid 1.3 and after)
- app_url = None
- scheme = None
- host = None
- port = None
-
- if 'route_name' in kw:
- newkw = {}
- route_name = kw['route_name']
- remainder = getattr(url_adapter, 'virtual_path_tuple', None)
- if remainder is None:
- # older user-supplied IResourceURL adapter without 1.5
- # 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
-
- 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)
-
- if 'app_url' in kw:
- app_url = kw['app_url']
-
- 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
-
- resource_url = None
- local_url = getattr(resource, '__resource_url__', None)
-
- if local_url is not None:
- # the resource handles its own url generation
- d = dict(
- virtual_path=virtual_path,
- physical_path=url_adapter.physical_path,
- app_url=app_url,
- )
-
- # allow __resource_url__ to punt by returning None
- resource_url = local_url(self, d)
-
- if resource_url is None:
- # the resource did not handle its own url generation or the
- # __resource_url__ function returned None
- resource_url = app_url + virtual_path
+ 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
+
+ resource_url = None
+ local_url = getattr(resource, '__resource_url__', None)
+
+ if local_url is not None:
+ # the resource handles its own url generation
+ d = dict(
+ virtual_path=virtual_path,
+ physical_path=url_adapter.physical_path,
+ app_url=app_url,
+ )
+
+ # allow __resource_url__ to punt by returning None
+ resource_url = local_url(self, d)
+
+ if resource_url is None:
+ # the resource did not handle its own url generation or the
+ # __resource_url__ function returned None
+ resource_url = app_url + virtual_path
qs = ''
anchor = ''
@@ -947,4 +930,4 @@ def current_route_path(request, *elements, **kw):
@lru_cache(1000)
def _join_elements(elements):
- return '/'.join([quote_path_segment(s, safe=':@&+$,') for s in elements])
+ return '/'.join([quote_path_segment(s, safe=PATH_SEGMENT_SAFE) for s in elements])
diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py
index c88ad9590..a61071845 100644
--- a/pyramid/urldispatch.py
+++ b/pyramid/urldispatch.py
@@ -22,6 +22,7 @@ from pyramid.exceptions import URLDecodeError
from pyramid.traversal import (
quote_path_segment,
split_path_info,
+ PATH_SAFE,
)
_marker = object()
@@ -207,6 +208,10 @@ def _compile_route(route):
return d
gen = ''.join(gen)
+
+ def q(v):
+ return quote_path_segment(v, safe=PATH_SAFE)
+
def generator(dict):
newdict = {}
for k, v in dict.items():
@@ -223,17 +228,17 @@ def _compile_route(route):
# a stararg argument
if is_nonstr_iter(v):
v = '/'.join(
- [quote_path_segment(x, safe='/') for x in v]
+ [q(x) for x in v]
) # native
else:
if v.__class__ not in string_types:
v = str(v)
- v = quote_path_segment(v, safe='/')
+ v = q(v)
else:
if v.__class__ not in string_types:
v = str(v)
# v may be bytes (py2) or native string (py3)
- v = quote_path_segment(v, safe='/')
+ v = q(v)
# at this point, the value will be a native string
newdict[k] = v
diff --git a/pyramid/util.py b/pyramid/util.py
index 4936dcb24..2827884a3 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -3,7 +3,7 @@ import functools
try:
# py2.7.7+ and py3.3+ have native comparison support
from hmac import compare_digest
-except ImportError: # pragma: nocover
+except ImportError: # pragma: no cover
compare_digest = None
import inspect
import traceback
@@ -28,13 +28,24 @@ from pyramid.compat import (
from pyramid.interfaces import IActionInfo
from pyramid.path import DottedNameResolver as _DottedNameResolver
+_marker = object()
+
class DottedNameResolver(_DottedNameResolver):
def __init__(self, package=None): # default to package = None for bw compat
_DottedNameResolver.__init__(self, package)
-_marker = object()
+def is_string_or_iterable(v):
+ if isinstance(v, string_types):
+ return True
+ if hasattr(v, '__iter__'):
+ return True
+def as_sorted_tuple(val):
+ if not is_nonstr_iter(val):
+ val = (val,)
+ val = tuple(sorted(val))
+ return val
class InstancePropertyHelper(object):
"""A helper object for assigning properties and descriptors to instances.
@@ -220,17 +231,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 = {}
diff --git a/pyramid/view.py b/pyramid/view.py
index 498bdde45..0c1b8cd97 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -657,8 +657,19 @@ class ViewMethodsMixin(object):
This method returns a :term:`response` object or raises
:class:`pyramid.httpexceptions.HTTPNotFound` if a matching view cannot
- be found."""
+ be found.
+ 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.
+
+ .. 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.
+
+ """
if request is None:
request = self
registry = getattr(request, 'registry', None)
@@ -673,7 +684,7 @@ 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
@@ -690,6 +701,11 @@ class ViewMethodsMixin(object):
secure=secure,
request_iface=request_iface.combined,
)
- if response is None:
- raise HTTPNotFound
- return response
+
+ if response is None:
+ 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/scaffoldtests.sh b/scaffoldtests.sh
index 84fd8e072..317bff8b5 100755
--- a/scaffoldtests.sh
+++ b/scaffoldtests.sh
@@ -1,3 +1,2 @@
#!/bin/bash
-tox -e{py27,py33,py34,pypy}-scaffolds,
-
+tox -e{py27,py34,py35,pypy}-scaffolds,
diff --git a/setup.cfg b/setup.cfg
index b52d24bdb..6f1f33760 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -13,7 +13,56 @@ docs = develop easy_install pyramid[docs]
[bdist_wheel]
universal = 1
+[metadata]
+license_file = LICENSE.txt
+
[flake8]
-ignore = E301,E302,E731,E261,E123,E121,E128,E129,E125,W291,E501,W293,E303,W391,E266,E231,E201,E202,E127,E262,E265
+ignore =
+ # E121: continuation line under-indented for hanging indent
+ E121,
+ # E123: closing bracket does not match indentation of opening bracket's line
+ E123,
+ # E125: continuation line with same indent as next logical line
+ E125,
+ # E127: continuation line over-indented for visual indent
+ E127,
+ # E128: continuation line under-indented for visual indent
+ E128,
+ # E129: visually indented line with same indent as next logical line
+ E129,
+ # E201: whitespace after '('
+ E201,
+ # E202: whitespace before ')'
+ E202,
+ # E231: missing whitespace after ',', ';', or ':'
+ E231,
+ # E261: at least two spaces before inline comment
+ E261,
+ # E262: inline comment should start with '# '
+ E262,
+ # E265: block comment should start with '# '
+ E265,
+ # E266: too many leading '#' for block comment
+ E266,
+ # E301: expected 1 blank line, found 0
+ E301,
+ # E302: expected 2 blank lines, found 0
+ E302,
+ # E303: too many blank lines (3)
+ E303,
+ # E305: expected 2 blank lines after class or function definition, found 1
+ E305,
+ # E306: expected 1 blank line before a nested definition, found 0
+ E306,
+ # E501: line too long (82 > 79 characters)
+ E501,
+ # E731: do not assign a lambda expression, use a def
+ E731,
+ # W291: trailing whitespace
+ W291,
+ # W293: blank line contains whitespace
+ W293,
+ # W391: blank line at end of file
+ W391
exclude = pyramid/tests/,pyramid/compat.py,pyramid/resource.py
show-source = True
diff --git a/setup.py b/setup.py
index f738ee623..03416efe7 100644
--- a/setup.py
+++ b/setup.py
@@ -13,21 +13,9 @@
##############################################################################
import os
-import sys
from setuptools import setup, find_packages
-py_version = sys.version_info[:2]
-
-PY3 = py_version[0] == 3
-
-if PY3:
- if py_version < (3, 4):
- raise RuntimeError('On Python 3, Pyramid requires Python 3.4 or better')
-else:
- if py_version < (2, 7):
- raise RuntimeError('On Python 2, Pyramid requires Python 2.7 or better')
-
here = os.path.abspath(os.path.dirname(__file__))
try:
with open(os.path.join(here, 'README.rst')) as f:
@@ -39,21 +27,23 @@ except IOError:
install_requires = [
'setuptools',
- 'WebOb >= 1.3.1', # request.domain and CookieProfile
+ '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',
]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
+ 'zope.component >= 4.0', # py3 compat
]
-if not PY3:
- tests_require.append('zope.component>=3.11.0')
docs_extras = [
'Sphinx >= 1.3.5',
@@ -61,17 +51,17 @@ docs_extras = [
'repoze.sphinx.autointerface',
'pylons_sphinx_latesturl',
'pylons-sphinx-themes',
- 'sphinxcontrib-programoutput',
+ 'sphinxcontrib-autoprogram',
]
testing_extras = tests_require + [
'nose',
'coverage',
- 'virtualenv', # for scaffolding tests
+ 'virtualenv', # for scaffolding tests
]
setup(name='pyramid',
- version='1.8.dev0',
+ version='1.9a2',
description='The Pyramid Web Framework, a Pylons project',
long_description=README + '\n\n' + CHANGES,
classifiers=[
@@ -82,6 +72,7 @@ setup(name='pyramid',
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Framework :: Pyramid",
@@ -97,6 +88,7 @@ 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={
'testing': testing_extras,
diff --git a/tox.ini b/tox.ini
index 8ceb142cb..242decfc4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,9 +1,9 @@
[tox]
envlist =
- py27,py34,py35,py36,pypy,
+ py27,py34,py35,py36,py37,pypy,
docs,pep8,
{py2,py3}-cover,coverage,
-skip-missing-interpreters = True
+skip_missing_interpreters = True
[testenv]
# Most of these are defaults but if you specify any you can't fall back
@@ -13,12 +13,13 @@ basepython =
py34: python3.4
py35: python3.5
py36: python3.6
+ py37: python3.7
pypy: pypy
py2: python2.7
py3: python3.5
commands =
- pip install pyramid[testing]
+ pip install -q pyramid[testing]
nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:}
[testenv:py27-scaffolds]
@@ -39,6 +40,12 @@ commands =
python pyramid/scaffolds/tests.py
deps = virtualenv
+[testenv:py36-scaffolds]
+basepython = python3.6
+commands =
+ python pyramid/scaffolds/tests.py
+deps = virtualenv
+
[testenv:pypy-scaffolds]
basepython = pypy
commands =
@@ -71,7 +78,7 @@ commands =
# combination of versions of coverage and nosexcover that i can find.
[testenv:py2-cover]
commands =
- pip install pyramid[testing]
+ pip install -q pyramid[testing]
coverage run --source=pyramid {envbindir}/nosetests
coverage xml -o coverage-py2.xml
setenv =
@@ -79,7 +86,7 @@ setenv =
[testenv:py3-cover]
commands =
- pip install pyramid[testing]
+ pip install -q pyramid[testing]
coverage run --source=pyramid {envbindir}/nosetests
coverage xml -o coverage-py3.xml
setenv =