summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--CHANGES.txt252
-rw-r--r--CONTRIBUTORS.txt4
-rw-r--r--HACKING.txt24
-rw-r--r--TODO.txt21
-rw-r--r--docs/_static/directory_structure_generic.pngbin0 -> 11845 bytes
-rw-r--r--docs/_static/directory_structure_initial.pngbin0 -> 7752 bytes
-rw-r--r--docs/_static/directory_structure_pyramid.pngbin0 -> 16746 bytes
m---------docs/_themes0
-rw-r--r--docs/api/config.rst4
-rw-r--r--docs/api/request.rst24
-rw-r--r--docs/api/view.rst6
-rw-r--r--docs/conf.py4
-rw-r--r--docs/designdefense.rst62
-rw-r--r--docs/glossary.rst16
-rw-r--r--docs/index.rst8
-rw-r--r--docs/latexindex.rst1
-rw-r--r--docs/narr/MyProject/myproject/__init__.py1
-rw-r--r--docs/narr/MyProject/setup.py1
-rw-r--r--docs/narr/advconfig.rst1
-rw-r--r--docs/narr/assets.rst20
-rw-r--r--docs/narr/hooks.rst76
-rw-r--r--docs/narr/install.rst23
-rw-r--r--docs/narr/introspector.rst12
-rw-r--r--docs/narr/project.rst55
-rw-r--r--docs/narr/renderers.rst280
-rw-r--r--docs/narr/sessions.rst24
-rw-r--r--docs/narr/subrequest.rst6
-rw-r--r--docs/narr/templates.rst436
-rw-r--r--docs/narr/urldispatch.rst7
-rw-r--r--docs/narr/viewconfig.rst67
-rw-r--r--docs/quick_tour.rst218
-rw-r--r--docs/quick_tour/jinja2/hello_world.jinja23
-rw-r--r--docs/quick_tutorial/authentication.rst134
-rw-r--r--docs/quick_tutorial/authentication/development.ini42
-rw-r--r--docs/quick_tutorial/authentication/setup.py14
-rw-r--r--docs/quick_tutorial/authentication/tutorial/__init__.py25
-rw-r--r--docs/quick_tutorial/authentication/tutorial/home.pt18
-rw-r--r--docs/quick_tutorial/authentication/tutorial/login.pt25
-rw-r--r--docs/quick_tutorial/authentication/tutorial/security.py8
-rw-r--r--docs/quick_tutorial/authentication/tutorial/views.py64
-rw-r--r--docs/quick_tutorial/authorization.rst112
-rw-r--r--docs/quick_tutorial/authorization/development.ini42
-rw-r--r--docs/quick_tutorial/authorization/setup.py14
-rw-r--r--docs/quick_tutorial/authorization/tutorial/__init__.py26
-rw-r--r--docs/quick_tutorial/authorization/tutorial/home.pt18
-rw-r--r--docs/quick_tutorial/authorization/tutorial/login.pt25
-rw-r--r--docs/quick_tutorial/authorization/tutorial/resources.py9
-rw-r--r--docs/quick_tutorial/authorization/tutorial/security.py8
-rw-r--r--docs/quick_tutorial/authorization/tutorial/views.py66
-rw-r--r--docs/quick_tutorial/conf.py281
-rw-r--r--docs/quick_tutorial/databases.rst195
-rw-r--r--docs/quick_tutorial/databases/development.ini49
-rw-r--r--docs/quick_tutorial/databases/setup.py20
-rw-r--r--docs/quick_tutorial/databases/sqltutorial.sqlitebin0 -> 12288 bytes
-rw-r--r--docs/quick_tutorial/databases/tutorial/__init__.py21
-rw-r--r--docs/quick_tutorial/databases/tutorial/initialize_db.py37
-rw-r--r--docs/quick_tutorial/databases/tutorial/models.py35
-rw-r--r--docs/quick_tutorial/databases/tutorial/tests.py58
-rw-r--r--docs/quick_tutorial/databases/tutorial/views.py96
-rw-r--r--docs/quick_tutorial/databases/tutorial/wiki_view.pt19
-rw-r--r--docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt22
-rw-r--r--docs/quick_tutorial/databases/tutorial/wikipage_view.pt17
-rw-r--r--docs/quick_tutorial/debugtoolbar.rst89
-rw-r--r--docs/quick_tutorial/debugtoolbar/development.ini40
-rw-r--r--docs/quick_tutorial/debugtoolbar/setup.py13
-rw-r--r--docs/quick_tutorial/debugtoolbar/tutorial/__init__.py13
-rw-r--r--docs/quick_tutorial/forms.rst148
-rw-r--r--docs/quick_tutorial/forms/development.ini41
-rw-r--r--docs/quick_tutorial/forms/setup.py15
-rw-r--r--docs/quick_tutorial/forms/tutorial/__init__.py13
-rw-r--r--docs/quick_tutorial/forms/tutorial/tests.py36
-rw-r--r--docs/quick_tutorial/forms/tutorial/views.py96
-rw-r--r--docs/quick_tutorial/forms/tutorial/wiki_view.pt19
-rw-r--r--docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt22
-rw-r--r--docs/quick_tutorial/forms/tutorial/wikipage_view.pt17
-rw-r--r--docs/quick_tutorial/functional_testing.rst70
-rw-r--r--docs/quick_tutorial/functional_testing/development.ini40
-rw-r--r--docs/quick_tutorial/functional_testing/setup.py13
-rw-r--r--docs/quick_tutorial/functional_testing/tutorial/__init__.py13
-rw-r--r--docs/quick_tutorial/functional_testing/tutorial/tests.py31
-rw-r--r--docs/quick_tutorial/hello_world.rst114
-rw-r--r--docs/quick_tutorial/hello_world/app.py17
-rw-r--r--docs/quick_tutorial/index.rst50
-rw-r--r--docs/quick_tutorial/ini.rst146
-rw-r--r--docs/quick_tutorial/ini/development.ini38
-rw-r--r--docs/quick_tutorial/ini/setup.py13
-rw-r--r--docs/quick_tutorial/ini/tutorial/__init__.py13
-rw-r--r--docs/quick_tutorial/jinja2.rst98
-rw-r--r--docs/quick_tutorial/jinja2/development.ini42
-rw-r--r--docs/quick_tutorial/jinja2/setup.py13
-rw-r--r--docs/quick_tutorial/jinja2/tutorial/__init__.py9
-rw-r--r--docs/quick_tutorial/jinja2/tutorial/home.jinja29
-rw-r--r--docs/quick_tutorial/jinja2/tutorial/tests.py50
-rw-r--r--docs/quick_tutorial/jinja2/tutorial/views.py18
-rw-r--r--docs/quick_tutorial/json.rst103
-rw-r--r--docs/quick_tutorial/json/development.ini41
-rw-r--r--docs/quick_tutorial/json/setup.py14
-rw-r--r--docs/quick_tutorial/json/tutorial/__init__.py11
-rw-r--r--docs/quick_tutorial/json/tutorial/home.pt9
-rw-r--r--docs/quick_tutorial/json/tutorial/tests.py50
-rw-r--r--docs/quick_tutorial/json/tutorial/views.py19
-rw-r--r--docs/quick_tutorial/logging.rst79
-rw-r--r--docs/quick_tutorial/logging/development.ini41
-rw-r--r--docs/quick_tutorial/logging/setup.py14
-rw-r--r--docs/quick_tutorial/logging/tutorial/__init__.py10
-rw-r--r--docs/quick_tutorial/logging/tutorial/home.pt9
-rw-r--r--docs/quick_tutorial/logging/tutorial/tests.py44
-rw-r--r--docs/quick_tutorial/logging/tutorial/views.py23
-rw-r--r--docs/quick_tutorial/more_view_classes.rst182
-rw-r--r--docs/quick_tutorial/more_view_classes/development.ini41
-rw-r--r--docs/quick_tutorial/more_view_classes/setup.py14
-rw-r--r--docs/quick_tutorial/more_view_classes/tutorial/__init__.py10
-rw-r--r--docs/quick_tutorial/more_view_classes/tutorial/delete.pt9
-rw-r--r--docs/quick_tutorial/more_view_classes/tutorial/edit.pt10
-rw-r--r--docs/quick_tutorial/more_view_classes/tutorial/hello.pt16
-rw-r--r--docs/quick_tutorial/more_view_classes/tutorial/home.pt12
-rw-r--r--docs/quick_tutorial/more_view_classes/tutorial/tests.py31
-rw-r--r--docs/quick_tutorial/more_view_classes/tutorial/views.py39
-rw-r--r--docs/quick_tutorial/package.rst112
-rw-r--r--docs/quick_tutorial/package/setup.py9
-rw-r--r--docs/quick_tutorial/package/tutorial/__init__.py1
-rw-r--r--docs/quick_tutorial/package/tutorial/app.py17
-rw-r--r--docs/quick_tutorial/request_response.rst103
-rw-r--r--docs/quick_tutorial/request_response/development.ini41
-rw-r--r--docs/quick_tutorial/request_response/setup.py13
-rw-r--r--docs/quick_tutorial/request_response/tutorial/__init__.py9
-rw-r--r--docs/quick_tutorial/request_response/tutorial/tests.py54
-rw-r--r--docs/quick_tutorial/request_response/tutorial/views.py22
-rw-r--r--docs/quick_tutorial/requirements.rst250
-rw-r--r--docs/quick_tutorial/routing.rst121
-rw-r--r--docs/quick_tutorial/routing/development.ini41
-rw-r--r--docs/quick_tutorial/routing/setup.py14
-rw-r--r--docs/quick_tutorial/routing/tutorial/__init__.py9
-rw-r--r--docs/quick_tutorial/routing/tutorial/home.pt10
-rw-r--r--docs/quick_tutorial/routing/tutorial/tests.py36
-rw-r--r--docs/quick_tutorial/routing/tutorial/views.py20
-rw-r--r--docs/quick_tutorial/scaffolds.rst86
-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/development.ini60
-rw-r--r--docs/quick_tutorial/scaffolds/production.ini54
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/__init__.py12
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/favicon.icobin0 -> 1406 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.pngbin0 -> 333 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.pngbin0 -> 203 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css8
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.pngbin0 -> 2797 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css372
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png (renamed from pyramid/scaffolds/alchemy/+package+/static/pyramid-small.png)bin7044 -> 7044 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.pngbin0 -> 33055 bytes
-rw-r--r--docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gifbin0 -> 49 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.cfg27
-rw-r--r--docs/quick_tutorial/scaffolds/setup.py42
-rw-r--r--docs/quick_tutorial/sessions.rst100
-rw-r--r--docs/quick_tutorial/sessions/development.ini41
-rw-r--r--docs/quick_tutorial/sessions/setup.py14
-rw-r--r--docs/quick_tutorial/sessions/tutorial/__init__.py14
-rw-r--r--docs/quick_tutorial/sessions/tutorial/home.pt10
-rw-r--r--docs/quick_tutorial/sessions/tutorial/tests.py44
-rw-r--r--docs/quick_tutorial/sessions/tutorial/views.py29
-rw-r--r--docs/quick_tutorial/static_assets.rst91
-rw-r--r--docs/quick_tutorial/static_assets/development.ini41
-rw-r--r--docs/quick_tutorial/static_assets/setup.py14
-rw-r--r--docs/quick_tutorial/static_assets/tutorial/__init__.py11
-rw-r--r--docs/quick_tutorial/static_assets/tutorial/home.pt11
-rw-r--r--docs/quick_tutorial/static_assets/tutorial/static/app.css4
-rw-r--r--docs/quick_tutorial/static_assets/tutorial/tests.py44
-rw-r--r--docs/quick_tutorial/static_assets/tutorial/views.py18
-rw-r--r--docs/quick_tutorial/templating.rst123
-rw-r--r--docs/quick_tutorial/templating/development.ini41
-rw-r--r--docs/quick_tutorial/templating/setup.py14
-rw-r--r--docs/quick_tutorial/templating/tutorial/__init__.py10
-rw-r--r--docs/quick_tutorial/templating/tutorial/home.pt9
-rw-r--r--docs/quick_tutorial/templating/tutorial/tests.py44
-rw-r--r--docs/quick_tutorial/templating/tutorial/views.py13
-rw-r--r--docs/quick_tutorial/tutorial_approach.rst45
-rw-r--r--docs/quick_tutorial/unit_testing.rst119
-rw-r--r--docs/quick_tutorial/unit_testing/development.ini40
-rw-r--r--docs/quick_tutorial/unit_testing/setup.py13
-rw-r--r--docs/quick_tutorial/unit_testing/tutorial/__init__.py13
-rw-r--r--docs/quick_tutorial/unit_testing/tutorial/tests.py18
-rw-r--r--docs/quick_tutorial/view_classes.rst98
-rw-r--r--docs/quick_tutorial/view_classes/development.ini41
-rw-r--r--docs/quick_tutorial/view_classes/setup.py14
-rw-r--r--docs/quick_tutorial/view_classes/tutorial/__init__.py10
-rw-r--r--docs/quick_tutorial/view_classes/tutorial/home.pt9
-rw-r--r--docs/quick_tutorial/view_classes/tutorial/tests.py44
-rw-r--r--docs/quick_tutorial/view_classes/tutorial/views.py17
-rw-r--r--docs/quick_tutorial/views.rst122
-rw-r--r--docs/quick_tutorial/views/development.ini40
-rw-r--r--docs/quick_tutorial/views/setup.py13
-rw-r--r--docs/quick_tutorial/views/tutorial/__init__.py9
-rw-r--r--docs/quick_tutorial/views/tutorial/tests.py44
-rw-r--r--docs/quick_tutorial/views/tutorial/views.py14
-rw-r--r--docs/tutorials/bfg/index.rst204
-rw-r--r--docs/tutorials/wiki/basiclayout.rst9
-rw-r--r--docs/tutorials/wiki/definingviews.rst4
-rw-r--r--docs/tutorials/wiki/src/authorization/setup.py1
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/__init__.py1
-rw-r--r--docs/tutorials/wiki/src/basiclayout/setup.py1
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py1
-rw-r--r--docs/tutorials/wiki/src/models/setup.py1
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/__init__.py1
-rw-r--r--docs/tutorials/wiki/src/tests/setup.py1
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/__init__.py1
-rw-r--r--docs/tutorials/wiki/src/views/setup.py1
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/__init__.py1
-rw-r--r--docs/tutorials/wiki/tests.rst4
-rw-r--r--docs/tutorials/wiki2/authorization.rst8
-rw-r--r--docs/tutorials/wiki2/basiclayout.rst30
-rw-r--r--docs/tutorials/wiki2/definingmodels.rst2
-rw-r--r--docs/tutorials/wiki2/definingviews.rst8
-rw-r--r--docs/tutorials/wiki2/src/authorization/setup.py5
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/__init__.py1
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models.py3
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/setup.py5
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py1
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models.py4
-rw-r--r--docs/tutorials/wiki2/src/models/setup.py5
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/__init__.py1
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models.py4
-rw-r--r--docs/tutorials/wiki2/src/tests/setup.py5
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/__init__.py1
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models.py3
-rw-r--r--docs/tutorials/wiki2/src/views/setup.py5
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/__init__.py1
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models.py4
-rw-r--r--docs/tutorials/wiki2/tests.rst4
-rw-r--r--docs/whatsnew-1.0.rst6
-rw-r--r--docs/whatsnew-1.1.rst9
-rw-r--r--docs/whatsnew-1.5.rst204
-rw-r--r--pyramid/authorization.py3
-rw-r--r--pyramid/chameleon_text.py37
-rw-r--r--pyramid/chameleon_zpt.py49
-rw-r--r--pyramid/compat.py2
-rw-r--r--pyramid/config/__init__.py45
-rw-r--r--pyramid/config/adapters.py10
-rw-r--r--pyramid/config/factories.py8
-rw-r--r--pyramid/config/i18n.py18
-rw-r--r--pyramid/config/rendering.py61
-rw-r--r--pyramid/config/routes.py173
-rw-r--r--pyramid/config/views.py61
-rw-r--r--pyramid/encode.py6
-rw-r--r--pyramid/events.py11
-rw-r--r--pyramid/fixers/__init__.py1
-rw-r--r--pyramid/fixers/fix_bfg_imports.py220
-rw-r--r--pyramid/interfaces.py22
-rw-r--r--pyramid/mako_templating.py252
-rw-r--r--pyramid/path.py6
-rw-r--r--pyramid/renderers.py197
-rw-r--r--pyramid/request.py171
-rw-r--r--pyramid/router.py9
-rw-r--r--pyramid/scaffolds/alchemy/+package+/__init__.py1
-rw-r--r--pyramid/scaffolds/alchemy/+package+/models.py4
-rw-r--r--pyramid/scaffolds/alchemy/setup.py_tmpl5
-rw-r--r--pyramid/scaffolds/starter/+package+/__init__.py1
-rw-r--r--pyramid/scaffolds/starter/+package+/static/pyramid-small.pngbin7044 -> 0 bytes
-rw-r--r--pyramid/scaffolds/starter/setup.py_tmpl1
-rw-r--r--pyramid/scaffolds/tests.py13
-rw-r--r--pyramid/scaffolds/zodb/+package+/__init__.py1
-rw-r--r--pyramid/scaffolds/zodb/+package+/static/pyramid-small.pngbin7044 -> 0 bytes
-rw-r--r--pyramid/scaffolds/zodb/setup.py_tmpl5
-rw-r--r--pyramid/scripts/pcreate.py2
-rw-r--r--pyramid/scripts/pviews.py27
-rw-r--r--pyramid/testing.py22
-rw-r--r--pyramid/tests/fixtures/components.mak3
-rw-r--r--pyramid/tests/fixtures/hello .world.mako3
-rw-r--r--pyramid/tests/fixtures/hello_inherit_pkg.mak2
-rw-r--r--pyramid/tests/fixtures/hellocompo.mak3
-rw-r--r--pyramid/tests/fixtures/helloinherit.mak2
-rw-r--r--pyramid/tests/fixtures/helloworld.mak3
-rw-r--r--pyramid/tests/fixtures/helloworld.mako3
-rw-r--r--pyramid/tests/fixtures/layout.mak2
-rw-r--r--pyramid/tests/fixtures/minimal.pt3
-rw-r--r--pyramid/tests/fixtures/nonminimal.mak1
-rw-r--r--pyramid/tests/fixtures/pp.pt3
-rw-r--r--pyramid/tests/fixtures/withmacro.pt7
-rw-r--r--pyramid/tests/pkgs/fixtureapp/subpackage/templates/bar.pt2
-rw-r--r--pyramid/tests/pkgs/fixtureapp/templates/fixture.pt6
-rw-r--r--pyramid/tests/pkgs/rendererscanapp/__init__.py2
-rw-r--r--pyramid/tests/pkgs/rendererscanapp/one.pt4
-rw-r--r--pyramid/tests/pkgs/rendererscanapp/two/__init__.py2
-rw-r--r--pyramid/tests/pkgs/rendererscanapp/two/two.pt4
-rw-r--r--pyramid/tests/pkgs/viewdecoratorapp/views/templates/foo.pt3
-rw-r--r--pyramid/tests/pkgs/viewdecoratorapp/views/views.py4
-rw-r--r--pyramid/tests/test_authorization.py13
-rw-r--r--pyramid/tests/test_chameleon_text.py145
-rw-r--r--pyramid/tests/test_chameleon_zpt.py175
-rw-r--r--pyramid/tests/test_config/files/minimal.pt3
-rw-r--r--pyramid/tests/test_config/files/minimal.txt1
-rw-r--r--pyramid/tests/test_config/pkgs/asset/subpackage/templates/bar.pt2
-rw-r--r--pyramid/tests/test_config/pkgs/asset/templates/fixture.pt6
-rw-r--r--pyramid/tests/test_config/test_factories.py105
-rw-r--r--pyramid/tests/test_config/test_i18n.py18
-rw-r--r--pyramid/tests/test_config/test_init.py293
-rw-r--r--pyramid/tests/test_config/test_rendering.py31
-rw-r--r--pyramid/tests/test_config/test_routes.py6
-rw-r--r--pyramid/tests/test_config/test_util.py14
-rw-r--r--pyramid/tests/test_config/test_views.py223
-rw-r--r--pyramid/tests/test_encode.py12
-rw-r--r--pyramid/tests/test_integration.py14
-rw-r--r--pyramid/tests/test_mako_templating.py655
-rw-r--r--pyramid/tests/test_path.py16
-rw-r--r--pyramid/tests/test_renderers.py491
-rw-r--r--pyramid/tests/test_request.py157
-rw-r--r--pyramid/tests/test_scripts/dummy.py7
-rw-r--r--pyramid/tests/test_scripts/test_pcreate.py15
-rw-r--r--pyramid/tests/test_scripts/test_pviews.py51
-rw-r--r--pyramid/tests/test_traversal.py15
-rw-r--r--pyramid/tests/test_view.py63
-rw-r--r--pyramid/traversal.py7
-rw-r--r--pyramid/view.py51
-rw-r--r--rtd.txt1
-rw-r--r--setup.py8
319 files changed, 8319 insertions, 4792 deletions
diff --git a/.travis.yml b/.travis.yml
index 00c293046..9d4324ff8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,7 @@ python:
- pypy
- 3.2
-script: python setup.py test
+script: python setup.py test -q
notifications:
email:
diff --git a/CHANGES.txt b/CHANGES.txt
index cdaad5a90..051328a02 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,249 @@
+Unreleased
+==========
+
+Bug Fixes
+---------
+
+- Fix the ``pcreate`` script so that when the target directory name ends with a
+ slash it does not produce a non-working project directory structure.
+ Previously saying ``pcreate -s starter /foo/bar/`` produced different output
+ than saying ``pcreate -s starter /foo/bar``. The former did not work
+ properly.
+
+- Fix the ``principals_allowed_by_permission`` method of
+ ``ACLAuthorizationPolicy`` so it anticipates a callable ``__acl__``
+ on resources. Previously it did not try to call the ``__acl__``
+ if it was callable.
+
+- The ``pviews`` script did not work when a url required custom request
+ methods in order to perform traversal. Custom methods and descriptors added
+ via ``pyramid.config.Configurator.add_request_method`` will now be present,
+ allowing traversal to continue.
+ See https://github.com/Pylons/pyramid/issues/1104
+
+Documentation
+-------------
+
+- Added a "Quick Tutorial" to go with the Quick Tour
+
+- Removed mention of ``pyramid_beaker`` from docs. Beaker is no longer
+ maintained. Point people at ``pyramid_redis_sessions`` instead.
+
+Backwards Incompatibilities
+---------------------------
+
+- The key/values in the ``_query`` parameter of ``request.route_url`` and the
+ ``query`` parameter of ``request.resource_url`` (and their variants), used
+ to encode a value of ``None`` as the string ``'None'``, leaving the resulting
+ query string to be ``a=b&key=None``. The value is now dropped in this
+ situation, leaving a query string of ``a=b&key=``.
+ See https://github.com/Pylons/pyramid/issues/1119
+
+1.5a2 (2013-09-22)
+==================
+
+Features
+--------
+
+- Users can now provide dotted Python names to as the ``factory`` argument
+ the Configurator methods named ``add_{view,route,subscriber}_predicate``
+ (instead of passing the predicate factory directly, you can pass a
+ dotted name which refers to the factory).
+
+Bug Fixes
+---------
+
+- Fix an exception in ``pyramid.path.package_name`` when resolving the package
+ name for namespace packages that had no ``__file__`` attribute.
+
+Backwards Incompatibilities
+---------------------------
+
+- Pyramid no longer depends on or configures the Mako and Chameleon templating
+ system renderers by default. Disincluding these templating systems by
+ default means that the Pyramid core has fewer dependencies and can run on
+ future platforms without immediate concern for the compatibility of its
+ templating add-ons. It also makes maintenance slightly more effective, as
+ different people can maintain the templating system add-ons that they
+ understand and care about without needing commit access to the Pyramid core,
+ and it allows users who just don't want to see any packages they don't use
+ come along for the ride when they install Pyramid.
+
+ This means that upon upgrading to Pyramid 1.5a2+, projects that use either
+ of these templating systems will see a traceback that ends something like
+ this when their application attempts to render a Chameleon or Mako template::
+
+ ValueError: No such renderer factory .pt
+
+ Or::
+
+ ValueError: No such renderer factory .mako
+
+ Or::
+
+ ValueError: No such renderer factory .mak
+
+ Support for Mako templating has been moved into an add-on package named
+ ``pyramid_mako``, and support for Chameleon templating has been moved into
+ an add-on package named ``pyramid_chameleon``. These packages are drop-in
+ replacements for the old built-in support for these templating langauges.
+ All you have to do is install them and make them active in your configuration
+ to register renderer factories for ``.pt`` and/or ``.mako`` (or ``.mak``) to
+ make your application work again.
+
+ To re-add support for Chameleon and/or Mako template renderers into your
+ existing projects, follow the below steps.
+
+ If you depend on Mako templates:
+
+ * Make sure the ``pyramid_mako`` package is installed. One way to do this
+ is by adding ``pyramid_mako`` to the ``install_requires`` section of your
+ package's ``setup.py`` file and afterwards rerunning ``setup.py develop``::
+
+ setup(
+ #...
+ install_requires=[
+ 'pyramid_mako', # new dependency
+ 'pyramid',
+ #...
+ ],
+ )
+
+ * Within the portion of your application which instantiates a Pyramid
+ ``pyramid.config.Configurator`` (often the ``main()`` function in
+ your project's ``__init__.py`` file), tell Pyramid to include the
+ ``pyramid_mako`` includeme::
+
+ config = Configurator(.....)
+ config.include('pyramid_mako')
+
+ If you depend on Chameleon templates:
+
+ * Make sure the ``pyramid_chameleon`` package is installed. One way to do
+ this is by adding ``pyramid_chameleon`` to the ``install_requires`` section
+ of your package's ``setup.py`` file and afterwards rerunning
+ ``setup.py develop``::
+
+ setup(
+ #...
+ install_requires=[
+ 'pyramid_chameleon', # new dependency
+ 'pyramid',
+ #...
+ ],
+ )
+
+ * Within the portion of your application which instantiates a Pyramid
+ ``~pyramid.config.Configurator`` (often the ``main()`` function in
+ your project's ``__init__.py`` file), tell Pyramid to include the
+ ``pyramid_chameleon`` includeme::
+
+ config = Configurator(.....)
+ config.include('pyramid_chameleon')
+
+ Note that it's also fine to install these packages into *older* Pyramids for
+ forward compatibility purposes. Even if you don't upgrade to Pyramid 1.5
+ immediately, performing the above steps in a Pyramid 1.4 installation is
+ perfectly fine, won't cause any difference, and will give you forward
+ compatibility when you eventually do upgrade to Pyramid 1.5.
+
+ With the removal of Mako and Chameleon support from the core, some
+ unit tests that use the ``pyramid.renderers.render*`` methods may begin to
+ fail. If any of your unit tests are invoking either
+ ``pyramid.renderers.render()`` or ``pyramid.renderers.render_to_response()``
+ with either Mako or Chameleon templates then the
+ ``pyramid.config.Configurator`` instance in effect during
+ the unit test should be also be updated to include the addons, as shown
+ above. For example::
+
+ class ATest(unittest.TestCase):
+ def setUp(self):
+ self.config = pyramid.testing.setUp()
+ self.config.include('pyramid_mako')
+
+ def test_it(self):
+ result = pyramid.renderers.render('mypkg:templates/home.mako', {})
+
+ Or::
+
+ class ATest(unittest.TestCase):
+ def setUp(self):
+ self.config = pyramid.testing.setUp()
+ self.config.include('pyramid_chameleon')
+
+ def test_it(self):
+ result = pyramid.renderers.render('mypkg:templates/home.pt', {})
+
+- If you're using the Pyramid debug toolbar, when you upgrade Pyramid to
+ 1.5a2+, you'll also need to upgrade the ``pyramid_debugtoolbar`` package to
+ at least version 1.0.8, as older toolbar versions are not compatible with
+ Pyramid 1.5a2+ due to the removal of Mako support from the core. It's
+ fine to use this newer version of the toolbar code with older Pyramids too.
+
+- Removed the ``request.response_*`` varying attributes. These attributes
+ have been deprecated since Pyramid 1.1, and as per the deprecation policy,
+ have now been removed.
+
+- ``request.response`` will no longer be mutated when using the
+ ``pyramid.renderers.render()`` API. Almost all renderers mutate the
+ ``request.response`` response object (for example, the JSON renderer sets
+ ``request.response.content_type`` to ``application/json``), but this is
+ only necessary when the renderer is generating a response; it was a bug
+ when it was done as a side effect of calling ``pyramid.renderers.render()``.
+
+- Removed the ``bfg2pyramid`` fixer script.
+
+- The ``pyramid.events.NewResponse`` event is now sent **after** response
+ callbacks are executed. It previously executed before response callbacks
+ were executed. Rationale: it's more useful to be able to inspect the response
+ after response callbacks have done their jobs instead of before.
+
+- Removed the class named ``pyramid.view.static`` that had been deprecated
+ since Pyramid 1.1. Instead use ``pyramid.static.static_view`` with
+ ``use_subpath=True`` argument.
+
+- Removed the ``pyramid.view.is_response`` function that had been deprecated
+ since Pyramid 1.1. Use the ``pyramid.request.Request.is_response`` method
+ instead.
+
+- Removed the ability to pass the following arguments to
+ ``pyramid.config.Configurator.add_route``: ``view``, ``view_context``.
+ ``view_for``, ``view_permission``, ``view_renderer``, and ``view_attr``.
+ Using these arguments had been deprecated since Pyramid 1.1. Instead of
+ passing view-related arguments to ``add_route``, use a separate call to
+ ``pyramid.config.Configurator.add_view`` to associate a view with a route
+ using its ``route_name`` argument. Note that this impacts the
+ ``pyramid.config.Configurator.add_static_view`` function too, because it
+ delegates to ``add_route``.
+
+- Removed the ability to influence and query a ``pyramid.request.Request``
+ object as if it were a dictionary. Previously it was possible to use methods
+ like ``__getitem__``, ``get``, ``items``, and other dictlike methods to
+ access values in the WSGI environment. This behavior had been deprecated
+ since Pyramid 1.1. Use methods of ``request.environ`` (a real dictionary)
+ instead.
+
+- Removed ancient backwards compatibily hack in
+ ``pyramid.traversal.DefaultRootFactory`` which populated the ``__dict__`` of
+ the factory with the matchdict values for compatibility with BFG 0.9.
+
+- The ``renderer_globals_factory`` argument to the
+ ``pyramid.config.Configurator` constructor and its ``setup_registry`` method
+ has been removed. The ``set_renderer_globals_factory`` method of
+ ``pyramid.config.Configurator`` has also been removed. The (internal)
+ ``pyramid.interfaces.IRendererGlobals`` interface was also removed. These
+ arguments, methods and interfaces had been deprecated since 1.1. Use a
+ ``BeforeRender`` event subscriber as documented in the "Hooks" chapter of the
+ Pyramid narrative documentation instead of providing renderer globals values
+ to the configurator.
+
+Deprecations
+------------
+
+- The ``pyramid.config.Configurator.set_request_property`` method now issues
+ a deprecation warning when used. It had been docs-deprecated in 1.4
+ but did not issue a deprecation warning when used.
+
1.5a1 (2013-08-30)
==================
@@ -42,7 +288,7 @@ Features
The above example will ensure that the view is called if the request method
is not POST (at least if no other view is more specific).
- The :class:`pyramid.config.not_` class can be used against any value that is
+ The ``pyramid.config.not_`` class can be used against any value that is
a predicate value passed in any of these contexts:
- ``pyramid.config.Configurator.add_view``
@@ -111,9 +357,7 @@ Features
In the past, only the most specific type containing views would be checked
and if no matching view could be found then a PredicateMismatch would be
raised. Now predicate mismatches don't hide valid views registered on
- super-types. Here's an example that now works:
-
- .. code-block:: python
+ super-types. Here's an example that now works::
class IResource(Interface):
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 0ddaebf15..bfe22e540 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -220,3 +220,7 @@ Contributors
- Matthew Wilkes, 2013/08/23
- Takahiro Fujiwara, 2013/08/28
+
+- Doug Hellmann, 2013/09/06
+
+- Karl O. Pinc, 2013/09/27
diff --git a/HACKING.txt b/HACKING.txt
index abfed6dab..4ebb59160 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -21,10 +21,10 @@ checkout.
(alternately, create a writeable fork on GitHub and check that out).
Since pyramid is a framework and not an application, it can be
-convenient to work against a sample application, preferably in its
-own virtualenv. A quick way to achieve this is to (ab-)use ``tox``
-(http://codespeak.net/~hpk/tox/) with a custom configuration file that's part of
-the checkout::
+convenient to work against a sample application, preferably in its own
+virtualenv. A quick way to achieve this is to (ab-)use ``tox``
+(http://tox.readthedocs.org/en/latest/) with a custom configuration
+file that's part of the checkout::
tox -c hacking-tox.ini
@@ -78,7 +78,7 @@ In order to add a feature to Pyramid:
documentation (in ``docs/``).
- The feature must work fully on the following CPython versions: 2.6,
- 2.7, and 3.2 on both UNIX and Windows.
+ 2.7, 3.2, and 3.3 on both UNIX and Windows.
- The feature must work on the latest version of PyPy.
@@ -104,7 +104,9 @@ Coding Style
- PEP8 compliance. Whitespace rules are relaxed: not necessary to put
2 newlines between classes. But 80-column lines, in particular, are
- mandatory.
+ mandatory. See
+ http://docs.pylonsproject.org/en/latest/community/codestyle.html for more
+ information.
- Please do not remove trailing whitespace. Configure your editor to reduce
diff noise. See https://github.com/Pylons/pyramid/issues/788 for more.
@@ -112,13 +114,15 @@ Coding Style
Running Tests
--------------
-- To run all tests for Pyramid on a single Python version, run ``nosetests`` from
- your development virtualenv (See *Using a Development Checkout* above).
+- To run all tests for Pyramid on a single Python version, run ``nosetests``
+ from your development virtualenv (See *Using a Development Checkout* above).
- To run individual tests (i.e. during development) you can use a regular
expression with the ``-t`` parameter courtesy of the `nose-selecttests
- <https://pypi.python.org/pypi/nose-selecttests/>`_ plugin that's been installed (along with nose itself) via ``python setup.py dev``. The easiest usage is to
- simply provide the verbatim name of the test you're working on.
+ <https://pypi.python.org/pypi/nose-selecttests/>`_ plugin that's been
+ installed (along with nose itself) via ``python setup.py dev``. The
+ easiest usage is to simply provide the verbatim name of the test you're
+ working on.
- To run the full set of Pyramid tests on all platforms, install ``tox``
(http://codespeak.net/~hpk/tox/) into a system Python. The ``tox`` console
diff --git a/TODO.txt b/TODO.txt
index 46eb33b82..62b8c39f4 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -12,9 +12,6 @@ Nice-to-Have
- Have action methods return their discriminators.
-- Fix renderers chapter to better document system values passed to template
- renderers.
-
- Modify view mapper narrative docs to not use pyramid_handlers.
- Modify the urldispatch chapter examples to assume a scan rather than
@@ -41,8 +38,6 @@ Nice-to-Have
- Add narrative docs for wsgiapp and wsgiapp2.
-- Flesh out "Paste" narrative docs chapter.
-
- Basic WSGI documentation (pipeline / app / server).
- Change docs about creating a venusian decorator to not use ZCA (use
@@ -121,27 +116,17 @@ Nice-to-Have
Future
------
-- 1.5: remove ``pyramid.view.static`` and ``pyramid.view.is_response``.
-
-- 1.5: turn ``pyramid.settings.Settings`` into a function that returns the
+- 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.5: Remove ``pyramid.requests.DeprecatedRequestMethodsMixin`` and code in
- renderers module that looks for _response_content_type, et. al.
-
-- 1.5: Maybe? deprecate set_request_property in favor of pointing people at
- add_request_method, schedule removal for 1.8?
-
-- 1.5: Remove pyramid.config.rendering set_renderer_globals_factory maybe.
-
-- 1.5: remove pyramid.config.route _add_view_from_route function.
-
- 1.6: Remove IContextURL and TraversalContextURL.
- 1.7: Change ``pyramid.authentication.AuthTktAuthenticationPolicy`` default
``hashalg`` to ``sha512``.
+- 1.8 Remove set_request_property.
+
Probably Bad Ideas
------------------
diff --git a/docs/_static/directory_structure_generic.png b/docs/_static/directory_structure_generic.png
new file mode 100644
index 000000000..c6d1a5b03
--- /dev/null
+++ b/docs/_static/directory_structure_generic.png
Binary files differ
diff --git a/docs/_static/directory_structure_initial.png b/docs/_static/directory_structure_initial.png
new file mode 100644
index 000000000..000f1bb27
--- /dev/null
+++ b/docs/_static/directory_structure_initial.png
Binary files differ
diff --git a/docs/_static/directory_structure_pyramid.png b/docs/_static/directory_structure_pyramid.png
new file mode 100644
index 000000000..74edd6533
--- /dev/null
+++ b/docs/_static/directory_structure_pyramid.png
Binary files differ
diff --git a/docs/_themes b/docs/_themes
-Subproject a181b85d4ca08fa109ee2786d4f1b5b17e9b458
+Subproject 26732645619b372764097e5e8086f89871d90c0
diff --git a/docs/api/config.rst b/docs/api/config.rst
index 1f65be9f1..48dd2f0b9 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -52,10 +52,6 @@
.. automethod:: override_asset(to_override, override_with)
- :methodcategory:`Setting Renderer Globals`
-
- .. automethod:: set_renderer_globals_factory(factory)
-
:methodcategory:`Getting and Adding Settings`
.. automethod:: add_settings
diff --git a/docs/api/request.rst b/docs/api/request.rst
index 02290eaf3..72abddb68 100644
--- a/docs/api/request.rst
+++ b/docs/api/request.rst
@@ -199,13 +199,13 @@
- Ensures that the user implied by the request passed has the necessary
authorization to invoke view callable before calling it.
- - causes a :class:`~pyramid.events.NewResponse` event to be sent when
- the Pyramid application returns a response.
-
- Calls any :term:`response callback` functions defined within the
request's lifetime if a response is obtained from the Pyramid
application.
+ - causes a :class:`~pyramid.events.NewResponse` event to be sent if a
+ response is obtained.
+
- Calls any :term:`finished callback` functions defined within the
request's lifetime.
@@ -235,24 +235,6 @@
.. automethod:: resource_path
- .. attribute:: response_*
-
- In Pyramid 1.0, you could set attributes on a
- :class:`pyramid.request.Request` which influenced the behavior of
- *rendered* responses (views which use a :term:`renderer` and which
- don't directly return a response). These attributes began with
- ``response_``, such as ``response_headerlist``. If you needed to
- influence response values from a view that uses a renderer (such as the
- status code, a header, the content type, etc) you would set these
- attributes. See :ref:`response_prefixed_attrs` for further discussion.
- As of Pyramid 1.1, assignment to ``response_*`` attrs is deprecated.
- Assigning to one is still supported but will cause a deprecation
- warning to be emitted, and eventually the feature will be removed. For
- new code, instead of assigning ``response_*`` attributes to the
- request, use API of the :attr:`pyramid.request.Request.response`
- object (exposed to view code as ``request.response``) to influence
- rendered response behavior.
-
.. attribute:: json_body
This property will return the JSON-decoded variant of the request
diff --git a/docs/api/view.rst b/docs/api/view.rst
index 21d2bb90d..d8e429552 100644
--- a/docs/api/view.rst
+++ b/docs/api/view.rst
@@ -11,8 +11,6 @@
.. autofunction:: render_view
- .. autofunction:: is_response
-
.. autoclass:: view_config
:members:
@@ -25,8 +23,4 @@
.. autoclass:: forbidden_view_config
:members:
- .. autoclass:: static
- :members:
- :inherited-members:
-
diff --git a/docs/conf.py b/docs/conf.py
index a7a4a441a..3b6e75a17 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -73,9 +73,6 @@ intersphinx_mapping = {
'http://docs.pylonsproject.org/projects/deform/en/latest',
None),
'sqla': ('http://docs.sqlalchemy.org/en/latest', None),
- 'beaker': (
- 'http://docs.pylonsproject.org/projects/pyramid_beaker/en/latest',
- None),
'who': ('http://docs.repoze.org/who/latest', None),
'python': ('http://docs.python.org', None),
'python3': ('http://docs.python.org/3', None),
@@ -92,6 +89,7 @@ intersphinx_mapping = {
None),
}
+
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
diff --git a/docs/designdefense.rst b/docs/designdefense.rst
index cebcf6218..bbce3e29c 100644
--- a/docs/designdefense.rst
+++ b/docs/designdefense.rst
@@ -594,55 +594,45 @@ requires extensibility because it must be deployed in multiple locations.
Pyramid Is Too Big
------------------
-"The :app:`Pyramid` compressed tarball is almost 2MB. It must be
+"The :app:`Pyramid` compressed tarball is larger than 2MB. It must be
enormous!"
-No. We just ship it with test code and helper templates. Here's a
+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:
docs/
- 3.0MB
+ 4.9MB
pyramid/tests/
- 1.1MB
+ 2.0MB
-pyramid/paster_templates/
+pyramid/scaffolds/
- 804KB
+ 460KB
-pyramid/ (except for ``pyramd/tests and pyramid/paster_templates``)
+pyramid/ (except for ``pyramd/tests`` and ``pyramid/scaffolds``)
- 539K
+ 844KB
-The actual :app:`Pyramid` runtime code is about 10% of the total size of the
-tarball omitting docs, helper templates used for package generation, and test
-code. Of the approximately 19K lines of Python code in the package, the code
+Of the approximately 34K lines of Python code in the package, the code
that actually has a chance of executing during normal operation, excluding
-tests and paster template Python files, accounts for approximately 5K lines
-of Python code. This is comparable to Pylons 1.X, which ships with a little
-over 2K lines of Python code, excluding tests.
+tests and scaffolding Python files, accounts for approximately 10K lines.
+
Pyramid Has Too Many Dependencies
---------------------------------
-This is true. At the time of this writing (Pyramid 1.3), the total number of
-Python package distributions that :app:`Pyramid` depends upon transitively is
-if you use Python 3.2 or Python 2.7 is 10. If you use Python 2.6, Pyramid
-will pull in 12 package distributions. This is a lot more than zero package
-distribution dependencies: a metric which various Python microframeworks and
-Django boast.
-
-However, Pyramid 1.2 relied on 15 packages under Python 2.7 and 17 packages
-under Python 2.6, so we've made progress here. A port to Python 3 completed
-in Pyramid 1.3 helped us shed a good number of dependencies by forcing us to
-make better packaging decisions.
+Over time, we've made lots of progress on reducing the number of packaging
+dependencies Pyramid has had. Pyramid 1.2 had 15 of them. Pyramid 1.3 and 1.4
+had 12 of them. The current release as of this writing, Pyramid 1.5, has
+only 7. This number is unlikely to become any smaller.
-In the future, we may also move templating system dependencies out of the
-core and place them in add-on packages, to be included by developers instead
-of by the framework. This would reduce the number of core dependencies by
-about five, leaving us with only five remaining core dependencies.
+A port to Python 3 completed in Pyramid 1.3 helped us shed a good number of
+dependencies by forcing us to make better packaging decisions. Removing
+Chameleon and Mako templating system dependencies in the Pyramid core in 1.5
+let us shed most of the remainder of them.
Pyramid "Cheats" To Obtain Speed
--------------------------------
@@ -774,7 +764,7 @@ content management system (CMS) may have basic functionality that needs to be
extended for a particular deployment. That CMS system may be deployed for
many organizations at many places. Some number of deployments of this CMS
may be deployed centrally by a third party and managed as a group. It's
-useful to be able to extend such a system for each deployment via preordained
+easier to be able to extend such a system for each deployment via preordained
plugpoints than it is to continually keep each software branch of the system
in sync with some upstream source: the upstream developers may change code in
such a way that your changes to the same codebase conflict with theirs in
@@ -802,8 +792,8 @@ such a feature.
main template and the CSS in a separate Python package which defines
overrides.
-- If a deployment needs an application page to do something differently needs
- it to expose more or different information, the deployer may override the
+- If a deployment needs an application page to do something differently, or
+ to expose more or different information, the deployer may override the
view that renders the page within a separate Python package.
- If a deployment needs an additional feature, the deployer may add a view to
@@ -820,7 +810,7 @@ won't regularly need to deal wth meaningless textual merge conflicts that
trivial changes to upstream packages often entail when it comes time to
update the upstream package, because if you extend an application externally,
there just is no textual merge done. Your modifications will also, for
-whatever its worth, be contained in one, canonical, well-defined place.
+whatever it's worth, be contained in one, canonical, well-defined place.
Branching an application and continually merging in order to get new features
and bugfixes is clearly useful. You can do that with a :app:`Pyramid`
@@ -832,7 +822,7 @@ dismiss this feature in favor of branching and merging because applications
written in their framework of choice aren't extensible out of the box in a
comparably fundamental way.
-While :app:`Pyramid` application are fundamentally extensible even if you
+While :app:`Pyramid` applications are fundamentally extensible even if you
don't write them with specific extensibility in mind, if you're moderately
adventurous, you can also take it a step further. If you learn more about
the :term:`Zope Component Architecture`, you can optionally use it to expose
@@ -852,7 +842,7 @@ applications by :app:`Pyramid` are good or bad is mostly pointless. You
needn't take advantage of the extensibility features provided by a particular
:app:`Pyramid` application in order to affect a modification for a particular
set of its deployments. You can ignore the application's extensibility
-plugpoints entirely, and instead use version control branching and merging to
+plugpoints entirely, and use version control branching and merging to
manage application deployment modifications instead, as if you were deploying
an application written using any other web framework.
@@ -1659,7 +1649,7 @@ If you can understand this hello world program, you can use Pyramid:
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()
-Pyramid has ~ 650 pages of documentation (printed), covering topics from the
+Pyramid has ~ 700 pages of documentation (printed), covering topics from the
very basic to the most advanced. *Nothing* is left undocumented, quite
literally. It also has an *awesome*, very helpful community. Visit the
#pyramid IRC channel on freenode.net (irc://freenode.net#pyramid) and see.
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 8ade889a3..398f945a4 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -798,9 +798,8 @@ Glossary
:term:`Internationalization`.
renderer globals
- Values injected as names into a renderer based on application
- policy. See :ref:`adding_renderer_globals` for more
- information.
+ Values injected as names into a renderer by a
+ :class:`pyramid.event.BeforeRender` event.
response callback
A user-defined callback executed by the :term:`router` at a
@@ -1010,7 +1009,8 @@ Glossary
Green Unicorn
Aka ``gunicorn``, a fast :term:`WSGI` server that runs on UNIX under
- Python 2.6+ or Python 3.1+. See http://gunicorn.org/ for detailed information.
+ Python 2.6+ or Python 3.1+. See http://gunicorn.org/ for detailed
+ information.
predicate factory
A callable which is used by a third party during the registration of a
@@ -1021,4 +1021,10 @@ Glossary
add-on
A Python :term:`distribution` that uses Pyramid's extensibility
to plug into a Pyramid application and provide extra,
- configurable services. \ No newline at end of file
+ configurable services.
+
+ pyramid_redis_sessions
+ A package by Eric Rasmussen which allows you to store Pyramid session
+ data in a Redis database. See
+ https://pypi.python.org/pypi/pyramid_redis_sessions for more information.
+
diff --git a/docs/index.rst b/docs/index.rst
index d2a0008a8..78a00966d 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -20,7 +20,8 @@ After you install :app:`Pyramid` and run this application, when you visit
See :ref:`firstapp_chapter` for a full explanation of how this application
works. Read the :ref:`html_narrative_documentation` to understand how
:app:`Pyramid` is designed to scale from simple applications like this to
-very large web applications.
+very large web applications. To just dive in headfirst, read the
+:doc:`quick_tour`.
Front Matter
============
@@ -43,10 +44,14 @@ up to speed right away.
:hidden:
quick_tour
+ quick_tutorial/index
* :doc:`quick_tour` goes through the major features in Pyramid, covering
a little about a lot.
+* :doc:`quick_tutorial/index` does the same, but in a tutorial format:
+ deeper treatment of each topic and with working code.
+
* To see a minimal Pyramid web application, check out
:ref:`firstapp_chapter`.
@@ -126,7 +131,6 @@ platforms.
tutorials/wiki2/index.rst
tutorials/wiki/index.rst
- tutorials/bfg/index.rst
tutorials/modwsgi/index.rst
.. _html_api_documentation:
diff --git a/docs/latexindex.rst b/docs/latexindex.rst
index eb14f5076..c4afff212 100644
--- a/docs/latexindex.rst
+++ b/docs/latexindex.rst
@@ -78,7 +78,6 @@ Tutorials
tutorials/wiki2/index.rst
tutorials/wiki/index.rst
- tutorials/bfg/index.rst
tutorials/modwsgi/index.rst
.. _api_documentation:
diff --git a/docs/narr/MyProject/myproject/__init__.py b/docs/narr/MyProject/myproject/__init__.py
index 6c512f52f..ad5ecbc6f 100644
--- a/docs/narr/MyProject/myproject/__init__.py
+++ b/docs/narr/MyProject/myproject/__init__.py
@@ -5,6 +5,7 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.scan()
diff --git a/docs/narr/MyProject/setup.py b/docs/narr/MyProject/setup.py
index 6969c73e7..a23f46c91 100644
--- a/docs/narr/MyProject/setup.py
+++ b/docs/narr/MyProject/setup.py
@@ -10,6 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
'pyramid_debugtoolbar',
'waitress',
]
diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst
index 1b8e33de3..d3431e39e 100644
--- a/docs/narr/advconfig.rst
+++ b/docs/narr/advconfig.rst
@@ -302,7 +302,6 @@ These are the methods of the configurator which provide conflict detection:
:meth:`~pyramid.config.Configurator.set_view_mapper`,
:meth:`~pyramid.config.Configurator.set_authentication_policy`,
:meth:`~pyramid.config.Configurator.set_authorization_policy`,
-:meth:`~pyramid.config.Configurator.set_renderer_globals_factory`,
:meth:`~pyramid.config.Configurator.set_locale_negotiator`,
:meth:`~pyramid.config.Configurator.set_default_permission`,
:meth:`~pyramid.config.Configurator.add_traverser`,
diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst
index 26b3e3a92..b0a8d18b0 100644
--- a/docs/narr/assets.rst
+++ b/docs/narr/assets.rst
@@ -227,14 +227,14 @@ API to generate them for you. For example:
.. code-block:: python
:linenos:
- from pyramid.chameleon_zpt import render_template_to_response
+ from pyramid.renderers import render_to_response
def my_view(request):
css_url = request.static_url('mypackage:assets/1/foo.css')
js_url = request.static_url('mypackage:assets/2/foo.js')
- return render_template_to_response('templates/my_template.pt',
- css_url = css_url,
- js_url = js_url)
+ return render_to_response('templates/my_template.pt',
+ dict(css_url=css_url, js_url=js_url),
+ request=request)
If the request "application URL" of the running system is
``http://example.com``, the ``css_url`` generated above would be:
@@ -336,7 +336,9 @@ your application root as below.
from pyramid.static import static_view
static_view = static_view('/path/to/static/dir', use_subpath=True)
-.. note:: For better cross-system flexibility, use an :term:`asset
+.. note::
+
+ For better cross-system flexibility, use an :term:`asset
specification` as the argument to :class:`~pyramid.static.static_view`
instead of a physical absolute filesystem path, e.g. ``mypackage:static``
instead of ``/path/to/mypackage/static``.
@@ -432,9 +434,9 @@ feature, a :term:`Configurator` API exists named
:meth:`pyramid.config.Configurator.override_asset`. This API allows you to
*override* the following kinds of assets defined in any Python package:
-- Individual :term:`Chameleon` templates.
+- Individual template files.
-- A directory containing multiple Chameleon templates.
+- A directory containing multiple template files.
- Individual static files served up by an instance of the
``pyramid.static.static_view`` helper class.
@@ -460,8 +462,8 @@ can override a single asset. For example:
:linenos:
config.override_asset(
- to_override='some.package:templates/mytemplate.pt',
- override_with='another.package:othertemplates/anothertemplate.pt')
+ to_override='some.package:templates/mytemplate.pt',
+ override_with='another.package:othertemplates/anothertemplate.pt')
The string value passed to both ``to_override`` and ``override_with`` sent to
the ``override_asset`` API is called an :term:`asset specification`. The
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 3a2568775..0c450fad7 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -372,10 +372,8 @@ that can be used for this purpose. For example:
def add_global(event):
event['mykey'] = 'foo'
-An object of this type is sent as an event just before a :term:`renderer` is
-invoked (but *after* the application-level renderer globals factory added via
-:class:`~pyramid.config.Configurator.set_renderer_globals_factory`, if any,
-has injected its own keys into the renderer globals dictionary).
+An object of this type is sent as an event just before a :term:`renderer`
+is invoked.
If a subscriber attempts to add a key that already exist in the renderer
globals dictionary, a :exc:`KeyError` is raised. This limitation is enforced
@@ -417,66 +415,6 @@ your view callable, like so:
See the API documentation for the :class:`~pyramid.events.BeforeRender` event
interface at :class:`pyramid.interfaces.IBeforeRender`.
-Another (deprecated) mechanism which allows event subscribers more control
-when adding renderer global values exists in :ref:`adding_renderer_globals`.
-
-.. index::
- single: adding renderer globals
-
-.. _adding_renderer_globals:
-
-Adding Renderer Globals (Deprecated)
-------------------------------------
-
-.. deprecated:: 1.1
- An alternative mechanism which allows event subscribers to add renderer
- global values is documented in :ref:`beforerender_event`.
-
-Whenever :app:`Pyramid` handles a request to perform a rendering (after a
-view with a ``renderer=`` configuration attribute is invoked, or when any of
-the methods beginning with ``render`` within the :mod:`pyramid.renderers`
-module are called), *renderer globals* can be injected into the *system*
-values sent to the renderer. By default, no renderer globals are injected,
-and the "bare" system values (such as ``request``, ``context``, ``view``, and
-``renderer_name``) are the only values present in the system dictionary
-passed to every renderer.
-
-A callback that :app:`Pyramid` will call every time a renderer is invoked can
-be added by passing a ``renderer_globals_factory`` argument to the
-constructor of the :term:`configurator`. This callback can either be a
-callable object or a :term:`dotted Python name` representing such a callable.
-
-.. code-block:: python
- :linenos:
-
- def renderer_globals_factory(system):
- return {'a': 1}
-
- config = Configurator(
- renderer_globals_factory=renderer_globals_factory)
-
-Such a callback must accept a single positional argument (notionally named
-``system``) which will contain the original system values. It must return a
-dictionary of values that will be merged into the system dictionary. See
-:ref:`renderer_system_values` for description of the values present in the
-system dictionary.
-
-If you're doing imperative configuration, and you'd rather do it after you've
-already constructed a :term:`configurator` it can also be registered via the
-:meth:`pyramid.config.Configurator.set_renderer_globals_factory` method:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.config import Configurator
-
- def renderer_globals_factory(system):
- return {'a': 1}
-
- config = Configurator()
- config.set_renderer_globals_factory(renderer_globals_factory)
-
-
.. index::
single: response callback
@@ -514,7 +452,7 @@ callback will be an exception object instead of its default value of
``None``.
Response callbacks are called in the order they're added
-(first-to-most-recently-added). All response callbacks are called *after*
+(first-to-most-recently-added). All response callbacks are called *before*
the :class:`~pyramid.events.NewResponse` event is sent. Errors raised by
response callbacks are not handled specially. They will be propagated to the
caller of the :app:`Pyramid` router application.
@@ -1384,9 +1322,11 @@ The first argument to :meth:`pyramid.config.Configurator.add_view_predicate`,
the name, is a string representing the name that is expected to be passed to
``view_config`` (or its imperative analogue ``add_view``).
-The second argument is a view or route predicate factory. A view or route
-predicate factory is most often a class with a constructor (``__init__``), a
-``text`` method, a ``phash`` method and a ``__call__`` method. For example:
+The second argument is a view or route predicate factory, or a :term:`dotted
+Python name` which refers to a view or route predicate factory. A view or
+route predicate factory is most often a class with a constructor
+(``__init__``), a ``text`` method, a ``phash`` method and a ``__call__``
+method. For example:
.. code-block:: python
:linenos:
diff --git a/docs/narr/install.rst b/docs/narr/install.rst
index fb67b899b..e419a8b20 100644
--- a/docs/narr/install.rst
+++ b/docs/narr/install.rst
@@ -25,6 +25,10 @@ on :term:`PyPy` (1.9+).
:app:`Pyramid` installation does not require the compilation of any C code, so
you need only a Python interpreter that meets the requirements mentioned.
+Some :app:`Pyramid` dependencies may attempt to build C extensions for
+performance speedups. If a compiler or Python headers are unavailable the
+dependency will fall back to using pure Python instead.
+
For Mac OS X Users
~~~~~~~~~~~~~~~~~~
@@ -285,13 +289,20 @@ Installing :app:`Pyramid` Into the Virtual Python Environment
After you've got your virtualenv installed, you may install :app:`Pyramid`
itself using the following commands:
-.. code-block:: text
-
- $ $VENV/bin/easy_install pyramid
+.. parsed-literal::
+
+ $ $VENV/bin/easy_install "pyramid==\ |release|\ "
The ``easy_install`` command will take longer than the previous ones to
complete, as it downloads and installs a number of dependencies.
+.. note::
+
+ If you see any warnings and/or errors related to failing to compile the C
+ extensions, in most cases you may safely ignore those errors. If you wish
+ to use the C extensions, please verify that you have a functioning compiler
+ and the Python header files installed.
+
.. index::
single: installing on Windows
@@ -357,9 +368,9 @@ You can use Pyramid on Windows under Python 2 or 3.
#. Use ``easy_install`` to get :app:`Pyramid` and its direct dependencies
installed:
- .. code-block:: text
-
- c:\env> %VENV%\Scripts\easy_install pyramid
+ .. parsed-literal::
+
+ c:\\env> %VENV%\\Scripts\\easy_install "pyramid==\ |release|\ "
What Gets Installed
-------------------
diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst
index dec22c5b1..3c0a6744f 100644
--- a/docs/narr/introspector.rst
+++ b/docs/narr/introspector.rst
@@ -232,18 +232,6 @@ introspectables in categories not described here.
The factory object (the resolved ``factory`` argument to
``add_renderer``).
-``renderer globals factory``
-
- There will be one and only one introspectable in the ``renderer globals
- factory`` category. It represents a call to
- :meth:`pyramid.config.Configurator.set_renderer_globals_factory`; it will
- have the following data.
-
- ``factory``
-
- The factory object (the resolved ``factory`` argument to
- ``set_renderer_globals_factory``).
-
``routes``
Each introspectable in the ``routes`` category represents a call to
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 52f13d5a8..9451f41b1 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -144,7 +144,7 @@ contains no space characters, so it's wise to *avoid* a path that contains
i.e. ``My Documents``. As a result, the author, when he uses Windows, just
puts his projects in ``C:\projects``.
-.. warning::
+.. warning::
You’ll need to avoid using ``pcreate`` to create a project with the same
name as a Python standard library component. In particular, this means you
@@ -193,10 +193,10 @@ Elided output from a run of this command on UNIX is shown below:
...
Finished processing dependencies for MyProject==0.0
-This will install a :term:`distribution` representing your project into the
-interpreter's library set so it can be found by ``import`` statements and by
-other console scripts such as ``pserve``, ``pshell``, ``proutes`` and
-``pviews``.
+This will install a :term:`distribution` representing your project
+into the virtual environment interpreter's library set so it can be
+found by ``import`` statements and by other console scripts such as
+``pserve``, ``pshell``, ``proutes`` and ``pviews``.
.. index::
single: running tests
@@ -570,8 +570,8 @@ adding more settings to 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
-exists, and its value is ``true``, :term:`Chameleon` and :term:`Mako`
-template changes will not require an application restart to be detected. See
+exists, and its value is ``true``, supported template changes will not
+require an application restart to be detected. See
:ref:`reload_templates_section` for more information.
.. warning:: The ``pyramid.reload_templates`` option should be turned off for
@@ -818,7 +818,7 @@ also informs Python that the directory which contains it is a *package*.
#. Line 1 imports the :term:`Configurator` class from :mod:`pyramid.config`
that we use later.
-#. Lines 4-11 define a function named ``main`` that returns a :app:`Pyramid`
+#. Lines 4-12 define a function named ``main`` that returns a :app:`Pyramid`
WSGI application. This function is meant to be called by the
:term:`PasteDeploy` framework as a result of running ``pserve``.
@@ -826,17 +826,20 @@ also informs Python that the directory which contains it is a *package*.
Line 7 creates an instance of a :term:`Configurator`.
- Line 8 registers a static view, which will serve up the files from the
+ Line 8 adds support for Chameleon templating bindings, allowing us to
+ specify renderers with the ``.pt`` extension.
+
+ Line 9 registers a static view, which will serve up the files from the
``myproject:static`` :term:`asset specification` (the ``static``
directory of the ``myproject`` package).
- Line 9 adds a :term:`route` to the configuration. This route is later
+ Line 10 adds a :term:`route` to the configuration. This route is later
used by a view in the ``views`` module.
- Line 10 calls ``config.scan()``, which picks up view registrations declared
+ Line 11 calls ``config.scan()``, which picks up view registrations declared
elsewhere in the package (in this case, in the ``views.py`` module).
- Line 11 returns a :term:`WSGI` application to the caller of the function
+ Line 12 returns a :term:`WSGI` application to the caller of the function
(Pyramid's pserve).
.. index::
@@ -868,18 +871,17 @@ specification` that 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 it actually points to is a :term:`Chameleon` ZPT
-template file.
+optional. The template file pointed to is a :term:`Chameleon` ZPT
+template file (``templates/my_template.pt``).
This view callable function is handed a single piece of information: the
:term:`request`. The *request* is an instance of the :term:`WebOb`
``Request`` class representing the browser's request to our server.
-This view returns a dictionary. When this view is invoked, a
-:term:`renderer` converts the dictionary returned by the view into HTML, and
-returns the result as the :term:`response`. This view is configured to
-invoke a renderer which uses a :term:`Chameleon` ZPT template
-(``templates/my_template.pt``).
+This view is configured to invoke a :term:`renderer` on a template. The
+dictionary the view returns (on line 6) provides the value the renderer
+substitutes into the template when generating HTML. The renderer then
+returns the HTML in a :term:`response`.
See :ref:`views_which_use_a_renderer` for more information about how views,
renderers, and templates relate and cooperate.
@@ -968,12 +970,15 @@ named ``views`` instead of within a single ``views.py`` file, you might:
- Create a ``views`` directory inside your ``myproject`` package directory
(the same directory which holds ``views.py``).
-- *Move* the existing ``views.py`` file to a file inside the new ``views``
- directory named, say, ``blog.py``.
+- Create a file within the new ``views`` directory named ``__init__.py``. (It
+ can be empty. This just tells Python that the ``views`` directory is a
+ *package*.)
-- Create a file within the new ``views`` directory named ``__init__.py`` (it
- can be empty, this just tells Python that the ``views`` directory is a
- *package*.
+- *Move* the existing ``views.py`` file to a file inside the new ``views``
+ directory named, say, ``blog.py``. Because the ``templates`` directory
+ remains in the ``myproject`` package, the template :term:`asset
+ specification` values in ``blog.py`` must now be fully qualified with the
+ project's package name (``myproject:templates/blog.pt``).
You can then continue to add view callable functions to the ``blog.py``
module, but you can also add other ``.py`` files which contain view callable
@@ -1022,7 +1027,7 @@ 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, making it a good choice as a default server from the
-perspective of Pyramid's developers.
+perspective of Pyramid's developers.
Any WSGI server is capable of running a :app:`Pyramid` application. But we
suggest you stick with the default server for development, and that you wait
diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst
index a2811dbae..4046c67fa 100644
--- a/docs/narr/renderers.rst
+++ b/docs/narr/renderers.rst
@@ -49,10 +49,12 @@ Writing View Callables Which Use a Renderer
-------------------------------------------
As we've seen, a view callable needn't always return a Response object.
-Instead, it may return an arbitrary Python object, with the expectation
-that a :term:`renderer` will convert that object into a response instance on
-your behalf. Some renderers use a templating system; other renderers use
-object serialization techniques.
+Instead, it may return an arbitrary Python object, with the expectation that
+a :term:`renderer` will convert that object into a response instance on your
+behalf. Some renderers use a templating system; other renderers use object
+serialization techniques. In practice, renderers obtain application data
+values from Python dictionaries so, in practice, view callables which use
+renderers return Python dictionaries.
View configuration can vary the renderer associated with a view callable via
the ``renderer`` attribute. For example, this call to
@@ -67,9 +69,8 @@ When this configuration is added to an application, the
``myproject.views.my_view`` view callable will now use a ``json`` renderer,
which renders view return values to a :term:`JSON` response serialization.
-Other built-in renderers include renderers which use the :term:`Chameleon`
-templating language to render a dictionary to a response. Additional
-renderers can be added by developers to the system as necessary.
+Pyramid defines several :ref:`built_in_renderers`, and additional renderers
+can be added by developers to the system as necessary.
See :ref:`adding_and_overriding_renderers`.
Views which use a renderer and return a non-Response value can vary non-body
@@ -129,6 +130,11 @@ Built-In Renderers
Several built-in renderers exist in :app:`Pyramid`. These renderers can be
used in the ``renderer`` attribute of view configurations.
+.. note::
+
+ Bindings for officially supported templating languages can be found
+ at :ref:`available_template_system_bindings`.
+
.. index::
pair: renderer; string
@@ -203,13 +209,7 @@ representing the JSON serialization of the return value:
The return value needn't be a dictionary, but the return value must contain
values serializable by the configured serializer (by default ``json.dumps``).
-.. note::
-
- Extra arguments can be passed to the serializer by overriding the default
- ``json`` renderer. See :class:`pyramid.renderers.JSON` and
- :ref:`adding_and_overriding_renderers` for more information.
-
-You can configure a view to use the JSON renderer by naming ``json`` as the
+You can configure a view to use the JSON renderer by naming``json`` as the
``renderer`` argument of a view configuration, e.g. by using
:meth:`~pyramid.config.Configurator.add_view`:
@@ -230,6 +230,18 @@ using the api of the ``request.response`` attribute. See
Serializing Custom Objects
++++++++++++++++++++++++++
+Some objects are not, by default, JSON-serializable (such as datetimes and
+other arbitrary Python objects). You can, however, register code that makes
+non-serializable objects serializable in two ways:
+
+- By defining a ``__json__`` method on objects in your application.
+
+- For objects you don't "own", you can register JSON renderer that knows about
+ an *adapter* for that kind of object.
+
+Using a Custom ``__json__`` Method
+**********************************
+
Custom objects can be made easily JSON-serializable in Pyramid by defining a
``__json__`` method on the object's class. This method should return values
natively JSON-serializable (such as ints, lists, dictionaries, strings, and
@@ -255,6 +267,9 @@ will be the active request object at render time.
# the JSON value returned by ``objects`` will be:
# [{"x": 1}, {"x": 2}]
+Using the ``add_adapter`` Method of a Custom JSON Renderer
+**********************************************************
+
If you aren't the author of the objects being serialized, it won't be
possible (or at least not reasonable) to add a custom ``__json__`` method
to their classes in order to influence serialization. If the object passed
@@ -269,19 +284,21 @@ objects using the registered adapters. A short example follows:
from pyramid.renderers import JSON
- json_renderer = JSON()
- def datetime_adapter(obj, request):
- return obj.isoformat()
- json_renderer.add_adapter(datetime.datetime, datetime_adapter)
+ if __name__ == '__main__':
+ config = Configurator()
+ json_renderer = JSON()
+ def datetime_adapter(obj, request):
+ return obj.isoformat()
+ json_renderer.add_adapter(datetime.datetime, datetime_adapter)
+ config.add_renderer('json', json_renderer)
- # then during configuration ....
- config = Configurator()
- config.add_renderer('json', json_renderer)
+The ``add_adapter`` method should accept two arguments: the *class* of the object that you want this adapter to run for (in the example above,
+``datetime.datetime``), and the adapter itself.
-The adapter should accept two arguments: the object needing to be serialized
-and ``request``, which will be the current request object at render time.
-The adapter should raise a :exc:`TypeError` if it can't determine what to do
-with the object.
+The adapter should be a callable. It should accept two arguments: the object
+needing to be serialized and ``request``, which will be the current request
+object at render time. The adapter should raise a :exc:`TypeError`
+if it can't determine what to do with the object.
See :class:`pyramid.renderers.JSON` and
:ref:`adding_and_overriding_renderers` for more information.
@@ -366,136 +383,6 @@ renderer in :ref:`json_serializing_custom_objects` can be used when passing
values to a JSONP renderer too.
.. index::
- pair: renderer; chameleon
-
-.. _chameleon_template_renderers:
-
-``*.pt`` or ``*.txt``: Chameleon Template Renderers
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Two built-in renderers exist for :term:`Chameleon` templates.
-
-If the ``renderer`` attribute of a view configuration is an absolute path, a
-relative path or :term:`asset specification` which has a final path element
-with a filename extension of ``.pt``, the Chameleon ZPT renderer is used.
-See :ref:`chameleon_zpt_templates` for more information about ZPT templates.
-
-If the ``renderer`` attribute of a view configuration is an absolute path or
-a :term:`asset specification` which has a final path element with a filename
-extension of ``.txt``, the :term:`Chameleon` text renderer is used. See
-:ref:`chameleon_text_templates` for more information about Chameleon text
-templates.
-
-The behavior of these renderers is the same, except for the engine
-used to render the template.
-
-When a ``renderer`` attribute that names a template path or :term:`asset
-specification` (e.g. ``myproject:templates/foo.pt`` or
-``myproject:templates/foo.txt``) is used, the view must return a
-:term:`Response` object or a Python *dictionary*. If the view callable with
-an associated template returns a Python dictionary, the named template will
-be passed the dictionary as its keyword arguments, and the template renderer
-implementation will return the resulting rendered template in a response to
-the user. If the view callable returns anything but a Response object or a
-dictionary, an error will be raised.
-
-Before passing keywords to the template, the keyword arguments derived from
-the dictionary returned by the view are augmented. The callable object --
-whatever object was used to define the view -- will be automatically inserted
-into the set of keyword arguments passed to the template as the ``view``
-keyword. If the view callable was a class, the ``view`` keyword will be an
-instance of that class. Also inserted into the keywords passed to the
-template are ``renderer_name`` (the string used in the ``renderer`` attribute
-of the directive), ``renderer_info`` (an object containing renderer-related
-information), ``context`` (the context resource of the view used to render
-the template), and ``request`` (the request passed to the view used to render
-the template). ``request`` is also available as ``req`` in Pyramid 1.3+.
-
-Here's an example view configuration which uses a Chameleon ZPT renderer:
-
-.. code-block:: python
- :linenos:
-
- # config is an instance of pyramid.config.Configurator
-
- config.add_view('myproject.views.hello_world',
- name='hello',
- context='myproject.resources.Hello',
- renderer='myproject:templates/foo.pt')
-
-Here's an example view configuration which uses a Chameleon text renderer:
-
-.. code-block:: python
- :linenos:
-
- config.add_view('myproject.views.hello_world',
- name='hello',
- context='myproject.resources.Hello',
- renderer='myproject:templates/foo.txt')
-
-Views which use a Chameleon renderer can vary response attributes by using
-the API of the ``request.response`` attribute. See
-:ref:`request_response_attr`.
-
-.. index::
- pair: renderer; mako
-
-.. _mako_template_renderers:
-
-``*.mak`` or ``*.mako``: Mako Template Renderer
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The ``Mako`` template renderer renders views using a Mako template. When
-used, the view must return a Response object or a Python *dictionary*. The
-dictionary items will then be used in the global template space. If the view
-callable returns anything but a Response object or a dictionary, an error
-will be raised.
-
-When using a ``renderer`` argument to a :term:`view configuration` to specify
-a Mako template, the value of the ``renderer`` may be a path relative to the
-``mako.directories`` setting (e.g. ``some/template.mak``) or, alternately,
-it may be a :term:`asset specification`
-(e.g. ``apackage:templates/sometemplate.mak``). Mako templates may
-internally inherit other Mako templates using a relative filename or a
-:term:`asset specification` as desired.
-
-Here's an example view configuration which uses a relative path:
-
-.. code-block:: python
- :linenos:
-
- # config is an instance of pyramid.config.Configurator
-
- config.add_view('myproject.views.hello_world',
- name='hello',
- context='myproject.resources.Hello',
- renderer='foo.mak')
-
-It's important to note that in Mako's case, the 'relative' path name
-``foo.mak`` above is not relative to the package, but is relative to the
-directory (or directories) configured for Mako via the ``mako.directories``
-configuration file setting.
-
-The renderer can also be provided in :term:`asset specification`
-format. Here's an example view configuration which uses one:
-
-.. code-block:: python
- :linenos:
-
- config.add_view('myproject.views.hello_world',
- name='hello',
- context='myproject.resources.Hello',
- renderer='mypackage:templates/foo.mak')
-
-The above configuration will use the file named ``foo.mak`` in the
-``templates`` directory of the ``mypackage`` package.
-
-The ``Mako`` template renderer can take additional arguments beyond the
-standard ``pyramid.reload_templates`` setting, see the
-:ref:`environment_chapter` for additional
-:ref:`mako_template_renderer_settings`.
-
-.. index::
single: response headers (from a renderer)
single: renderer response headers
@@ -556,40 +443,6 @@ For more information on attributes of the request, see the API documentation
in :ref:`request_module`. For more information on the API of
``request.response``, see :attr:`pyramid.request.Request.response`.
-.. _response_prefixed_attrs:
-
-Deprecated Mechanism to Vary Attributes of Rendered Responses
--------------------------------------------------------------
-
-In previous releases of Pyramid (1.0 and before), the ``request.response``
-attribute did not exist. Instead, Pyramid required users to set special
-``response_`` -prefixed attributes of the request to influence response
-behavior. As of Pyramid 1.1, those request attributes are deprecated and
-their use will cause a deprecation warning to be issued when used. Until
-their existence is removed completely, we document them below, for benefit of
-people with older code bases.
-
-``response_content_type``
- Defines the content-type of the resulting response,
- e.g. ``text/xml``.
-
-``response_headerlist``
- A sequence of tuples describing header values that should be set in the
- response, e.g. ``[('Set-Cookie', 'abc=123'), ('X-My-Header', 'foo')]``.
-
-``response_status``
- A WSGI-style status code (e.g. ``200 OK``) describing the status of the
- response.
-
-``response_charset``
- The character set (e.g. ``UTF-8``) of the response.
-
-``response_cache_for``
- A value in seconds which will influence ``Cache-Control`` and ``Expires``
- headers in the returned response. The same can also be achieved by
- returning various values in the ``response_headerlist``, this is purely a
- convenience.
-
.. _adding_and_overriding_renderers:
Adding and Changing Renderers
@@ -739,44 +592,37 @@ ending with ``.jinja2`` in its ``renderer`` value. The ``name`` passed
to the ``MyJinja2Renderer`` constructor will be the full value that was
set as ``renderer=`` in the view configuration.
-.. index::
- pair: renderer; changing
-
-Changing an Existing Renderer
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Adding a Default Renderer
+~~~~~~~~~~~~~~~~~~~~~~~~~
-You can associate more than one filename extension with the same existing
-renderer implementation as necessary if you need to use a different file
-extension for the same kinds of templates. For example, to associate the
-``.zpt`` extension with the Chameleon ZPT renderer factory, use the
-:meth:`pyramid.config.Configurator.add_renderer` method:
+To associate a *default* renderer with *all* view configurations (even
+ones which do not possess a ``renderer`` attribute), pass ``None`` as
+the ``name`` attribute to the renderer tag:
.. code-block:: python
- config.add_renderer('.zpt', 'pyramid.chameleon_zpt.renderer_factory')
-
-After you do this, :app:`Pyramid` will treat templates ending in both the
-``.pt`` and ``.zpt`` filename extensions as Chameleon ZPT templates.
-
-To change the default mapping in which files with a ``.pt`` extension are
-rendered via a Chameleon ZPT page template renderer, use a variation on the
-following in your application's startup code:
-
-.. code-block:: python
+ config.add_renderer(None, 'mypackage.json_renderer_factory')
- config.add_renderer('.pt', 'mypackage.pt_renderer')
+.. index::
+ pair: renderer; changing
-After you do this, the :term:`renderer factory` in
-``mypackage.pt_renderer`` will be used to render templates which end
-in ``.pt``, replacing the default Chameleon ZPT renderer.
+Changing an Existing Renderer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-To associate a *default* renderer with *all* view configurations (even
-ones which do not possess a ``renderer`` attribute), pass ``None`` as
-the ``name`` attribute to the renderer tag:
+Pyramid supports overriding almost every aspect of its setup through its
+:ref:`Conflict Resolution <automatic_conflict_resolution>` mechanism. This
+means that in most cases overriding a renderer is as simple as using the
+:meth:`pyramid.config.Configurator.add_renderer` method to re-define the
+template extension. For example, if you would like to override the ``.txt``
+extension to specify a new renderer you could do the following:
.. code-block:: python
- config.add_renderer(None, 'mypackage.json_renderer_factory')
+ json_renderer = pyramid.renderers.JSON()
+ config.add_renderer('json', json_renderer)
+
+After doing this, any views registered with the ``json`` renderer will use
+the new renderer.
.. index::
pair: renderer; overriding at runtime
diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst
index 358977089..649d22bd2 100644
--- a/docs/narr/sessions.rst
+++ b/docs/narr/sessions.rst
@@ -99,6 +99,11 @@ example:
else:
return Response('Fred was not in the session')
+The first time this view is invoked produces ``Fred was not in the
+session``. Subsequent invocations produce ``Fred was in the
+session``, assuming of course that the client side maintains the
+session's identity across multiple requests.
+
You can use a session much like a Python dictionary. It supports all
dictionary methods, along with some extra attributes, and methods.
@@ -146,8 +151,6 @@ Some gotchas:
you've changed sessioning data.
.. index::
- single: pyramid_beaker
- single: Beaker
single: pyramid_redis_sessions
single: session factory (alternates)
@@ -156,19 +159,10 @@ Some gotchas:
Using Alternate Session Factories
---------------------------------
-At the time of this writing, exactly two alternate session factories
-exist.
-
-The first is named ``pyramid_redis_sessions``. It can be downloaded from PyPI.
-It uses Redis as a backend. It is the recommended persistent session solution
-at the time of this writing.
-
-The second is named ``pyramid_beaker``. This is a session factory that uses the
-`Beaker <http://beaker.groovie.org/>`_ library as a backend. Beaker has
-support for file-based sessions, database based sessions, and encrypted
-cookie-based sessions. See `the pyramid_beaker documentation
-<http://docs.pylonsproject.org/projects/pyramid_beaker/en/latest/>`_ for more
-information about ``pyramid_beaker``.
+At the time of this writing, exactly one project-endorsed alternate session
+factory exists named :term:`pyramid_redis_sessions`. It can be downloaded from
+PyPI. It uses the Redis database as a backend. It is the recommended
+persistent session solution at the time of this writing.
.. index::
single: session factory (custom)
diff --git a/docs/narr/subrequest.rst b/docs/narr/subrequest.rst
index 6437bd0fa..4b4e99d41 100644
--- a/docs/narr/subrequest.rst
+++ b/docs/narr/subrequest.rst
@@ -232,12 +232,12 @@ unconditionally:
- Ensures that the user implied by the request passed has the necessary
authorization to invoke view callable before calling it.
-- causes a :class:`~pyramid.events.NewResponse` event to be sent when the
- Pyramid application returns a response.
-
- Calls any :term:`response callback` functions defined within the subrequest's
lifetime if a response is obtained from the Pyramid application.
+- causes a :class:`~pyramid.events.NewResponse` event to be sent if a response
+ is obtained.
+
- Calls any :term:`finished callback` functions defined within the subrequest's
lifetime.
diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst
index 26bb6b6cd..3e19f7198 100644
--- a/docs/narr/templates.rst
+++ b/docs/narr/templates.rst
@@ -8,10 +8,6 @@ dynamic data provided by a :term:`view`. :app:`Pyramid` offers a
number of ways to perform templating tasks out of the box, and
provides add-on templating support through a set of bindings packages.
-Out of the box, :app:`Pyramid` provides templating via the :term:`Chameleon`
-and :term:`Mako` templating libraries. :term:`Chameleon` provides support for
-two different types of templates: :term:`ZPT` templates, and text templates.
-
Before discussing how built-in templates are used in
detail, we'll discuss two ways to render templates within
:app:`Pyramid` in general: directly, and via renderer
@@ -32,7 +28,7 @@ given templating engine to do so.
:app:`Pyramid` provides various APIs that allow you to render templates
directly from within a view callable. For example, if there is a
-:term:`Chameleon` ZPT template named ``foo.pt`` in a directory named
+:term:`Chameleon` ZPT template named ``foo.pt`` in a directory named
``templates`` in your application, you can render the template from
within the body of a view callable like so:
@@ -60,19 +56,8 @@ In this case, this is the directory containing the file that
defines the ``sample_view`` function. Although a renderer path is
usually just a simple relative pathname, a path named as a renderer
can be absolute, starting with a slash on UNIX or a drive letter
-prefix on Windows.
-
-.. warning::
-
- Only :term:`Chameleon` templates support defining a renderer for a
- template relative to the location of the module where the view callable is
- defined. Mako templates, and other templating system bindings work
- differently. In particular, Mako templates use a "lookup path" as defined
- by the ``mako.directories`` configuration file instead of treating
- relative paths as relative to the current view module. See
- :ref:`mako_templates`.
-
-The path can alternately be a :term:`asset specification` in the form
+prefix on Windows. The path can alternately be a
+:term:`asset specification` in the form
``some.dotted.package_name:relative/path``. This makes it possible to
address template assets which live in another package. For example:
@@ -90,16 +75,9 @@ An asset specification points at a file within a Python *package*.
In this case, it points at a file named ``foo.pt`` within the
``templates`` directory of the ``mypackage`` package. Using a
asset specification instead of a relative template name is usually
-a good idea, because calls to ``render_to_response`` using asset
-specifications will continue to work properly if you move the code
-containing them around.
-
-.. note::
-
- Mako templating system bindings also respect absolute asset
- specifications as an argument to any of the ``render*`` commands. If a
- template name defines a ``:`` (colon) character and is not an absolute
- path, it is treated as an absolute asset specification.
+a good idea, because calls to :func:`~pyramid.renderers.render_to_response`
+using asset specifications will continue to work properly if you move the
+code containing them around.
In the examples above we pass in a keyword argument named ``request``
representing the current :app:`Pyramid` request. Passing a request
@@ -147,8 +125,8 @@ import its API functions into your views module, use those APIs to generate a
string, then return that string as the body of a :app:`Pyramid`
:term:`Response` object.
-For example, here's an example of using "raw" `Mako
-<http://www.makotemplates.org/>`_ from within a :app:`Pyramid` :term:`view`:
+For example, here's an example of using "raw" Mako_ from within a
+:app:`Pyramid` :term:`view`:
.. code-block:: python
:linenos:
@@ -163,10 +141,10 @@ For example, here's an example of using "raw" `Mako
return response
You probably wouldn't use this particular snippet in a project, because it's
-easier to use the Mako renderer bindings which already exist in
-:app:`Pyramid`. But if your favorite templating system is not supported as a
-renderer extension for :app:`Pyramid`, you can create your own simple
-combination as shown above.
+easier to use the supported
+:ref:`Mako bindings <available_template_system_bindings>`. But if your
+favorite templating system is not supported as a renderer extension for
+:app:`Pyramid`, you can create your own simple combination as shown above.
.. note::
@@ -281,8 +259,8 @@ You can define more values which will be passed to every template executed as
a result of rendering by defining :term:`renderer globals`.
What any particular renderer does with these system values is up to the
-renderer itself, but most template renderers, including Chameleon and Mako
-renderers, make these names available as top-level template variables.
+renderer itself, but most template renderers make these names available as
+top-level template variables.
.. index::
pair: renderer; templates
@@ -322,7 +300,9 @@ template renderer:
def my_view(request):
return {'foo':1, 'bar':2}
-.. note:: You do not need to supply the ``request`` value as a key
+.. note::
+
+ You do not need to supply the ``request`` value as a key
in the dictionary result returned from a renderer-configured view
callable. :app:`Pyramid` automatically supplies this value for
you so that the "most correct" system values are provided to
@@ -350,11 +330,7 @@ it possible to address template assets which live in another package.
Not just any template from any arbitrary templating system may be used as a
renderer. Bindings must exist specifically for :app:`Pyramid` to use a
-templating language template as a renderer. Currently, :app:`Pyramid` has
-built-in support for two Chameleon templating languages: ZPT and text, and
-the Mako templating system. See :ref:`built_in_renderers` for a discussion
-of their details. :app:`Pyramid` also supports the use of :term:`Jinja2`
-templates as renderers. See :ref:`available_template_system_bindings`.
+templating language template as a renderer.
.. sidebar:: Why Use A Renderer via View Configuration
@@ -383,236 +359,6 @@ The same set of system values are provided to templates rendered via a
renderer view configuration as those provided to templates rendered
imperatively. See :ref:`renderer_system_values`.
-
-.. index::
- single: Chameleon ZPT templates
- single: ZPT templates (Chameleon)
-
-.. _chameleon_zpt_templates:
-
-:term:`Chameleon` ZPT Templates
--------------------------------
-
-Like :term:`Zope`, :app:`Pyramid` uses :term:`ZPT` (Zope Page
-Templates) as its default templating language. However,
-:app:`Pyramid` uses a different implementation of the :term:`ZPT`
-specification than Zope does: the :term:`Chameleon` templating
-engine. The Chameleon engine complies largely with the `Zope Page
-Template <http://wiki.zope.org/ZPT/FrontPage>`_ template
-specification. However, it is significantly faster.
-
-The language definition documentation for Chameleon ZPT-style
-templates is available from `the Chameleon website
-<http://chameleon.repoze.org/>`_.
-
-Given a :term:`Chameleon` ZPT template named ``foo.pt`` in a directory
-in your application named ``templates``, you can render the template as
-a :term:`renderer` like so:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.view import view_config
-
- @view_config(renderer='templates/foo.pt')
- def my_view(request):
- return {'foo':1, 'bar':2}
-
-See also :ref:`built_in_renderers` for more general information about
-renderers, including Chameleon ZPT renderers.
-
-.. index::
- single: ZPT template (sample)
-
-A Sample ZPT Template
-~~~~~~~~~~~~~~~~~~~~~
-
-Here's what a simple :term:`Chameleon` ZPT template used under
-:app:`Pyramid` might look like:
-
-.. code-block:: xml
- :linenos:
-
- <!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"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
- <head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <title>${project} Application</title>
- </head>
- <body>
- <h1 class="title">Welcome to <code>${project}</code>, an
- application generated by the <a
- href="http://docs.pylonsproject.org/projects/pyramid/current/"
- >pyramid</a> web
- application framework.</h1>
- </body>
- </html>
-
-Note the use of :term:`Genshi` -style ``${replacements}`` above. This
-is one of the ways that :term:`Chameleon` ZPT differs from standard
-ZPT. The above template expects to find a ``project`` key in the set
-of keywords passed in to it via :func:`~pyramid.renderers.render` or
-:func:`~pyramid.renderers.render_to_response`. Typical ZPT
-attribute-based syntax (e.g. ``tal:content`` and ``tal:replace``) also
-works in these templates.
-
-.. index::
- single: ZPT macros
- single: Chameleon ZPT macros
-
-Using ZPT Macros in :app:`Pyramid`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-When a :term:`renderer` is used to render a template, :app:`Pyramid` makes at
-least two top-level names available to the template by default: ``context``
-and ``request``. One of the common needs in ZPT-based templates is to use
-one template's "macros" from within a different template. In Zope, this is
-typically handled by retrieving the template from the ``context``. But the
-context in :app:`Pyramid` is a :term:`resource` object, and templates cannot
-usually be retrieved from resources. To use macros in :app:`Pyramid`, you
-need to make the macro template itself available to the rendered template by
-passing the macro template, or even the macro itself, *into* the rendered
-template. To do this you can use the :func:`pyramid.renderers.get_renderer`
-API to retrieve the macro template, and pass it into the template being
-rendered via the dictionary returned by the view. For example, using a
-:term:`view configuration` via a :class:`~pyramid.view.view_config` decorator
-that uses a :term:`renderer`:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.renderers import get_renderer
- from pyramid.view import view_config
-
- @view_config(renderer='templates/mytemplate.pt')
- def my_view(request):
- main = get_renderer('templates/master.pt').implementation()
- return {'main':main}
-
-Where ``templates/master.pt`` might look like so:
-
-.. code-block:: xml
- :linenos:
-
- <html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:tal="http://xml.zope.org/namespaces/tal"
- xmlns:metal="http://xml.zope.org/namespaces/metal">
- <span metal:define-macro="hello">
- <h1>
- Hello <span metal:define-slot="name">Fred</span>!
- </h1>
- </span>
- </html>
-
-And ``templates/mytemplate.pt`` might look like so:
-
-.. code-block:: xml
- :linenos:
-
- <html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:tal="http://xml.zope.org/namespaces/tal"
- xmlns:metal="http://xml.zope.org/namespaces/metal">
- <span metal:use-macro="main.macros['hello']">
- <span metal:fill-slot="name">Chris</span>
- </span>
- </html>
-
-
-Using A Chameleon Macro Name Within a Renderer Name
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-At times, you may want to render a macro inside of a Chameleon ZPT template
-instead of the full Chameleon ZPT template. To render the content of a
-``define-macro`` field inside a Chameleon ZPT template, given a Chameleon
-template file named ``foo.pt`` and a macro named ``bar`` defined within it
-(e.g. ``<div metal:define-macro="bar">...</div>``), you can configure the
-template as a :term:`renderer` like so:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.view import view_config
-
- @view_config(renderer='foo#bar.pt')
- def my_view(request):
- return {'project':'my project'}
-
-The above will render only the ``bar`` macro defined within the ``foo.pt``
-template instead of the entire template.
-
-.. versionadded:: 1.4
-
-.. index::
- single: Chameleon text templates
-
-.. _chameleon_text_templates:
-
-Templating with :term:`Chameleon` Text Templates
-------------------------------------------------
-
-:app:`Pyramid` also allows for the use of templates which are
-composed entirely of non-XML text via :term:`Chameleon`. To do so,
-you can create templates that are entirely composed of text except for
-``${name}`` -style substitution points.
-
-Here's an example usage of a Chameleon text template. Create a file
-on disk named ``mytemplate.txt`` in your project's ``templates``
-directory with the following contents:
-
-.. code-block:: text
-
- Hello, ${name}!
-
-Then in your project's ``views.py`` module, you can create a view
-which renders this template:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.view import view_config
-
- @view_config(renderer='templates/mytemplate.txt')
- def my_view(request):
- return {'name':'world'}
-
-When the template is rendered, it will show:
-
-.. code-block:: text
-
- Hello, world!
-
-See also :ref:`built_in_renderers` for more general information about
-renderers, including Chameleon text renderers.
-
-.. index::
- single: template renderer side effects
-
-Side Effects of Rendering a Chameleon Template
-----------------------------------------------
-
-When a Chameleon template is rendered from a file, the templating
-engine writes a file in the same directory as the template file itself
-as a kind of cache, in order to do less work the next time the
-template needs to be read from disk. If you see "strange" ``.py``
-files showing up in your ``templates`` directory (or otherwise
-directly "next" to your templates), it is due to this feature.
-
-If you're using a version control system such as Subversion, you
-should configure it to ignore these files. Here's the contents of the
-author's ``svn propedit svn:ignore .`` in each of my ``templates``
-directories.
-
-.. code-block:: text
-
- *.pt.py
- *.txt.py
-
-Note that I always name my Chameleon ZPT template files with a ``.pt``
-extension and my Chameleon text template files with a ``.txt``
-extension so that these ``svn:ignore`` patterns work.
-
.. index::
pair: debugging; templates
@@ -644,107 +390,6 @@ The output tells you which template the error occurred in, as well as
displaying the arguments passed to the template itself.
.. index::
- single: template internationalization
- single: internationalization (of templates)
-
-:term:`Chameleon` Template Internationalization
------------------------------------------------
-
-See :ref:`chameleon_translation_strings` for information about
-supporting internationalized units of text within :term:`Chameleon`
-templates.
-
-.. index::
- single: Mako
-
-.. _mako_templates:
-
-Templating With Mako Templates
-------------------------------
-
-:term:`Mako` is a templating system written by Mike Bayer. :app:`Pyramid`
-has built-in bindings for the Mako templating system. The language
-definition documentation for Mako templates is available from `the Mako
-website <http://www.makotemplates.org/>`_.
-
-To use a Mako template, given a :term:`Mako` template file named ``foo.mak``
-in the ``templates`` subdirectory in your application package named
-``mypackage``, you can configure the template as a :term:`renderer` like so:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.view import view_config
-
- @view_config(renderer='foo.mak')
- def my_view(request):
- return {'project':'my project'}
-
-For the above view callable to work, the following setting needs to be
-present in the application stanza of your configuration's ``ini`` file:
-
-.. code-block:: ini
-
- mako.directories = mypackage:templates
-
-This lets the Mako templating system know that it should look for templates
-in the ``templates`` subdirectory of the ``mypackage`` Python package. See
-:ref:`mako_template_renderer_settings` for more information about the
-``mako.directories`` setting and other Mako-related settings that can be
-placed into the application's ``ini`` file.
-
-.. index::
- single: Mako template (sample)
-
-A Sample Mako Template
-~~~~~~~~~~~~~~~~~~~~~~
-
-Here's what a simple :term:`Mako` template used under :app:`Pyramid` might
-look like:
-
-.. code-block:: xml
- :linenos:
-
- <html>
- <head>
- <title>${project} Application</title>
- </head>
- <body>
- <h1 class="title">Welcome to <code>${project}</code>, an
- application generated by the <a
- href="http://docs.pylonsproject.org/projects/pyramid/current/"
- >pyramid</a> web framework.</h1>
- </body>
- </html>
-
-This template doesn't use any advanced features of Mako, only the
-``${}`` replacement syntax for names that are passed in as
-:term:`renderer globals`. See the `the Mako documentation
-<http://www.makotemplates.org/>`_ to use more advanced features.
-
-Using A Mako def name Within a Renderer Name
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Sometimes you'd like to render a ``def`` inside of a Mako template instead of
-the full Mako template. To render a def inside a Mako template, given a
-:term:`Mako` template file named ``foo.mak`` and a def named ``bar``, you can
-configure the template as a :term:`renderer` like so:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.view import view_config
-
- @view_config(renderer='foo#bar.mak')
- def my_view(request):
- return {'project':'my project'}
-
-The above will render the ``bar`` def from within the ``foo.mak`` template
-instead of the entire template.
-
-.. versionadded:: 1.4
-
-.. index::
single: automatic reloading of templates
single: template automatic reload
@@ -759,9 +404,11 @@ appear immediately without needing to restart the application process.
environment so that a change to a template will be automatically
detected, and the template will be reloaded on the next rendering.
-.. warning:: Auto-template-reload behavior is not recommended for
- production sites as it slows rendering slightly; it's
- usually only desirable during development.
+.. warning::
+
+ Auto-template-reload behavior is not recommended for
+ production sites as it slows rendering slightly; it's
+ usually only desirable during development.
In order to turn on automatic reloading of templates, you can use an
environment variable, or a configuration file setting.
@@ -772,31 +419,48 @@ variable set to ``1``, For example:
.. code-block:: text
- $ PYRAMID_RELOAD_TEMPLATES=1 $VENV/bin/pserve myproject.ini
+ $ PYRAMID_RELOAD_TEMPLATES=1 $VENV/bin/pserve myproject.ini
To use a setting in the application ``.ini`` file for the same
purpose, set the ``pyramid.reload_templates`` key to ``true`` within the
application's configuration section, e.g.:
.. code-block:: ini
- :linenos:
+ :linenos:
- [app:main]
- use = egg:MyProject
- pyramid.reload_templates = true
+ [app:main]
+ use = egg:MyProject
+ pyramid.reload_templates = true
.. index::
single: template system bindings
+ single: Chameleon
single: Jinja2
+ single: Mako
.. _available_template_system_bindings:
Available Add-On Template System Bindings
-----------------------------------------
-Jinja2 template bindings are available for :app:`Pyramid` in the
-``pyramid_jinja2`` package. You can get the latest release of
-this package from the
-`Python package index <http://pypi.python.org/pypi/pyramid_jinja2>`_
-(pypi).
+The Pylons Project maintains several packages providing bindings to different
+templating languages including the following:
+
++------------------------------+------------------------------+
+| Template Language | Pyramid Bindings |
++==============================+==============================+
+| Chameleon_ | pyramid_chameleon_ |
++------------------------------+------------------------------+
+| Jinja2_ | pyramid_jinja2_ |
++------------------------------+------------------------------+
+| Mako_ | pyramid_mako_ |
++------------------------------+------------------------------+
+
+.. _Chameleon: http://chameleon.readthedocs.org/en/latest/
+.. _pyramid_chameleon: https://pypi.python.org/pypi/pyramid_chameleon
+
+.. _Jinja2: http://jinja.pocoo.org/docs/
+.. _pyramid_jinja2: https://pypi.python.org/pypi/pyramid_jinja2
+.. _Mako: http://www.makotemplates.org/
+.. _pyramid_mako: https://pypi.python.org/pypi/pyramid_mako
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index 62eb89348..61849c3c0 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -399,13 +399,6 @@ process. Examples of route predicate arguments are ``pattern``, ``xhr``, and
Other arguments are ``name`` and ``factory``. These arguments represent
neither predicates nor view configuration information.
-.. warning::
-
- Some arguments are view-configuration related arguments, such as
- ``view_renderer``. These only have an effect when the route configuration
- names a ``view`` and these arguments have been deprecated as of
- :app:`Pyramid` 1.1.
-
.. index::
single: route matching
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index e09fd83ab..7c76116f7 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -465,6 +465,36 @@ configured view.
.. versionadded:: 1.4a1
+Inverting Predicate Values
+++++++++++++++++++++++++++
+
+You can invert the meaning of any predicate value by wrapping it in a call to
+:class:`pyramid.config.not_`.
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import not_
+
+ config.add_view(
+ 'mypackage.views.my_view',
+ route_name='ok',
+ request_method=not_('POST')
+ )
+
+The above example will ensure that the view is called if the request method
+is *not* ``POST``, at least if no other view is more specific.
+
+This technique of wrapping a predicate value in ``not_`` can be used anywhere
+predicate values are accepted:
+
+- :meth:`pyramid.config.Configurator.add_view`
+
+- :meth:`pyramid.view.view_config`
+
+.. versionadded:: 1.5
+
+
.. index::
single: view_config decorator
@@ -557,35 +587,6 @@ form of :term:`declarative configuration`, while
:meth:`pyramid.config.Configurator.add_view` is a form of :term:`imperative
configuration`. However, they both do the same thing.
-Inverting Predicate Values
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can invert the meaning of any predicate value by wrapping it in a call to
-:class:`pyramid.config.not_`.
-
-.. code-block:: python
- :linenos:
-
- from pyramid.config import not_
-
- config.add_view(
- 'mypackage.views.my_view',
- route_name='ok',
- request_method=not_('POST')
- )
-
-The above example will ensure that the view is called if the request method
-is *not* ``POST``, at least if no other view is more specific.
-
-This technique of wrapping a predicate value in ``not_`` can be used anywhere
-predicate values are accepted:
-
-- :meth:`pyramid.config.Configurator.add_view`
-
-- :meth:`pyramid.view.view_config`
-
-.. versionadded:: 1.5
-
.. index::
single: view_config placement
@@ -821,7 +822,7 @@ of this:
def delete(self):
return Response('delete')
- if __name__ == '__main__':
+ def main(global_config, **settings):
config = Configurator()
config.add_route('rest', '/rest')
config.add_view(
@@ -830,9 +831,10 @@ of this:
RESTView, route_name='rest', attr='post', request_method='POST')
config.add_view(
RESTView, route_name='rest', attr='delete', request_method='DELETE')
+ return config.make_wsgi_app()
To reduce the amount of repetition in the ``config.add_view`` statements, we
-can move the ``route_name='rest'`` argument to a ``@view_default`` class
+can move the ``route_name='rest'`` argument to a ``@view_defaults`` class
decorator on the RESTView class:
.. code-block:: python
@@ -856,12 +858,13 @@ decorator on the RESTView class:
def delete(self):
return Response('delete')
- if __name__ == '__main__':
+ def main(global_config, **settings):
config = Configurator()
config.add_route('rest', '/rest')
config.add_view(RESTView, attr='get', request_method='GET')
config.add_view(RESTView, attr='post', request_method='POST')
config.add_view(RESTView, attr='delete', request_method='DELETE')
+ return config.make_wsgi_app()
:class:`pyramid.view.view_defaults` accepts the same set of arguments that
:class:`pyramid.view.view_config` does, and they have the same meaning. Each
diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst
index e55730a0d..2db18c8a7 100644
--- a/docs/quick_tour.rst
+++ b/docs/quick_tour.rst
@@ -4,73 +4,56 @@
Quick Tour of Pyramid
=====================
-Pyramid lets you start small and finish big. This *Quick Tour* guide
-walks you through many of Pyramid's key features. Let's put the
-emphasis on *start* by doing a quick tour through Pyramid, with
-snippets of code to illustrate major concepts.
+Pyramid lets you start small and finish big. This *Quick Tour* of Pyramid is
+for those who want to evaluate Pyramid, whether you are new to Python
+web frameworks, or a pro in a hurry. For more detailed treatment of
+each topic, give the :ref:`quick_tutorial` a try.
-.. note::
-
- We use Python 3 in our samples. Pyramid was one of the first
- (October 2011) web frameworks to fully support Python 3. You can
- use Python 3 as well for this guide, but you can also use Python 2.7.
-
-Python Setup
+Installation
============
-First things first: we need our Python environment in ship-shape.
-Pyramid encourages standard Python development practices (virtual
-environments, packaging tools, logging, etc.) so let's get our working
-area in place. For Python 3.3:
-
-.. code-block:: bash
-
- $ pyvenv-3.3 env33
- $ source env33/bin/activate
- $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | python
-
-If ``wget`` complains with a certificate error, run it with:
-
-.. code-block:: bash
-
- $ wget --no-check-certificate
+Once you have a standard Python environment setup, getting started with
+Pyramid is a breeze. Unfortunately "standard" is not so simple in Python.
+For this Quick Tour, it means:
+`Python <http://www.python.org/download/releases/>`_, a
+`virtual environment <http://docs.python.org/dev/library/venv.html>`_
+(or `virtualenv for Python 2.7 <https://pypi.python.org/pypi/virtualenv>`_),
+and `setuptools <https://pypi.python.org/pypi/setuptools/>`_.
-In these steps above we first made a :term:`virtualenv` and then
-"activated" it, which adjusted our path to look first in
-``env33/bin`` for commands (such as ``python``). We next downloaded
-Python's packaging support and installed it, giving us the
-``easy_install`` command-line script for adding new packages. Python
-2.7 users will need to use ``virtualenv`` instead of ``pyvenv`` to make
-their virtual environment.
+As an example, for Python 3.3+ on Linux:
-.. note::
+.. parsed-literal::
- Why ``easy_install`` and not ``pip``? Pyramid encourages use of
- namespace packages which, until recently, ``pip`` didn't permit.
- Also, Pyramid has some optional C extensions for performance. With
- ``easy_install``, Windows users can get these extensions without
- needing a C compiler.
+ $ pyvenv env33
+ $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | env33/bin/python
+ $ env33/bin/easy_install "pyramid==\ |release|\ "
-.. seealso:: See Also: Python 3's :mod:`venv module <python3:venv>`,
- the ``setuptools`` `installation
- instructions <https://pypi.python.org/pypi/setuptools/0.9.8#installation-instructions>`_,
- `easy_install help <https://pypi.python.org/pypi/setuptools/0.9.8#using-setuptools-and-easyinstall>`_,
- and Pyramid's :ref:`Before You Install <installing_chapter>`.
+For Windows:
-Pyramid Installation
-====================
+.. parsed-literal::
-We now have a standard starting point for Python. Getting Pyramid
-installed is easy:
+ # Use your browser to download:
+ # https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py
+ c:\\> c:\\Python33\\python -m venv env33
+ c:\\> env33\\Scripts\\python ez_setup.py
+ c:\\> env33\\Scripts\\easy_install "pyramid==\ |release|\ "
-.. code-block:: bash
+Of course Pyramid runs fine on Python 2.6+, as do the examples in this
+*Quick Tour*. We're just showing Python 3 a little love (Pyramid had
+production support in October 2011.)
- $ easy_install pyramid
+.. note::
-Our virtual environment now has the Pyramid software available to its
-Python.
+ Why ``easy_install`` and not ``pip``? Pyramid encourages use of namespace
+ packages which, until recently, ``pip`` didn't permit. Also, Pyramid has
+ some optional C extensions for performance. With ``easy_install``, Windows
+ users can get these extensions without needing a C compiler.
-.. seealso:: See Also: :ref:`installing_unix`
+.. seealso:: See Also:
+ :ref:`Quick Tutorial section on Requirements <qtut_requirements>`,
+ :ref:`installing_unix`,
+ :ref:`Before You Install <installing_chapter>`, and
+ :ref:`Installing Pyramid on a Windows System <installing_windows>`
Hello World
===========
@@ -109,7 +92,9 @@ in Pyramid development. Building an application from loosely-coupled
parts via :doc:`../narr/configuration` is a central idea in Pyramid,
one that we will revisit regurlarly in this *Quick Tour*.
-.. seealso:: See Also: :ref:`firstapp_chapter` and
+.. seealso:: See Also:
+ :ref:`Quick Tutorial Hello World <qtut_hello_world>`,
+ :ref:`firstapp_chapter`, and
:ref:`Single File Tasks tutorial <tutorials:single-file-tutorial>`
Handling Web Requests and Responses
@@ -140,7 +125,10 @@ the name is included in the body of the response::
Finally, we set the response's content type and return the Response.
-.. seealso:: See Also: :ref:`webob_chapter`
+.. seealso:: See Also:
+ :ref:`Quick Tutorial Request and Response <qtut_request_response>`
+ and
+ :ref:`webob_chapter`
Views
=====
@@ -190,7 +178,9 @@ configuration`, in which a Python :term:`decorator` is placed on the
line above the view. Both approaches result in the same final
configuration, thus usually, it is simply a matter of taste.
-.. seealso:: See Also: :doc:`../narr/views`,
+.. seealso:: See Also:
+ :ref:`Quick Tutorial Views <qtut_views>`,
+ :doc:`../narr/views`,
:doc:`../narr/viewconfig`, and
:ref:`debugging_view_configuration`
@@ -236,7 +226,9 @@ view:
"replacement patterns" (the curly braces) in the route declaration.
This information can then be used in your view.
-.. seealso:: See Also: :doc:`../narr/urldispatch`,
+.. seealso:: See Also:
+ :ref:`Quick Tutorial Routing <qtut_routing>`,
+ :doc:`../narr/urldispatch`,
:ref:`debug_routematch_section`, and
:doc:`../narr/router`
@@ -250,8 +242,9 @@ Python, but instead, will use a templating language.
Pyramid doesn't mandate a particular database system, form library,
etc. It encourages replaceability. This applies equally to templating,
which is fortunate: developers have strong views about template
-languages. That said, Pyramid bundles Chameleon and Mako,
-so in this step, let's use Chameleon as an example:
+languages. That said, the Pylons Project officially supports bindings for
+Chameleon, Jinja2 and Mako, so in this step, let's use Chameleon as an
+example:
.. literalinclude:: quick_tour/templating/views.py
:start-after: Start View 1
@@ -269,9 +262,11 @@ Since our view returned ``dict(name=request.matchdict['name'])``,
we can use ``name`` as a variable in our template via
``${name}``.
-.. seealso:: See Also: :doc:`../narr/templates`,
- :ref:`debugging_templates`, and
- :ref:`mako_templates`
+.. seealso:: See Also:
+ :ref:`Quick Tutorial Templating <qtut_templating>`,
+ :doc:`../narr/templates`,
+ :ref:`debugging_templates`, and
+ :ref:`available_template_system_bindings`
Templating With ``jinja2``
==========================
@@ -302,7 +297,7 @@ The only change in our view...point the renderer at the ``.jinja2`` file:
Our Jinja2 template is very similar to our previous template:
.. literalinclude:: quick_tour/jinja2/hello_world.jinja2
- :language: jinja
+ :language: html
Pyramid's templating add-ons register a new kind of renderer into your
application. The renderer registration maps to different kinds of
@@ -310,9 +305,10 @@ filename extensions. In this case, changing the extension from ``.pt``
to ``.jinja2`` passed the view response through the ``pyramid_jinja2``
renderer.
-.. seealso:: See Also: `Jinja2 homepage <http://jinja.pocoo.org/>`_,
- and
- :ref:`pyramid_jinja2 Overview <jinja2:overview>`
+.. seealso:: See Also:
+ :ref:`Quick Tutorial Jinja2 <qtut_jinja2>`,
+ `Jinja2 homepage <http://jinja.pocoo.org/>`_, and
+ :ref:`pyramid_jinja2 Overview <jinja2:overview>`
Static Assets
=============
@@ -357,9 +353,11 @@ By using ``request.static_url`` to generate the full URL to the static
assets, you both ensure you stay in sync with the configuration and
gain refactoring flexibility later.
-.. seealso:: See Also: :doc:`../narr/assets`,
- :ref:`preventing_http_caching`, and
- :ref:`influencing_http_caching`
+.. seealso:: See Also:
+ :ref:`Quick Tutorial Static Assets <qtut_static_assets>`,
+ :doc:`../narr/assets`,
+ :ref:`preventing_http_caching`, and
+ :ref:`influencing_http_caching`
Returning JSON
==============
@@ -376,9 +374,11 @@ This wires up a view that returns some data through the JSON
:term:`renderer`, which calls Python's JSON support to serialize the data
into JSON and set the appropriate HTTP headers.
-.. seealso:: See Also: :ref:`views_which_use_a_renderer`,
- :ref:`json_renderer`, and
- :ref:`adding_and_overriding_renderers`
+.. seealso:: See Also:
+ :ref:`Quick Tutorial JSON <qtut_json>`,
+ :ref:`views_which_use_a_renderer`,
+ :ref:`json_renderer`, and
+ :ref:`adding_and_overriding_renderers`
View Classes
============
@@ -421,7 +421,20 @@ Only one route needed, stated in one place atop the view class. Also,
the assignment of the ``name`` is done in the ``__init__``. Our
templates can then use ``{{ view.name }}``.
-.. seealso:: See Also: :ref:`class_as_view`
+Pyramid view classes, combined with built-in and custom predicates,
+have much more to offer:
+
+- All the same view configuration parameters as function views
+
+- One route leading to multiple views, based on information in the
+ request or data such as ``request_param``, ``request_method``,
+ ``accept``, ``header``, ``xhr``, ``containment``, and
+ ``custom_predicates``
+
+.. seealso:: See Also:
+ :ref:`Quick Tutorial View Classes <qtut_view_classes>`,
+ :ref:`Quick Tutorial More View Classes <qtut_more_view_classes>`, and
+ :ref:`class_as_view`
Quick Project Startup with Scaffolds
====================================
@@ -469,8 +482,10 @@ configuration. This includes a new way of running your application:
Let's look at ``pserve`` and configuration in more depth.
-.. seealso:: See Also: :ref:`project_narr` and
- :doc:`../narr/scaffolding`
+.. seealso:: See Also:
+ :ref:`Quick Tutorial Scaffolds <qtut_scaffolds>`,
+ :ref:`project_narr`, and
+ :doc:`../narr/scaffolding`
Application Running with ``pserve``
===================================
@@ -498,7 +513,8 @@ Most of the work, though, comes from your project's wiring, as
expressed in the configuration file you supply to ``pserve``. Let's
take a look at this configuration file.
-.. seealso:: See Also: :ref:`what_is_this_pserve_thing`
+.. seealso:: See Also:
+ :ref:`what_is_this_pserve_thing`
Configuration with ``.ini`` Files
=================================
@@ -545,8 +561,10 @@ Additionally, the ``development.ini`` generated by this scaffold 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 traceback information.
-.. seealso:: See Also: :ref:`environment_chapter` and
- :doc:`../narr/paste`
+.. seealso:: See Also:
+ :ref:`Quick Tutorial Application Configuration <qtut_ini>`,
+ :ref:`environment_chapter` and
+ :doc:`../narr/paste`
Easier Development with ``debugtoolbar``
@@ -598,7 +616,10 @@ you want to disable this toolbar, no need to change code: you can
remove it from ``pyramid.includes`` in the relevant ``.ini``
configuration file.
-.. seealso:: See Also: :ref:`pyramid_debugtoolbar <toolbar:overview>`
+.. seealso:: See Also:
+ :ref:`Quick Tutorial
+ pyramid_debugtoolbar <qtut_debugtoolbar>` and
+ :ref:`pyramid_debugtoolbar <toolbar:overview>`
Unit Tests and ``nose``
=======================
@@ -649,7 +670,11 @@ 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.
-.. seealso:: See Also: :ref:`testing_chapter`
+.. seealso:: See Also:
+ :ref:`Quick Tutorial Unit Testing <qtut_unit_testing>`,
+ :ref:`Quick Tutorial Functional Testing <qtut_functional_testing>`,
+ and
+ :ref:`testing_chapter`
Logging
=======
@@ -692,7 +717,9 @@ visit ``http://localhost:6543`` your console will now show::
2013-08-09 10:42:42,968 DEBUG [hello_world.views][MainThread] Some Message
-.. seealso:: See Also: :ref:`logging_chapter`
+.. seealso:: See Also:
+ :ref:`Quick Tutorial Logging <qtut_logging>` and
+ :ref:`logging_chapter`
Sessions
========
@@ -702,8 +729,8 @@ that requires semi-permanent data to be saved. For example, a shopping
cart. This is called a :term:`session`.
Pyramid has basic built-in support for sessions, with add-ons such as
-*Beaker* (or your own custom sessioning engine) that provide richer
-session support. Let's take a look at the
+``pyramid_redis_sessions`` (or your own custom sessioning engine) that provide
+richer session support. 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:
@@ -739,9 +766,9 @@ Jinja2 template:
:end-before: End Sphinx Include 1
.. seealso:: See Also:
- :ref:`sessions_chapter`, :ref:`flash_messages`,
- :ref:`session_module`, and
- :ref:`Beaker sessioning middleware <beaker:overview>`
+ :ref:`Quick Tutorial Sessions <qtut_sessions>`,
+ :ref:`sessions_chapter`, :ref:`flash_messages`,
+ :ref:`session_module`, and :term:`pyramid_redis_sessions`.
Databases
=========
@@ -786,10 +813,12 @@ of the system, can then easily get at the data thanks to SQLAlchemy:
:start-after: Start Sphinx Include
:end-before: End Sphinx Include
-.. seealso:: See Also: `SQLAlchemy <http://www.sqlalchemy.org/>`_,
- :ref:`making_a_console_script`,
- :ref:`bfg_sql_wiki_tutorial`, and
- :ref:`Application Transactions With pyramid_tm <tm:overview>`
+.. seealso:: See Also:
+ :ref:`Quick Tutorial Databases <qtut_databases>`,
+ `SQLAlchemy <http://www.sqlalchemy.org/>`_,
+ :ref:`making_a_console_script`,
+ :ref:`bfg_sql_wiki_tutorial`, and
+ :ref:`Application Transactions With pyramid_tm <tm:overview>`
Forms
=====
@@ -848,9 +877,10 @@ widgets using attractive CSS from Bootstrap and more powerful widgets
from Chosen.
.. seealso:: See Also:
- :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>`,
+ :ref:`Colander <colander:overview>`, and
+ `deform_bootstrap <https://pypi.python.org/pypi/deform_bootstrap>`_
Conclusion
==========
diff --git a/docs/quick_tour/jinja2/hello_world.jinja2 b/docs/quick_tour/jinja2/hello_world.jinja2
index e177744b5..7a902dd3a 100644
--- a/docs/quick_tour/jinja2/hello_world.jinja2
+++ b/docs/quick_tour/jinja2/hello_world.jinja2
@@ -1,4 +1,5 @@
-<html xmlns="http://www.w3.org/1999/xhtml">
+<!DOCTYPE html>
+<html lang="en">
<head>
<title>Hello World</title>
</head>
diff --git a/docs/quick_tutorial/authentication.rst b/docs/quick_tutorial/authentication.rst
new file mode 100644
index 000000000..8380a75ed
--- /dev/null
+++ b/docs/quick_tutorial/authentication.rst
@@ -0,0 +1,134 @@
+==============================
+20: Logins With Authentication
+==============================
+
+Login views that authenticate a username/password against a list of
+users.
+
+Background
+==========
+
+Most web applications have URLs that allow people to add/edit/delete
+content via a web browser. Time to add
+:ref:`security <security_chapter>`
+to the application. In this first step we introduce authentication.
+That is, logging in and logging out using Pyramid's rich facilities for
+pluggable user storages.
+
+In the next step we will introduce protection resources with
+authorization security statements.
+
+Objectives
+==========
+
+- Introduce the Pyramid concepts of authentication
+
+- Create login/logout views
+
+Steps
+=====
+
+#. We are going to use the view classes step as our starting point:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r view_classes authentication; cd authentication
+ $ $VENV/bin/python setup.py develop
+
+#. Put the security hash in the ``authentication/development.ini``
+ configuration file as ``tutorial.secret`` instead of putting it in
+ the code:
+
+ .. literalinclude:: authentication/development.ini
+ :language: ini
+ :linenos:
+
+#. Get authentication (and for now, authorization policies) and login
+ route into the :term:`configurator` in
+ ``authentication/tutorial/__init__.py``:
+
+ .. literalinclude:: authentication/tutorial/__init__.py
+ :linenos:
+
+#. Create a ``authentication/tutorial/security.py`` module that can find
+ our user information by providing an *authentication policy callback*:
+
+ .. literalinclude:: authentication/tutorial/security.py
+ :linenos:
+
+#. Update the views in ``authentication/tutorial/views.py``:
+
+ .. literalinclude:: authentication/tutorial/views.py
+ :linenos:
+
+#. Add a login template at ``authentication/tutorial/login.pt``:
+
+ .. literalinclude:: authentication/tutorial/login.pt
+ :language: html
+ :linenos:
+
+#. Provide a login/logout box in ``authentication/tutorial/home.pt``
+
+ .. literalinclude:: authentication/tutorial/home.pt
+ :language: html
+ :linenos:
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ in a browser.
+
+#. Click the "Log In" link.
+
+#. Submit the login form with the username ``editor`` and the password
+ ``editor``.
+
+#. Note that the "Log In" link has changed to "Logout".
+
+#. Click the "Logout" link.
+
+Analysis
+========
+
+Unlike many web frameworks, Pyramid includes a built-in (but optional)
+security model for authentication and authorization. This security
+system is intended to be flexible and support many needs. In this
+security model, authentication (who are you) and authorization (what
+are you allowed to do) are not just pluggable, but de-coupled. To learn
+one step at a time, we provide a system that identifies users and lets
+them log out.
+
+In this example we chose to use the bundled
+:ref:`AuthTktAuthenticationPolicy <authentication_module>`
+policy. We enabled it in our configuration and provided a
+ticket-signing secret in our INI file.
+
+Our view class grew a login view. When you reached it via a GET,
+it returned a login form. When reached via POST, it processed the
+username and password against the "groupfinder" callable that we
+registered in the configuration.
+
+In our template, we fetched the ``logged_in`` value from the view
+class. We use this to calculate the logged-in user,
+if any. In the template we can then choose to show a login link to
+anonymous visitors or a logout link to logged-in users.
+
+Extra Credit
+============
+
+#. What is the difference between a user and a principal?
+
+#. Can I use a database behind my ``groupfinder`` to look up principals?
+
+#. Do I have to put a ``renderer`` in my ``@forbidden_view_config``
+ decorator?
+
+#. Once I am logged in, does any user-centric information get jammed
+ onto each request? Use ``import pdb; pdb.set_trace()`` to answer
+ this.
+
+.. seealso:: :ref:`security_chapter`,
+ :ref:`AuthTktAuthenticationPolicy <authentication_module>`
diff --git a/docs/quick_tutorial/authentication/development.ini b/docs/quick_tutorial/authentication/development.ini
new file mode 100644
index 000000000..5d4580ff5
--- /dev/null
+++ b/docs/quick_tutorial/authentication/development.ini
@@ -0,0 +1,42 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+tutorial.secret = 98zd
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/authentication/setup.py b/docs/quick_tutorial/authentication/setup.py
new file mode 100644
index 000000000..2221b72e9
--- /dev/null
+++ b/docs/quick_tutorial/authentication/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon'
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/authentication/tutorial/__init__.py b/docs/quick_tutorial/authentication/tutorial/__init__.py
new file mode 100644
index 000000000..efc09e760
--- /dev/null
+++ b/docs/quick_tutorial/authentication/tutorial/__init__.py
@@ -0,0 +1,25 @@
+from pyramid.authentication import AuthTktAuthenticationPolicy
+from pyramid.authorization import ACLAuthorizationPolicy
+from pyramid.config import Configurator
+
+from .security import groupfinder
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
+
+ # Security policies
+ authn_policy = AuthTktAuthenticationPolicy(
+ settings['tutorial.secret'], callback=groupfinder,
+ hashalg='sha512')
+ authz_policy = ACLAuthorizationPolicy()
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
+
+ config.add_route('home', '/')
+ config.add_route('hello', '/howdy')
+ config.add_route('login', '/login')
+ config.add_route('logout', '/logout')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/authentication/tutorial/home.pt b/docs/quick_tutorial/authentication/tutorial/home.pt
new file mode 100644
index 000000000..6ecd0081b
--- /dev/null
+++ b/docs/quick_tutorial/authentication/tutorial/home.pt
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${name}</title>
+</head>
+<body>
+
+<div>
+ <a tal:condition="view.logged_in is None"
+ href="${request.application_url}/login">Log In</a>
+ <a tal:condition="view.logged_in is not None"
+ href="${request.application_url}/logout">Logout</a>
+</div>
+
+<h1>Hi ${name}</h1>
+<p>Visit <a href="${request.route_url('hello')}">hello</a></p>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/authentication/tutorial/login.pt b/docs/quick_tutorial/authentication/tutorial/login.pt
new file mode 100644
index 000000000..4451fc4f8
--- /dev/null
+++ b/docs/quick_tutorial/authentication/tutorial/login.pt
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${name}</title>
+</head>
+<body>
+<h1>Login</h1>
+<span tal:replace="message"/>
+
+<form action="${url}" method="post">
+ <input type="hidden" name="came_from"
+ value="${came_from}"/>
+ <label for="login">Username</label>
+ <input type="text" id="login"
+ name="login"
+ value="${login}"/><br/>
+ <label for="password">Password</label>
+ <input type="password" id="password"
+ name="password"
+ value="${password}"/><br/>
+ <input type="submit" name="form.submitted"
+ value="Log In"/>
+</form>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/authentication/tutorial/security.py b/docs/quick_tutorial/authentication/tutorial/security.py
new file mode 100644
index 000000000..ab90bab2c
--- /dev/null
+++ b/docs/quick_tutorial/authentication/tutorial/security.py
@@ -0,0 +1,8 @@
+USERS = {'editor': 'editor',
+ 'viewer': 'viewer'}
+GROUPS = {'editor': ['group:editors']}
+
+
+def groupfinder(userid, request):
+ if userid in USERS:
+ return GROUPS.get(userid, []) \ No newline at end of file
diff --git a/docs/quick_tutorial/authentication/tutorial/views.py b/docs/quick_tutorial/authentication/tutorial/views.py
new file mode 100644
index 000000000..3038b6d9b
--- /dev/null
+++ b/docs/quick_tutorial/authentication/tutorial/views.py
@@ -0,0 +1,64 @@
+from pyramid.httpexceptions import HTTPFound
+from pyramid.security import (
+ remember,
+ forget,
+ authenticated_userid
+ )
+from pyramid.view import (
+ view_config,
+ view_defaults
+ )
+
+from .security import USERS
+
+
+@view_defaults(renderer='home.pt')
+class TutorialViews:
+ def __init__(self, request):
+ self.request = request
+ self.logged_in = authenticated_userid(request)
+
+ @view_config(route_name='home')
+ def home(self):
+ return {'name': 'Home View'}
+
+ @view_config(route_name='hello')
+ def hello(self):
+ return {'name': 'Hello View'}
+
+ @view_config(route_name='login', renderer='login.pt')
+ def login(self):
+ request = self.request
+ login_url = request.route_url('login')
+ referrer = request.url
+ if referrer == login_url:
+ referrer = '/' # never use login form itself as came_from
+ came_from = request.params.get('came_from', referrer)
+ message = ''
+ login = ''
+ password = ''
+ if 'form.submitted' in request.params:
+ login = request.params['login']
+ password = request.params['password']
+ if USERS.get(login) == password:
+ headers = remember(request, login)
+ return HTTPFound(location=came_from,
+ headers=headers)
+ message = 'Failed login'
+
+ return dict(
+ name='Login',
+ message=message,
+ url=request.application_url + '/login',
+ came_from=came_from,
+ login=login,
+ password=password,
+ )
+
+ @view_config(route_name='logout')
+ def logout(self):
+ request = self.request
+ headers = forget(request)
+ url = request.route_url('home')
+ return HTTPFound(location=url,
+ headers=headers)
diff --git a/docs/quick_tutorial/authorization.rst b/docs/quick_tutorial/authorization.rst
new file mode 100644
index 000000000..6b10d3409
--- /dev/null
+++ b/docs/quick_tutorial/authorization.rst
@@ -0,0 +1,112 @@
+===========================================
+21: Protecting Resources With Authorization
+===========================================
+
+Assign security statements to resources describing the permissions
+required to perform an operation.
+
+Background
+==========
+
+Our application has URLs that allow people to add/edit/delete content
+via a web browser. Time to add security to the application. Let's
+protect our add/edit views to require a login (username of
+``editor`` and password of ``editor``.) We will allow the other views
+to continue working without a password.
+
+Objectives
+==========
+
+- Introduce the Pyramid concepts of authentication, authorization,
+ permissions, and access control lists (ACLs)
+
+- Make a :term:`root factory` that returns an instance of our
+ class for the top of the application
+
+- Assign security statements to our root resource
+
+- Add a permissions predicate on a view
+
+- Provide a :term:`Forbidden view` to handle visiting a URL without
+ adequate permissions
+
+Steps
+=====
+
+#. We are going to use the authentication step as our starting point:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r authentication authorization; cd authorization
+ $ $VENV/bin/python setup.py develop
+
+#. Start by changing ``authorization/tutorial/__init__.py`` to
+ specify a root factory to the :term:`configurator`:
+
+ .. literalinclude:: authorization/tutorial/__init__.py
+ :linenos:
+
+#. That means we need to implement
+ ``authorization/tutorial/resources.py``
+
+ .. literalinclude:: authorization/tutorial/resources.py
+ :linenos:
+
+#. Change ``authorization/tutorial/views.py`` to require the ``edit``
+ permission on the ``hello`` view and implement the forbidden view:
+
+ .. literalinclude:: authorization/tutorial/views.py
+ :linenos:
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ in a browser.
+
+#. If you are still logged in, click the "Log Out" link.
+
+#. Visit http://localhost:6543/howdy in a browser. You should be
+ asked to login.
+
+Analysis
+========
+
+This simple tutorial step can be boiled down to the following:
+
+- A view can require a *permission* (``edit``)
+
+- The context for our view (the ``Root``) has an access control list
+ (ACL)
+
+- This ACL says that the ``edit`` permission is available on ``Root``
+ to the ``group:editors`` *principal*
+
+- The registered ``groupfinder`` answers whether a particular user
+ (``editor``) has a particular group (``group:editors``)
+
+In summary: ``hello`` wants ``edit`` permission, ``Root`` says
+``group:editors`` has ``edit`` permission.
+
+Of course, this only applies on ``Root``. Some other part of the site
+(a.k.a. *context*) might have a different ACL.
+
+If you are not logged in and visit ``/hello``, you need to get
+shown the login screen. How does Pyramid know what is the login page to
+use? We explicitly told Pyramid that the ``login`` view should be used
+by decorating the view with ``@forbidden_view_config``.
+
+Extra Credit
+============
+
+#. Perhaps you would like experience of not having enough permissions
+ (forbidden) to be richer. How could you change this?
+
+#. Perhaps we want to store security statements in a database and
+ allow editing via a browser. How might this be done?
+
+#. What if we want different security statements on different kinds of
+ objects? Or on the same kinds of objects, but in different parts of a
+ URL hierarchy?
diff --git a/docs/quick_tutorial/authorization/development.ini b/docs/quick_tutorial/authorization/development.ini
new file mode 100644
index 000000000..5d4580ff5
--- /dev/null
+++ b/docs/quick_tutorial/authorization/development.ini
@@ -0,0 +1,42 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+tutorial.secret = 98zd
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/authorization/setup.py b/docs/quick_tutorial/authorization/setup.py
new file mode 100644
index 000000000..2221b72e9
--- /dev/null
+++ b/docs/quick_tutorial/authorization/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon'
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/authorization/tutorial/__init__.py b/docs/quick_tutorial/authorization/tutorial/__init__.py
new file mode 100644
index 000000000..8f7ab8277
--- /dev/null
+++ b/docs/quick_tutorial/authorization/tutorial/__init__.py
@@ -0,0 +1,26 @@
+from pyramid.authentication import AuthTktAuthenticationPolicy
+from pyramid.authorization import ACLAuthorizationPolicy
+from pyramid.config import Configurator
+
+from .security import groupfinder
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings,
+ root_factory='.resources.Root')
+ config.include('pyramid_chameleon')
+
+ # Security policies
+ authn_policy = AuthTktAuthenticationPolicy(
+ settings['tutorial.secret'], callback=groupfinder,
+ hashalg='sha512')
+ authz_policy = ACLAuthorizationPolicy()
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
+
+ config.add_route('home', '/')
+ config.add_route('hello', '/howdy')
+ config.add_route('login', '/login')
+ config.add_route('logout', '/logout')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/authorization/tutorial/home.pt b/docs/quick_tutorial/authorization/tutorial/home.pt
new file mode 100644
index 000000000..6ecd0081b
--- /dev/null
+++ b/docs/quick_tutorial/authorization/tutorial/home.pt
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${name}</title>
+</head>
+<body>
+
+<div>
+ <a tal:condition="view.logged_in is None"
+ href="${request.application_url}/login">Log In</a>
+ <a tal:condition="view.logged_in is not None"
+ href="${request.application_url}/logout">Logout</a>
+</div>
+
+<h1>Hi ${name}</h1>
+<p>Visit <a href="${request.route_url('hello')}">hello</a></p>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/authorization/tutorial/login.pt b/docs/quick_tutorial/authorization/tutorial/login.pt
new file mode 100644
index 000000000..4451fc4f8
--- /dev/null
+++ b/docs/quick_tutorial/authorization/tutorial/login.pt
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${name}</title>
+</head>
+<body>
+<h1>Login</h1>
+<span tal:replace="message"/>
+
+<form action="${url}" method="post">
+ <input type="hidden" name="came_from"
+ value="${came_from}"/>
+ <label for="login">Username</label>
+ <input type="text" id="login"
+ name="login"
+ value="${login}"/><br/>
+ <label for="password">Password</label>
+ <input type="password" id="password"
+ name="password"
+ value="${password}"/><br/>
+ <input type="submit" name="form.submitted"
+ value="Log In"/>
+</form>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/authorization/tutorial/resources.py b/docs/quick_tutorial/authorization/tutorial/resources.py
new file mode 100644
index 000000000..0cb656f12
--- /dev/null
+++ b/docs/quick_tutorial/authorization/tutorial/resources.py
@@ -0,0 +1,9 @@
+from pyramid.security import Allow, Everyone
+
+
+class Root(object):
+ __acl__ = [(Allow, Everyone, 'view'),
+ (Allow, 'group:editors', 'edit')]
+
+ def __init__(self, request):
+ pass \ No newline at end of file
diff --git a/docs/quick_tutorial/authorization/tutorial/security.py b/docs/quick_tutorial/authorization/tutorial/security.py
new file mode 100644
index 000000000..ab90bab2c
--- /dev/null
+++ b/docs/quick_tutorial/authorization/tutorial/security.py
@@ -0,0 +1,8 @@
+USERS = {'editor': 'editor',
+ 'viewer': 'viewer'}
+GROUPS = {'editor': ['group:editors']}
+
+
+def groupfinder(userid, request):
+ if userid in USERS:
+ return GROUPS.get(userid, []) \ No newline at end of file
diff --git a/docs/quick_tutorial/authorization/tutorial/views.py b/docs/quick_tutorial/authorization/tutorial/views.py
new file mode 100644
index 000000000..92c1946ba
--- /dev/null
+++ b/docs/quick_tutorial/authorization/tutorial/views.py
@@ -0,0 +1,66 @@
+from pyramid.httpexceptions import HTTPFound
+from pyramid.security import (
+ remember,
+ forget,
+ authenticated_userid
+ )
+from pyramid.view import (
+ view_config,
+ view_defaults,
+ forbidden_view_config
+ )
+
+from .security import USERS
+
+
+@view_defaults(renderer='home.pt')
+class TutorialViews:
+ def __init__(self, request):
+ self.request = request
+ self.logged_in = authenticated_userid(request)
+
+ @view_config(route_name='home')
+ def home(self):
+ return {'name': 'Home View'}
+
+ @view_config(route_name='hello', permission='edit')
+ def hello(self):
+ return {'name': 'Hello View'}
+
+ @view_config(route_name='login', renderer='login.pt')
+ @forbidden_view_config(renderer='login.pt')
+ def login(self):
+ request = self.request
+ login_url = request.route_url('login')
+ referrer = request.url
+ if referrer == login_url:
+ referrer = '/' # never use login form itself as came_from
+ came_from = request.params.get('came_from', referrer)
+ message = ''
+ login = ''
+ password = ''
+ if 'form.submitted' in request.params:
+ login = request.params['login']
+ password = request.params['password']
+ if USERS.get(login) == password:
+ headers = remember(request, login)
+ return HTTPFound(location=came_from,
+ headers=headers)
+ message = 'Failed login'
+
+ return dict(
+ name='Login',
+ message=message,
+ url=request.application_url + '/login',
+ came_from=came_from,
+ login=login,
+ password=password,
+ )
+
+ @view_config(route_name='logout')
+ def logout(self):
+ request = self.request
+ headers = forget(request)
+ url = request.route_url('home')
+ return HTTPFound(location=url,
+ headers=headers)
diff --git a/docs/quick_tutorial/conf.py b/docs/quick_tutorial/conf.py
new file mode 100644
index 000000000..47b8fae41
--- /dev/null
+++ b/docs/quick_tutorial/conf.py
@@ -0,0 +1,281 @@
+# -*- coding: utf-8 -*-
+#
+# Getting Started with Pyramid and REST documentation build configuration file, created by
+# sphinx-quickstart on Mon Aug 26 14:44:57 2013.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.intersphinx']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Getting Started with Pyramid and REST'
+copyright = u'2013, Agendaless Consulting'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.0'
+# The full version, including alpha/beta/rc tags.
+release = '1.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'GettingStartedwithPyramidandRESTdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'GettingStartedwithPyramidandREST.tex',
+ u'Getting Started with Pyramid and REST Documentation',
+ u'Agendaless Consulting', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'gettingstartedwithpyramidandrest',
+ u'Getting Started with Pyramid and REST Documentation',
+ [u'Agendaless Consulting'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ ('index', 'GettingStartedwithPyramidandREST',
+ u'Getting Started with Pyramid and REST Documentation',
+ u'Agendaless Consulting', 'GettingStartedwithPyramidandREST',
+ 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#texinfo_no_detailmenu = False
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {
+ 'python': (
+ 'http://docs.python.org/2',
+ None),
+ 'sqla': (
+ 'http://docs.sqlalchemy.org/en/latest',
+ None),
+ 'pyramid': (
+ 'http://docs.pylonsproject.org/projects/pyramid/en/latest/',
+ None),
+ 'jinja2': (
+ 'http://docs.pylonsproject.org/projects/pyramid_jinja2/en/latest/',
+ None),
+ 'toolbar': (
+ 'http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest',
+ None),
+ 'deform': (
+ 'http://docs.pylonsproject.org/projects/deform/en/latest',
+ None),
+ 'colander': (
+ 'http://docs.pylonsproject.org/projects/colander/en/latest',
+ None),
+ 'tutorials': (
+ 'http://docs.pylonsproject.org/projects/pyramid_tutorials/en/latest/',
+ None),
+}
diff --git a/docs/quick_tutorial/databases.rst b/docs/quick_tutorial/databases.rst
new file mode 100644
index 000000000..93a02ffc7
--- /dev/null
+++ b/docs/quick_tutorial/databases.rst
@@ -0,0 +1,195 @@
+.. _qtut_databases:
+
+==============================
+19: Databases Using SQLAlchemy
+==============================
+
+Store/retrieve data using the SQLAlchemy ORM atop the SQLite database.
+
+Background
+==========
+
+Our Pyramid-based wiki application now needs database-backed storage of
+pages. This frequently means a SQL database. The Pyramid community
+strongly supports the
+:ref:`SQLAlchemy <sqla:index_toplevel>` project and its
+:ref:`object-relational mapper (ORM) <sqla:ormtutorial_toplevel>`
+as a convenient, Pythonic way to interface to databases.
+
+In this step we hook up SQLAlchemy to a SQLite database table,
+providing storage and retrieval for the wikipages in the previous step.
+
+.. note::
+
+ The ``alchemy`` scaffold is really helpful for getting a
+ 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.
+
+Objectives
+==========
+
+- Store pages in SQLite by using SQLAlchemy models
+
+- Use SQLAlchemy queries to list/add/view/edit pages
+
+- Provide a database-initialize command by writing a Pyramid *console
+ script* which can be run from the command line
+
+Steps
+=====
+
+.. warning::
+
+ Your Python might not have SQLite bundled. If not, install it into
+ your virtual environment with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/easy_install sphinx pysqlite
+
+#. We are going to use the forms step as our starting point:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r forms databases; cd databases
+
+#. We need to add some dependencies in ``databases/setup.py`` as well
+ as an "entry point" for the command-line script:
+
+ .. literalinclude:: databases/setup.py
+ :linenos:
+
+ .. note::
+
+ We aren't yet doing ``python3.3 setup.py develop`` as we
+ are changing it later.
+
+#. Our configuration file at ``databases/development.ini`` wires
+ together some new pieces:
+
+ .. literalinclude:: databases/development.ini
+ :language: ini
+
+#. This engine configuration now needs to be read into the application
+ through changes in ``databases/tutorial/__init__.py``:
+
+ .. literalinclude:: databases/tutorial/__init__.py
+ :linenos:
+
+#. Make a command-line script at ``databases/tutorial/initialize_db.py``
+ to initialize the database:
+
+ .. literalinclude:: databases/tutorial/initialize_db.py
+
+#. Since ``setup.py`` changed, we now run it:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/python setup.py develop
+
+#. The script references some models in ``databases/tutorial/models.py``:
+
+ .. literalinclude:: databases/tutorial/models.py
+ :linenos:
+
+#. Let's run this console script, thus producing our database and table:
+
+ .. code-block:: bash
+
+ $ initialize_tutorial_db development.ini
+ 2013-09-06 15:54:08,050 INFO [sqlalchemy.engine.base.Engine][MainThread] PRAGMA table_info("wikipages")
+ 2013-09-06 15:54:08,050 INFO [sqlalchemy.engine.base.Engine][MainThread] ()
+ 2013-09-06 15:54:08,051 INFO [sqlalchemy.engine.base.Engine][MainThread]
+ CREATE TABLE wikipages (
+ uid INTEGER NOT NULL,
+ title TEXT,
+ body TEXT,
+ PRIMARY KEY (uid),
+ UNIQUE (title)
+ )
+
+#. With our data now driven by SQLAlchemy queries, we need to update
+ our ``databases/tutorial/views.py``:
+
+ .. literalinclude:: databases/tutorial/views.py
+
+#. Our tests in ``databases/tutorial/tests.py`` changed to include
+ SQLAlchemy bootstrapping:
+
+ .. literalinclude:: databases/tutorial/tests.py
+ :linenos:
+
+#. Run the tests in your package using ``nose``:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/nosetests .
+ ..
+ -----------------------------------------------------------------
+ Ran 2 tests in 1.141s
+
+ OK
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ in a browser.
+
+Analysis
+========
+
+Let's start with the dependencies. We made the decision to use
+``SQLAlchemy`` to talk to our database. We also, though, installed
+``pyramid_tm`` and ``zope.sqlalchemy``. Why?
+
+Pyramid has a strong orientation towards support for ``transactions``.
+Specifically, you can install a transaction manager into your app
+application, either as middleware or a Pyramid "tween". Then,
+just before you return the response, all transaction-aware parts of
+your application are executed.
+
+This means Pyramid view code usually doesn't manage transactions. If
+your view code or a template generates an error, the transaction manager
+aborts the transaction. This is a very liberating way to write code.
+
+The ``pyramid_tm`` package provides a "tween" that is configured in the
+``development.ini`` configuration file. That installs it. We then need
+a package that makes SQLAlchemy and thus the RDBMS transaction manager
+integrate with the Pyramid transaction manager. That's what
+``zope.sqlalchemy`` does.
+
+Where do we point at the location on disk for the SQLite file? In the
+configuration file. This lets consumers of our package change the
+location in a safe (non-code) way. That is, in configuration. This
+configuration-oriented approach isn't required in Pyramid; you can
+still make such statements in your ``__init__.py`` or some companion
+module.
+
+The ``initialize_tutorial_db`` is a nice example of framework support.
+You point your setup at the location of some ``[console_scripts]`` and
+these get generated into your virtualenv's ``bin`` directory. Our
+console script follows the pattern of being fed a configuration file
+with all the bootstrapping. It then opens SQLAlchemy and creates the
+root of the wiki, which also makes the SQLite file. Note the
+``with transaction.manager`` part that puts the work in the scope of a
+transaction (as we aren't inside a web request where this is done
+automatically.)
+
+The ``models.py`` does a little bit extra work to hook up SQLAlchemy
+into the Pyramid transaction manager. It then declares the model for a
+``Page``.
+
+Our views have changes primarily around replacing our dummy
+dictionary-of-dictionaries data with proper database support: list the
+rows, add a row, edit a row, and delete a row.
+
+Extra Credit
+============
+
+#. Why all this code? Why can't I just type 2 lines and have magic ensue?
+
+#. Give a try at a button that deletes a wiki page.
diff --git a/docs/quick_tutorial/databases/development.ini b/docs/quick_tutorial/databases/development.ini
new file mode 100644
index 000000000..270da960f
--- /dev/null
+++ b/docs/quick_tutorial/databases/development.ini
@@ -0,0 +1,49 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+ pyramid_tm
+
+sqlalchemy.url = sqlite:///%(here)s/sqltutorial.sqlite
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial, sqlalchemy
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[logger_sqlalchemy]
+level = INFO
+handlers =
+qualname = sqlalchemy.engine
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/databases/setup.py b/docs/quick_tutorial/databases/setup.py
new file mode 100644
index 000000000..238358fe4
--- /dev/null
+++ b/docs/quick_tutorial/databases/setup.py
@@ -0,0 +1,20 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon',
+ 'deform',
+ 'sqlalchemy',
+ 'pyramid_tm',
+ 'zope.sqlalchemy'
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ [console_scripts]
+ initialize_tutorial_db = tutorial.initialize_db:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/databases/sqltutorial.sqlite b/docs/quick_tutorial/databases/sqltutorial.sqlite
new file mode 100644
index 000000000..b8bd856fd
--- /dev/null
+++ b/docs/quick_tutorial/databases/sqltutorial.sqlite
Binary files differ
diff --git a/docs/quick_tutorial/databases/tutorial/__init__.py b/docs/quick_tutorial/databases/tutorial/__init__.py
new file mode 100644
index 000000000..74aa25740
--- /dev/null
+++ b/docs/quick_tutorial/databases/tutorial/__init__.py
@@ -0,0 +1,21 @@
+from pyramid.config import Configurator
+
+from sqlalchemy import engine_from_config
+
+from .models import DBSession, Base
+
+def main(global_config, **settings):
+ engine = engine_from_config(settings, 'sqlalchemy.')
+ DBSession.configure(bind=engine)
+ Base.metadata.bind = engine
+
+ config = Configurator(settings=settings,
+ root_factory='tutorial.models.Root')
+ config.include('pyramid_chameleon')
+ config.add_route('wiki_view', '/')
+ config.add_route('wikipage_add', '/add')
+ config.add_route('wikipage_view', '/{uid}')
+ config.add_route('wikipage_edit', '/{uid}/edit')
+ config.add_static_view('deform_static', 'deform:static/')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/databases/tutorial/initialize_db.py b/docs/quick_tutorial/databases/tutorial/initialize_db.py
new file mode 100644
index 000000000..98be524a1
--- /dev/null
+++ b/docs/quick_tutorial/databases/tutorial/initialize_db.py
@@ -0,0 +1,37 @@
+import os
+import sys
+import transaction
+
+from sqlalchemy import engine_from_config
+
+from pyramid.paster import (
+ get_appsettings,
+ setup_logging,
+ )
+
+from .models import (
+ DBSession,
+ Page,
+ Base,
+ )
+
+
+def usage(argv):
+ cmd = os.path.basename(argv[0])
+ print('usage: %s <config_uri>\n'
+ '(example: "%s development.ini")' % (cmd, cmd))
+ sys.exit(1)
+
+
+def main(argv=sys.argv):
+ if len(argv) != 2:
+ usage(argv)
+ config_uri = argv[1]
+ setup_logging(config_uri)
+ settings = get_appsettings(config_uri)
+ engine = engine_from_config(settings, 'sqlalchemy.')
+ DBSession.configure(bind=engine)
+ Base.metadata.create_all(engine)
+ with transaction.manager:
+ model = Page(title='Root', body='<p>Root</p>')
+ DBSession.add(model)
diff --git a/docs/quick_tutorial/databases/tutorial/models.py b/docs/quick_tutorial/databases/tutorial/models.py
new file mode 100644
index 000000000..b27c38417
--- /dev/null
+++ b/docs/quick_tutorial/databases/tutorial/models.py
@@ -0,0 +1,35 @@
+from pyramid.security import Allow, Everyone
+
+from sqlalchemy import (
+ Column,
+ Integer,
+ Text,
+ )
+
+from sqlalchemy.ext.declarative import declarative_base
+
+from sqlalchemy.orm import (
+ scoped_session,
+ sessionmaker,
+ )
+
+from zope.sqlalchemy import ZopeTransactionExtension
+
+DBSession = scoped_session(
+ sessionmaker(extension=ZopeTransactionExtension()))
+Base = declarative_base()
+
+
+class Page(Base):
+ __tablename__ = 'wikipages'
+ uid = Column(Integer, primary_key=True)
+ title = Column(Text, unique=True)
+ body = Column(Text)
+
+
+class Root(object):
+ __acl__ = [(Allow, Everyone, 'view'),
+ (Allow, 'group:editors', 'edit')]
+
+ def __init__(self, request):
+ pass \ No newline at end of file
diff --git a/docs/quick_tutorial/databases/tutorial/tests.py b/docs/quick_tutorial/databases/tutorial/tests.py
new file mode 100644
index 000000000..e18e70c8c
--- /dev/null
+++ b/docs/quick_tutorial/databases/tutorial/tests.py
@@ -0,0 +1,58 @@
+import unittest
+import transaction
+
+from pyramid import testing
+
+
+def _initTestingDB():
+ from sqlalchemy import create_engine
+ from .models import (
+ DBSession,
+ Page,
+ Base
+ )
+ engine = create_engine('sqlite://')
+ Base.metadata.create_all(engine)
+ DBSession.configure(bind=engine)
+ with transaction.manager:
+ model = Page(title='FrontPage', body='This is the front page')
+ DBSession.add(model)
+ return DBSession
+
+
+class WikiViewTests(unittest.TestCase):
+ def setUp(self):
+ self.session = _initTestingDB()
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ self.session.remove()
+ testing.tearDown()
+
+ def test_wiki_view(self):
+ from tutorial.views import WikiViews
+
+ request = testing.DummyRequest()
+ inst = WikiViews(request)
+ response = inst.wiki_view()
+ self.assertEqual(response['title'], 'Wiki View')
+
+
+class WikiFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ self.session = _initTestingDB()
+ self.config = testing.setUp()
+ from pyramid.paster import get_app
+ app = get_app('development.ini')
+ from webtest import TestApp
+ self.testapp = TestApp(app)
+
+ def tearDown(self):
+ self.session.remove()
+ testing.tearDown()
+
+ def test_it(self):
+ res = self.testapp.get('/', status=200)
+ self.assertIn(b'Wiki: View', res.body)
+ res = self.testapp.get('/add', status=200)
+ self.assertIn(b'Add/Edit', res.body)
diff --git a/docs/quick_tutorial/databases/tutorial/views.py b/docs/quick_tutorial/databases/tutorial/views.py
new file mode 100644
index 000000000..4608c6d43
--- /dev/null
+++ b/docs/quick_tutorial/databases/tutorial/views.py
@@ -0,0 +1,96 @@
+import colander
+import deform.widget
+
+from pyramid.httpexceptions import HTTPFound
+from pyramid.view import view_config
+
+from .models import DBSession, Page
+
+
+class WikiPage(colander.MappingSchema):
+ title = colander.SchemaNode(colander.String())
+ body = colander.SchemaNode(
+ colander.String(),
+ widget=deform.widget.RichTextWidget()
+ )
+
+
+class WikiViews(object):
+ def __init__(self, request):
+ self.request = request
+
+ @property
+ def wiki_form(self):
+ schema = WikiPage()
+ return deform.Form(schema, buttons=('submit',))
+
+ @property
+ def reqts(self):
+ return self.wiki_form.get_widget_resources()
+
+ @view_config(route_name='wiki_view', renderer='wiki_view.pt')
+ def wiki_view(self):
+ pages = DBSession.query(Page).order_by(Page.title)
+ return dict(title='Wiki View', pages=pages)
+
+ @view_config(route_name='wikipage_add',
+ renderer='wikipage_addedit.pt')
+ def wikipage_add(self):
+ form = self.wiki_form.render()
+
+ if 'submit' in self.request.params:
+ controls = self.request.POST.items()
+ try:
+ appstruct = self.wiki_form.validate(controls)
+ except deform.ValidationFailure as e:
+ # Form is NOT valid
+ return dict(form=e.render())
+
+ # Add a new page to the database
+ new_title = appstruct['title']
+ new_body = appstruct['body']
+ DBSession.add(Page(title=new_title, body=new_body))
+
+ # Get the new ID and redirect
+ page = DBSession.query(Page).filter_by(title=new_title).one()
+ new_uid = page.uid
+
+ url = self.request.route_url('wikipage_view', uid=new_uid)
+ return HTTPFound(url)
+
+ return dict(form=form)
+
+
+ @view_config(route_name='wikipage_view', renderer='wikipage_view.pt')
+ def wikipage_view(self):
+ uid = int(self.request.matchdict['uid'])
+ page = DBSession.query(Page).filter_by(uid=uid).one()
+ return dict(page=page)
+
+
+ @view_config(route_name='wikipage_edit',
+ renderer='wikipage_addedit.pt')
+ def wikipage_edit(self):
+ uid = int(self.request.matchdict['uid'])
+ page = DBSession.query(Page).filter_by(uid=uid).one()
+
+ wiki_form = self.wiki_form
+
+ if 'submit' in self.request.params:
+ controls = self.request.POST.items()
+ try:
+ appstruct = wiki_form.validate(controls)
+ except deform.ValidationFailure as e:
+ return dict(page=page, form=e.render())
+
+ # Change the content and redirect to the view
+ page.title = appstruct['title']
+ page.body = appstruct['body']
+ url = self.request.route_url('wikipage_view', uid=uid)
+ return HTTPFound(url)
+
+ form = self.wiki_form.render(dict(
+ uid=page.uid, title=page.title, body=page.body)
+ )
+
+ return dict(page=page, form=form)
diff --git a/docs/quick_tutorial/databases/tutorial/wiki_view.pt b/docs/quick_tutorial/databases/tutorial/wiki_view.pt
new file mode 100644
index 000000000..9e3afe495
--- /dev/null
+++ b/docs/quick_tutorial/databases/tutorial/wiki_view.pt
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Wiki: View</title>
+</head>
+<body>
+<h1>Wiki</h1>
+
+<a href="${request.route_url('wikipage_add')}">Add
+ WikiPage</a>
+<ul>
+ <li tal:repeat="page pages">
+ <a href="${request.route_url('wikipage_view', uid=page.uid)}">
+ ${page.title}
+ </a>
+ </li>
+</ul>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt b/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt
new file mode 100644
index 000000000..d1fea0d7f
--- /dev/null
+++ b/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>WikiPage: Add/Edit</title>
+ <tal:block tal:repeat="reqt view.reqts['css']">
+ <link rel="stylesheet" type="text/css"
+ href="${request.static_url('deform:static/' + reqt)}"/>
+ </tal:block>
+ <tal:block tal:repeat="reqt view.reqts['js']">
+ <script src="${request.static_url('deform:static/' + reqt)}"
+ type="text/javascript"></script>
+ </tal:block>
+</head>
+<body>
+<h1>Wiki</h1>
+
+<p>${structure: form}</p>
+<script type="text/javascript">
+ deform.load()
+</script>
+</body>
+</html>
diff --git a/docs/quick_tutorial/databases/tutorial/wikipage_view.pt b/docs/quick_tutorial/databases/tutorial/wikipage_view.pt
new file mode 100644
index 000000000..cb9ff526e
--- /dev/null
+++ b/docs/quick_tutorial/databases/tutorial/wikipage_view.pt
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>WikiPage: View</title>
+</head>
+<body>
+<a href="${request.route_url('wiki_view')}">
+ Up
+</a> |
+<a href="${request.route_url('wikipage_edit', uid=page.uid)}">
+ Edit
+</a>
+
+<h1>${page.title}</h1>
+<p>${structure: page.body}</p>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/debugtoolbar.rst b/docs/quick_tutorial/debugtoolbar.rst
new file mode 100644
index 000000000..d25588c49
--- /dev/null
+++ b/docs/quick_tutorial/debugtoolbar.rst
@@ -0,0 +1,89 @@
+.. _qtut_debugtoolbar:
+
+============================================
+04: Easier Development with ``debugtoolbar``
+============================================
+
+Error-handling and introspection using the ``pyramid_debugtoolbar``
+add-on.
+
+Background
+==========
+
+As we introduce the basics we also want to show how to be productive in
+development and debugging. For example, we just discussed template
+reloading and earlier we showed ``--reload`` for application reloading.
+
+``pyramid_debugtoolbar`` is a popular Pyramid add-on which makes
+several tools available in your browser. Adding it to your project
+illustrates several points about configuration.
+
+Objectives
+==========
+
+- Install and enable the toolbar to help during development
+
+- Explain Pyramid add-ons
+
+- Show how an add-on gets configured into your application
+
+Steps
+=====
+
+#. First we copy the results of the previous step, as well as install
+ the ``pyramid_debugtoolbar`` package:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r ini debugtoolbar; cd debugtoolbar
+ $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/easy_install pyramid_debugtoolbar
+
+
+#. Our ``debugtoolbar/development.ini`` gets a configuration entry for
+ ``pyramid.includes``:
+
+ .. literalinclude:: debugtoolbar/development.ini
+ :language: ini
+ :linenos:
+
+#. Run the WSGI application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ in your browser. See the handy
+ toolbar on the right.
+
+Analysis
+========
+
+``pyramid_debugtoolbar`` is a full-fledged Python package,
+available on PyPI just like thousands of other Python packages. Thus we
+start by installing the ``pyramid_debugtoolbar`` package into our
+virtual environment using normal Python package installation commands.
+
+The ``pyramid_debugtoolbar`` Python package is also a Pyramid add-on,
+which means we need to include its add-on configuration into our web
+application. We could do this with imperative configuration in
+``tutorial/__init__.py`` by using ``config.include``. Pyramid also
+supports wiring in add-on configuration via our ``development.ini``
+using ``pyramid.includes``. We use this to load the configuration for
+the debugtoolbar.
+
+You'll now see an attractive (and collapsible) menu in the right of
+your browser, providing introspective access to debugging information.
+Even better, if your web application generates an error,
+you will see a nice traceback on the screen. When you want to disable
+this toolbar, no need to change code: you can remove it from
+``pyramid.includes`` in the relevant ``.ini`` configuration file (thus
+showing why configuration files are handy.)
+
+Note that the toolbar mutates the HTML generated by our app and uses jQuery to
+overlay itself. If you are using the toolbar while you're developing and you
+start to experience otherwise inexplicable client-side weirdness, you can shut
+it off by commenting out the ``pyramid_debugtoolbar`` line in
+``pyramid.includes`` temporarily.
+
+.. seealso:: See Also: :ref:`pyramid_debugtoolbar <toolbar:overview>`
diff --git a/docs/quick_tutorial/debugtoolbar/development.ini b/docs/quick_tutorial/debugtoolbar/development.ini
new file mode 100644
index 000000000..470d92c57
--- /dev/null
+++ b/docs/quick_tutorial/debugtoolbar/development.ini
@@ -0,0 +1,40 @@
+[app:main]
+use = egg:tutorial
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/debugtoolbar/setup.py b/docs/quick_tutorial/debugtoolbar/setup.py
new file mode 100644
index 000000000..9997984d3
--- /dev/null
+++ b/docs/quick_tutorial/debugtoolbar/setup.py
@@ -0,0 +1,13 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/debugtoolbar/tutorial/__init__.py b/docs/quick_tutorial/debugtoolbar/tutorial/__init__.py
new file mode 100644
index 000000000..2b4e84f30
--- /dev/null
+++ b/docs/quick_tutorial/debugtoolbar/tutorial/__init__.py
@@ -0,0 +1,13 @@
+from pyramid.config import Configurator
+from pyramid.response import Response
+
+
+def hello_world(request):
+ return Response('<body><h1>Hello World!</h1></body>')
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.add_route('hello', '/')
+ config.add_view(hello_world, route_name='hello')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/forms.rst b/docs/quick_tutorial/forms.rst
new file mode 100644
index 000000000..e8bc0c8b4
--- /dev/null
+++ b/docs/quick_tutorial/forms.rst
@@ -0,0 +1,148 @@
+.. _qtut_forms:
+
+====================================
+18: Forms and Validation With Deform
+====================================
+
+Schema-driven, autogenerated forms with validation.
+
+Background
+==========
+
+Modern web applications deal extensively with forms. Developers,
+though, have a wide range of philosophies about how frameworks should
+help them with their forms. As such, Pyramid doesn't directly bundle
+one particular form library. Instead, there are a variety of form
+libraries that are easy to use in Pyramid.
+
+:ref:`Deform <deform:overview>`
+is one such library. In this step, we introduce Deform for our
+forms and validation. This also gives us the
+:ref:`Colander <colander:overview>` for schemas and validation.
+
+Deform is getting a facelift, with styling from Twitter Bootstrap and
+advanced widgets from popular JavaScript projects. The work began in
+``deform_bootstrap`` and is being merged into an update to Deform.
+
+Objectives
+==========
+
+- Make a schema using Colander, the companion to Deform
+
+- Create a form with Deform and change our views to handle validation
+
+Steps
+=====
+
+#. First we copy the results of the ``view_classes`` step:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r view_classes forms; cd forms
+
+#. Let's edit ``forms/setup.py`` to declare a dependency on Deform
+ (which then pulls in Colander as a dependency:
+
+ .. literalinclude:: forms/setup.py
+ :linenos:
+
+#. We can now install our project in development mode:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/python setup.py develop
+
+#. Register a static view in ``forms/tutorial/__init__.py`` for
+ Deform's CSS/JS etc. as well as our demo wikipage scenario's
+ views:
+
+ .. literalinclude:: forms/tutorial/__init__.py
+ :linenos:
+
+#. Implement the new views, as well as the form schemas and some
+ dummy data, in ``forms/tutorial/views.py``:
+
+ .. literalinclude:: forms/tutorial/views.py
+ :linenos:
+
+#. A template for the top of the "wiki" in
+ ``forms/tutorial/wiki_view.pt``:
+
+ .. literalinclude:: forms/tutorial/wiki_view.pt
+ :language: html
+ :linenos:
+
+#. Another template for adding/editing in
+ ``forms/tutorial/wikipage_addedit.pt``:
+
+ .. literalinclude:: forms/tutorial/wikipage_addedit.pt
+ :language: html
+ :linenos:
+
+#. Finally, a template at ``forms/tutorial/wikipage_view.pt``
+ for viewing a wiki page:
+
+ .. literalinclude:: forms/tutorial/wikipage_view.pt
+ :language: html
+ :linenos:
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ in a browser.
+
+
+Analysis
+========
+
+This step helps illustrate the utility of asset specifications for
+static assets. We have an outside package called Deform with static
+assets which need to be published. We don't have to know where on disk
+it is located. We point at the package, then the path inside the package.
+
+We just need to include a call to ``add_static_view`` to make that
+directory available at a URL. For Pyramid-specific pages,
+Pyramid provides a facility (``config.include()``) which even makes
+that unnecessary for consumers of a package. (Deform is not specific to
+Pyramid.)
+
+Our forms have rich widgets which need the static CSS and JS just
+mentioned. Deform has a :term:`resource registry` which allows widgets
+to specify which JS and CSS are needed. Our ``wikipage_addedit.pt``
+template shows how we iterated over that data to generate markup that
+includes the needed resources.
+
+Our add and edit views use a pattern called *self-posting forms*.
+Meaning, the same URL is used to ``GET`` the form as is used to
+``POST`` the form. The route, the view, and the template are the same
+whether you are walking up to it the first time or you clicked a button.
+
+Inside the view we do ``if 'submit' in self.request.params:`` to see if
+this form was a ``POST`` where the user clicked on a particular button
+``<input name="submit">``.
+
+The form controller then follows a typical pattern:
+
+- If you are doing a GET, skip over and just return the form
+
+- If you are doing a POST, validate the form contents
+
+- If the form is invalid, bail out by re-rendering the form with the
+ supplied ``POST`` data
+
+- If the validation succeeded, perform some action and issue a
+ redirect via ``HTTPFound``.
+
+We are, in essence, writing our own form controller. Other
+Pyramid-based systems, including ``pyramid_deform``, provide a
+form-centric view class which automates much of this branching and
+routing.
+
+Extra Credit
+============
+
+#. Give a try at a button that goes to a delete view for a
+ particular wiki page.
diff --git a/docs/quick_tutorial/forms/development.ini b/docs/quick_tutorial/forms/development.ini
new file mode 100644
index 000000000..62e0c5123
--- /dev/null
+++ b/docs/quick_tutorial/forms/development.ini
@@ -0,0 +1,41 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/forms/setup.py b/docs/quick_tutorial/forms/setup.py
new file mode 100644
index 000000000..361ade013
--- /dev/null
+++ b/docs/quick_tutorial/forms/setup.py
@@ -0,0 +1,15 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon',
+ 'deform'
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/forms/tutorial/__init__.py b/docs/quick_tutorial/forms/tutorial/__init__.py
new file mode 100644
index 000000000..dff7457cf
--- /dev/null
+++ b/docs/quick_tutorial/forms/tutorial/__init__.py
@@ -0,0 +1,13 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_route('wiki_view', '/')
+ config.add_route('wikipage_add', '/add')
+ config.add_route('wikipage_view', '/{uid}')
+ config.add_route('wikipage_edit', '/{uid}/edit')
+ config.add_static_view('deform_static', 'deform:static/')
+ config.scan('.views')
+ return config.make_wsgi_app()
diff --git a/docs/quick_tutorial/forms/tutorial/tests.py b/docs/quick_tutorial/forms/tutorial/tests.py
new file mode 100644
index 000000000..5a2c40904
--- /dev/null
+++ b/docs/quick_tutorial/forms/tutorial/tests.py
@@ -0,0 +1,36 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ from .views import WikiViews
+
+ request = testing.DummyRequest()
+ inst = WikiViews(request)
+ response = inst.wiki_view()
+ self.assertEqual(len(response['pages']), 3)
+
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+
+ app = main({})
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ res = self.testapp.get('/', status=200)
+ self.assertIn(b'<title>Wiki: View</title>', res.body)
diff --git a/docs/quick_tutorial/forms/tutorial/views.py b/docs/quick_tutorial/forms/tutorial/views.py
new file mode 100644
index 000000000..004d2aba9
--- /dev/null
+++ b/docs/quick_tutorial/forms/tutorial/views.py
@@ -0,0 +1,96 @@
+import colander
+import deform.widget
+
+from pyramid.httpexceptions import HTTPFound
+from pyramid.view import view_config
+
+pages = {
+ '100': dict(uid='100', title='Page 100', body='<em>100</em>'),
+ '101': dict(uid='101', title='Page 101', body='<em>101</em>'),
+ '102': dict(uid='102', title='Page 102', body='<em>102</em>')
+}
+
+class WikiPage(colander.MappingSchema):
+ title = colander.SchemaNode(colander.String())
+ body = colander.SchemaNode(
+ colander.String(),
+ widget=deform.widget.RichTextWidget()
+ )
+
+
+class WikiViews(object):
+ def __init__(self, request):
+ self.request = request
+
+ @property
+ def wiki_form(self):
+ schema = WikiPage()
+ return deform.Form(schema, buttons=('submit',))
+
+ @property
+ def reqts(self):
+ return self.wiki_form.get_widget_resources()
+
+ @view_config(route_name='wiki_view', renderer='wiki_view.pt')
+ def wiki_view(self):
+ return dict(pages=pages.values())
+
+ @view_config(route_name='wikipage_add',
+ renderer='wikipage_addedit.pt')
+ def wikipage_add(self):
+ form = self.wiki_form.render()
+
+ if 'submit' in self.request.params:
+ controls = self.request.POST.items()
+ try:
+ appstruct = self.wiki_form.validate(controls)
+ except deform.ValidationFailure as e:
+ # Form is NOT valid
+ return dict(form=e.render())
+
+ # Form is valid, make a new identifier and add to list
+ last_uid = int(sorted(pages.keys())[-1])
+ new_uid = str(last_uid + 1)
+ pages[new_uid] = dict(
+ uid=new_uid, title=appstruct['title'],
+ body=appstruct['body']
+ )
+
+ # Now visit new page
+ url = self.request.route_url('wikipage_view', uid=new_uid)
+ return HTTPFound(url)
+
+ return dict(form=form)
+
+ @view_config(route_name='wikipage_view', renderer='wikipage_view.pt')
+ def wikipage_view(self):
+ uid = self.request.matchdict['uid']
+ page = pages[uid]
+ return dict(page=page)
+
+ @view_config(route_name='wikipage_edit',
+ renderer='wikipage_addedit.pt')
+ def wikipage_edit(self):
+ uid = self.request.matchdict['uid']
+ page = pages[uid]
+
+ wiki_form = self.wiki_form
+
+ if 'submit' in self.request.params:
+ controls = self.request.POST.items()
+ try:
+ appstruct = wiki_form.validate(controls)
+ except deform.ValidationFailure as e:
+ return dict(page=page, form=e.render())
+
+ # Change the content and redirect to the view
+ page['title'] = appstruct['title']
+ page['body'] = appstruct['body']
+
+ url = self.request.route_url('wikipage_view',
+ uid=page['uid'])
+ return HTTPFound(url)
+
+ form = wiki_form.render(page)
+
+ return dict(page=page, form=form) \ No newline at end of file
diff --git a/docs/quick_tutorial/forms/tutorial/wiki_view.pt b/docs/quick_tutorial/forms/tutorial/wiki_view.pt
new file mode 100644
index 000000000..9e3afe495
--- /dev/null
+++ b/docs/quick_tutorial/forms/tutorial/wiki_view.pt
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Wiki: View</title>
+</head>
+<body>
+<h1>Wiki</h1>
+
+<a href="${request.route_url('wikipage_add')}">Add
+ WikiPage</a>
+<ul>
+ <li tal:repeat="page pages">
+ <a href="${request.route_url('wikipage_view', uid=page.uid)}">
+ ${page.title}
+ </a>
+ </li>
+</ul>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt b/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt
new file mode 100644
index 000000000..d1fea0d7f
--- /dev/null
+++ b/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>WikiPage: Add/Edit</title>
+ <tal:block tal:repeat="reqt view.reqts['css']">
+ <link rel="stylesheet" type="text/css"
+ href="${request.static_url('deform:static/' + reqt)}"/>
+ </tal:block>
+ <tal:block tal:repeat="reqt view.reqts['js']">
+ <script src="${request.static_url('deform:static/' + reqt)}"
+ type="text/javascript"></script>
+ </tal:block>
+</head>
+<body>
+<h1>Wiki</h1>
+
+<p>${structure: form}</p>
+<script type="text/javascript">
+ deform.load()
+</script>
+</body>
+</html>
diff --git a/docs/quick_tutorial/forms/tutorial/wikipage_view.pt b/docs/quick_tutorial/forms/tutorial/wikipage_view.pt
new file mode 100644
index 000000000..cb9ff526e
--- /dev/null
+++ b/docs/quick_tutorial/forms/tutorial/wikipage_view.pt
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>WikiPage: View</title>
+</head>
+<body>
+<a href="${request.route_url('wiki_view')}">
+ Up
+</a> |
+<a href="${request.route_url('wikipage_edit', uid=page.uid)}">
+ Edit
+</a>
+
+<h1>${page.title}</h1>
+<p>${structure: page.body}</p>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/functional_testing.rst b/docs/quick_tutorial/functional_testing.rst
new file mode 100644
index 000000000..205ddf5cb
--- /dev/null
+++ b/docs/quick_tutorial/functional_testing.rst
@@ -0,0 +1,70 @@
+.. _qtut_functional_testing:
+
+===================================
+06: Functional Testing with WebTest
+===================================
+
+Write end-to-end full-stack testing using ``webtest``.
+
+Background
+==========
+
+Unit tests are a common and popular approach to test-driven development
+(TDD.) In web applications, though, the templating and entire apparatus
+of a web site are important parts of the delivered quality. We'd like a
+way to test these.
+
+WebTest is a Python package that does functional testing. With WebTest
+you can write tests which simulate a full HTTP request against a WSGI
+application, then test the information in the response. For speed
+purposes, WebTest skips the setup/teardown of an actual HTTP server,
+providing tests that run fast enough to be part of TDD.
+
+Objectives
+==========
+
+- Write a test which checks the contents of the returned HTML
+
+Steps
+=====
+
+#. First we copy the results of the previous step, as well as install
+ the ``webtest`` package:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r unit_testing functional_testing; cd functional_testing
+ $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/easy_install webtest
+
+#. Let's extend ``unit_testing/tutorial/tests.py`` to include a
+ functional test:
+
+ .. literalinclude:: functional_testing/tutorial/tests.py
+ :linenos:
+
+#. Now run the tests:
+
+ .. code-block:: bash
+
+
+ $ $VENV/bin/nosetests tutorial
+ .
+ ----------------------------------------------------------------------
+ Ran 2 tests in 0.141s
+
+ OK
+
+Analysis
+========
+
+We now have the end-to-end testing we were looking for. WebTest lets us
+simply extend our existing ``nose``-based test approach with functional
+tests that are reported in the same output. These new tests not only
+cover our templating, but they didn't dramatically increase the
+execution time of our tests.
+
+Extra Credit
+============
+
+#. Why do our functional tests use ``b''``? \ No newline at end of file
diff --git a/docs/quick_tutorial/functional_testing/development.ini b/docs/quick_tutorial/functional_testing/development.ini
new file mode 100644
index 000000000..470d92c57
--- /dev/null
+++ b/docs/quick_tutorial/functional_testing/development.ini
@@ -0,0 +1,40 @@
+[app:main]
+use = egg:tutorial
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/functional_testing/setup.py b/docs/quick_tutorial/functional_testing/setup.py
new file mode 100644
index 000000000..9997984d3
--- /dev/null
+++ b/docs/quick_tutorial/functional_testing/setup.py
@@ -0,0 +1,13 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/functional_testing/tutorial/__init__.py b/docs/quick_tutorial/functional_testing/tutorial/__init__.py
new file mode 100644
index 000000000..2b4e84f30
--- /dev/null
+++ b/docs/quick_tutorial/functional_testing/tutorial/__init__.py
@@ -0,0 +1,13 @@
+from pyramid.config import Configurator
+from pyramid.response import Response
+
+
+def hello_world(request):
+ return Response('<body><h1>Hello World!</h1></body>')
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.add_route('hello', '/')
+ config.add_view(hello_world, route_name='hello')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/functional_testing/tutorial/tests.py b/docs/quick_tutorial/functional_testing/tutorial/tests.py
new file mode 100644
index 000000000..4248acbe7
--- /dev/null
+++ b/docs/quick_tutorial/functional_testing/tutorial/tests.py
@@ -0,0 +1,31 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_hello_world(self):
+ from tutorial import hello_world
+
+ request = testing.DummyRequest()
+ response = hello_world(request)
+ self.assertEqual(response.status_code, 200)
+
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+ app = main({})
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def test_hello_world(self):
+ res = self.testapp.get('/', status=200)
+ self.assertIn(b'<h1>Hello World!</h1>', res.body)
diff --git a/docs/quick_tutorial/hello_world.rst b/docs/quick_tutorial/hello_world.rst
new file mode 100644
index 000000000..c7a8eaf5e
--- /dev/null
+++ b/docs/quick_tutorial/hello_world.rst
@@ -0,0 +1,114 @@
+.. _qtut_hello_world:
+
+================================
+01: Single-File Web Applications
+================================
+
+What's the simplest way to get started in Pyramid? A single-file module.
+No Python packages, no ``setup.py``, no other machinery.
+
+Background
+==========
+
+Microframeworks are all the rage these days. "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*.
+
+Pyramid is special because it can act as a single-file module
+microframework. You can have a single Python file that can be executed
+directly by Python. But Pyramid also provides facilities to scale to
+the largest of applications.
+
+Python has a standard called :term:`WSGI` that defines how
+Python web applications plug into standard servers, getting passed
+incoming requests and returning responses. Most modern Python web
+frameworks obey an "MVC" (model-view-controller) application pattern,
+where the data in the model has a view that mediates interaction with
+outside systems.
+
+In this step we'll see a brief glimpse of WSGI servers, WSGI
+applications, requests, responses, and views.
+
+Objectives
+==========
+
+- Get a running Pyramid web application, as simply as possible
+
+- Use that as a well-understood base for adding each unit of complexity
+
+- Initial exposure to WSGI apps, requests, views, and responses
+
+Steps
+=====
+
+#. Make sure you have followed the steps in :doc:`requirements`.
+
+#. Starting from your workspace directory
+ (``~/projects/quick_tutorial``), create a directory for this step:
+
+ .. code-block:: bash
+
+ $ mkdir hello_world; cd hello_world
+
+#. Copy the following into ``hello_world/app.py``:
+
+ .. literalinclude:: hello_world/app.py
+ :linenos:
+
+#. Run the application:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/python app.py
+
+#. Open http://localhost:6543/ in your browser.
+
+Analysis
+========
+
+New to Python web programming? If so, some lines in module merit
+explanation:
+
+#. *Line 11*. The ``if __name__ == '__main__':`` is Python's way of
+ saying "Start here when running from the command line".
+
+#. *Lines 12-14*. Use Pyramid's :term:`configurator` to connect
+ :term:`view` code to a particular URL
+ :term:`route`.
+
+#. *Lines 6-7*. Implement the view code that generates the
+ :term:`response`.
+
+#. *Lines 15-17*. Publish a :term:`WSGI` app using an HTTP
+ server.
+
+As shown in this example, the :term:`configurator` plays a
+central role in Pyramid development. Building an application from
+loosely-coupled parts via :ref:`configuration_narr` is a
+central idea in Pyramid, one that we will revisit regularly in this
+*Quick Tour*.
+
+Extra Credit
+============
+
+#. Why do we do this:
+
+ .. code-block:: python
+
+ print ('Starting up server on http://localhost:6547')
+
+ ...instead of:
+
+ .. code-block:: python
+
+ print 'Starting up server on http://localhost:6547'
+
+#. What happens if you return a string of HTML? A sequence of integers?
+
+#. Put something invalid, such as ``print xyz``, in the view function.
+ Kill your ``python app.py`` with ``cntrl-c`` and restart,
+ then reload your browser. See the exception in the console?
+
+#. The ``GI`` in ``WSGI`` stands for "Gateway Interface". What web
+ standard is this modelled after? \ No newline at end of file
diff --git a/docs/quick_tutorial/hello_world/app.py b/docs/quick_tutorial/hello_world/app.py
new file mode 100644
index 000000000..210075023
--- /dev/null
+++ b/docs/quick_tutorial/hello_world/app.py
@@ -0,0 +1,17 @@
+from wsgiref.simple_server import make_server
+from pyramid.config import Configurator
+from pyramid.response import Response
+
+
+def hello_world(request):
+ print ('Incoming request')
+ return Response('<body><h1>Hello World!</h1></body>')
+
+
+if __name__ == '__main__':
+ config = Configurator()
+ config.add_route('hello', '/')
+ config.add_view(hello_world, route_name='hello')
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 6543, app)
+ server.serve_forever() \ No newline at end of file
diff --git a/docs/quick_tutorial/index.rst b/docs/quick_tutorial/index.rst
new file mode 100644
index 000000000..9373fe38a
--- /dev/null
+++ b/docs/quick_tutorial/index.rst
@@ -0,0 +1,50 @@
+.. _quick_tutorial:
+
+==========================
+Quick Tutorial for Pyramid
+==========================
+
+Pyramid is a web framework for Python 2 and 3. This tutorial gives a
+Python 3/2-compatible, high-level tour of the major features.
+
+This hands-on tutorial covers "a little about a lot": practical
+introductions to the most common facilities. Fun, fast-paced, and most
+certainly not aimed at experts of the Pyramid web framework.
+
+Contents
+========
+
+.. toctree::
+ :maxdepth: 1
+
+ requirements
+ tutorial_approach
+ scaffolds
+ hello_world
+ package
+ ini
+ debugtoolbar
+ unit_testing
+ functional_testing
+ views
+ templating
+ view_classes
+ request_response
+ routing
+ jinja2
+ static_assets
+ json
+ more_view_classes
+ logging
+ sessions
+ forms
+ databases
+ authentication
+ authorization
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/quick_tutorial/ini.rst b/docs/quick_tutorial/ini.rst
new file mode 100644
index 000000000..630b1faa5
--- /dev/null
+++ b/docs/quick_tutorial/ini.rst
@@ -0,0 +1,146 @@
+.. _qtut_ini:
+
+=================================================
+03: Application Configuration with ``.ini`` Files
+=================================================
+
+Use Pyramid's ``pserve`` command with a ``.ini`` configuration file for
+simpler, better application running.
+
+Background
+==========
+
+Pyramid has a first-class concept of
+:ref:`configuration <configuration_narr>` distinct from code.
+This approach is optional, but its presence makes it distinct from
+other Python web frameworks. It taps into Python's ``setuptools``
+library, which establishes conventions for how Python projects can be
+installed and provide "entry points". Pyramid uses an entry point to
+let a Pyramid application it where to find the WSGI app.
+
+Objectives
+==========
+
+- Modify our ``setup.py`` to have an entry point telling Pyramid the
+ location of the WSGI app
+
+- Create an application driven by a ``.ini`` file
+
+- Startup the application with Pyramid's ``pserve`` command
+
+- Move code into the package's ``__init__.py``
+
+Steps
+=====
+
+#. First we copy the results of the previous step:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r package ini; cd ini
+
+#. Our ``ini/setup.py`` needs a setuptools "entry point" in the
+ ``setup()`` function:
+
+ .. literalinclude:: ini/setup.py
+ :linenos:
+
+#. We can now install our project, thus generating (or re-generating) an
+ "egg" at ``ini/tutorial.egg-info``:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/python setup.py develop
+
+#. Let's make a file ``ini/development.ini`` for our configuration:
+
+ .. literalinclude:: ini/development.ini
+ :language: ini
+ :linenos:
+
+#. We can refactor our startup code from the previous step's ``app.py``
+ into ``ini/tutorial/__init__.py``:
+
+ .. literalinclude:: ini/tutorial/__init__.py
+ :linenos:
+
+#. Now that ``ini/tutorial/app.py`` isn't used, let's remove it:
+
+ .. code-block:: bash
+
+ $ rm tutorial/app.py
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/.
+
+Analysis
+========
+
+Our ``development.ini`` file is read by ``pserve`` and serves to
+bootstrap our application. Processing then proceeds as described in
+the Pyramid chapter on
+:ref:`application startup <startup_chapter>`:
+
+- ``pserve`` looks for ``[app:main]`` and finds ``use = egg:tutorial``
+
+- The projects's ``setup.py`` has defined an "entry point" (lines 9-10)
+ for the project "main" entry point of ``tutorial:main``
+
+- The ``tutorial`` package's ``__init__`` has a ``main`` function
+
+- This function is invoked, with the values from certain ``.ini``
+ sections passed in
+
+The ``.ini`` file is also used for two other functions:
+
+- *Choice of WSGI server*. ``[server:main]`` wires up the choice of WSGI
+ *server* for your WSGI *application*. In this case, we are using
+ ``wsgiref`` bundled in the Python library.
+
+- *Python logging*. Pyramid uses Python standard logging, which needs a
+ number of configuration values. The ``.ini`` serves this function.
+ This provides the console log output that you see on startup and each
+ request.
+
+- *Port number*. ``port = 6543`` tells ``wsgiref`` to listen on port
+ 6543.
+
+We moved our startup code from ``app.py`` to the package's
+``tutorial/__init__.py``. This isn't necessary,
+but it is a common style in Pyramid to take the WSGI app bootstrapping
+out of your module's code and put it in the package's ``__init__.py``.
+
+The ``pserve`` application runner has a number of command-line arguments
+and options. We are using ``--reload`` which tells ``pserve`` to watch
+the filesystem for changes to relevant code (Python files, the INI file,
+etc.) and, when something changes, restart the application. Very handy
+during development.
+
+Extra Credit
+============
+
+#. If you don't like configuration and/or ``.ini`` files,
+ could you do this yourself in Python code?
+
+#. Can we have multiple ``.ini`` configuration files for a project? Why
+ might you want to do that?
+
+#. The entry point in ``setup.py`` didn't mention ``__init__.py`` when
+ it the ``main`` function. Why not?
+
+.. seealso::
+ :ref:`project_narr`,
+ :ref:`scaffolding_chapter`,
+ :ref:`what_is_this_pserve_thing`,
+ :ref:`environment_chapter`,
+ :ref:`paste_chapter`
+
+Extra Credit
+============
+
+#. What is the purpose of ``**settings``? What does the ``**`` signify?
diff --git a/docs/quick_tutorial/ini/development.ini b/docs/quick_tutorial/ini/development.ini
new file mode 100644
index 000000000..ca7d9bf81
--- /dev/null
+++ b/docs/quick_tutorial/ini/development.ini
@@ -0,0 +1,38 @@
+[app:main]
+use = egg:tutorial
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/ini/setup.py b/docs/quick_tutorial/ini/setup.py
new file mode 100644
index 000000000..9997984d3
--- /dev/null
+++ b/docs/quick_tutorial/ini/setup.py
@@ -0,0 +1,13 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/ini/tutorial/__init__.py b/docs/quick_tutorial/ini/tutorial/__init__.py
new file mode 100644
index 000000000..2b4e84f30
--- /dev/null
+++ b/docs/quick_tutorial/ini/tutorial/__init__.py
@@ -0,0 +1,13 @@
+from pyramid.config import Configurator
+from pyramid.response import Response
+
+
+def hello_world(request):
+ return Response('<body><h1>Hello World!</h1></body>')
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.add_route('hello', '/')
+ config.add_view(hello_world, route_name='hello')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/jinja2.rst b/docs/quick_tutorial/jinja2.rst
new file mode 100644
index 000000000..40d941098
--- /dev/null
+++ b/docs/quick_tutorial/jinja2.rst
@@ -0,0 +1,98 @@
+.. _qtut_jinja2:
+
+==============================
+12: Templating With ``jinja2``
+==============================
+
+We just said Pyramid doesn't prefer one templating language over
+another. Time to prove it. Jinja2 is a popular templating system,
+used in Flask and modelled after Django's templates. Let's add
+``pyramid_jinja2``, a Pyramid :term:`add-on` which enables Jinja2 as a
+:term:`renderer` in our Pyramid applications.
+
+Objectives
+==========
+
+- Show Pyramid's support for different templating systems
+
+- Learn about installing Pyramid add-ons
+
+Steps
+=====
+
+#. In this step let's start by installing the ``pyramid_jinja2``
+ add-on, the copying the ``view_class`` step's directory:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r view_classes jinja2; cd jinja2
+ $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/easy_install pyramid_jinja2
+
+#. We need to add an item to ``pyramid.includes`` in
+ ``jinja2/development.ini``:
+
+ .. literalinclude:: jinja2/development.ini
+ :language: ini
+ :linenos:
+
+#. Our ``jinja2/tutorial/views.py`` simply changes its ``renderer``:
+
+ .. literalinclude:: jinja2/tutorial/views.py
+ :linenos:
+
+#. Add ``jinja2/tutorial/home.jinja2`` as a template:
+
+ .. literalinclude:: jinja2/tutorial/home.jinja2
+ :language: html
+
+#. Get the ``pyramid.includes`` into the functional test setup in
+ ``jinja2/tutorial/tests.py``:
+
+ .. literalinclude:: jinja2/tutorial/tests.py
+ :linenos:
+
+#. Now run the tests:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/nosetests tutorial
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ in your browser.
+
+Analysis
+========
+
+Getting a Pyramid add-on into Pyramid is simple. First you use normal
+Python package installation tools to install the add-on package into
+your Python. You then tell Pyramid's configurator to run the setup code
+in the add-on. In this case the setup code told Pyramid to make a new
+"renderer" available that looked for ``.jinja2`` file extensions.
+
+Our view code stayed largely the same. We simply changed the file
+extension on the renderer. For the template, the syntax for Chameleon
+and Jinja2's basic variable insertion is very similar.
+
+Our functional tests don't have ``development.ini`` so they needed the
+``pyramid.includes`` to be setup in the test setup.
+
+Extra Credit
+============
+
+#. Our project now depends on ``pyramid_jinja2``. We installed that
+ dependency manually. What is another way we could have made the
+ association?
+
+#. We used ``development.ini`` to get the :term:`configurator` to
+ load ``pyramid_jinja2``'s configuration. What is another way could
+ include it into the config?
+
+.. seealso:: `Jinja2 homepage <http://jinja.pocoo.org/>`_,
+ and
+ :ref:`pyramid_jinja2 Overview <jinja2:overview>`
diff --git a/docs/quick_tutorial/jinja2/development.ini b/docs/quick_tutorial/jinja2/development.ini
new file mode 100644
index 000000000..c096fa936
--- /dev/null
+++ b/docs/quick_tutorial/jinja2/development.ini
@@ -0,0 +1,42 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+ pyramid_jinja2
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/jinja2/setup.py b/docs/quick_tutorial/jinja2/setup.py
new file mode 100644
index 000000000..9997984d3
--- /dev/null
+++ b/docs/quick_tutorial/jinja2/setup.py
@@ -0,0 +1,13 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/jinja2/tutorial/__init__.py b/docs/quick_tutorial/jinja2/tutorial/__init__.py
new file mode 100644
index 000000000..013d4538f
--- /dev/null
+++ b/docs/quick_tutorial/jinja2/tutorial/__init__.py
@@ -0,0 +1,9 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.add_route('home', '/')
+ config.add_route('hello', '/howdy')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/jinja2/tutorial/home.jinja2 b/docs/quick_tutorial/jinja2/tutorial/home.jinja2
new file mode 100644
index 000000000..975323169
--- /dev/null
+++ b/docs/quick_tutorial/jinja2/tutorial/home.jinja2
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: {{ name }}</title>
+</head>
+<body>
+<h1>Hi {{ name }}</h1>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/jinja2/tutorial/tests.py b/docs/quick_tutorial/jinja2/tutorial/tests.py
new file mode 100644
index 000000000..0b22946f3
--- /dev/null
+++ b/docs/quick_tutorial/jinja2/tutorial/tests.py
@@ -0,0 +1,50 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.home()
+ self.assertEqual('Home View', response['name'])
+
+ def test_hello(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.hello()
+ self.assertEqual('Hello View', response['name'])
+
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+
+ settings = {
+ 'pyramid.includes': [
+ 'pyramid_jinja2'
+ ]
+ }
+ app = main({}, **settings)
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def test_home(self):
+ res = self.testapp.get('/', status=200)
+ self.assertIn(b'<h1>Hi Home View', res.body)
+
+ def test_hello(self):
+ res = self.testapp.get('/howdy', status=200)
+ self.assertIn(b'<h1>Hi Hello View', res.body)
diff --git a/docs/quick_tutorial/jinja2/tutorial/views.py b/docs/quick_tutorial/jinja2/tutorial/views.py
new file mode 100644
index 000000000..fa9ec5121
--- /dev/null
+++ b/docs/quick_tutorial/jinja2/tutorial/views.py
@@ -0,0 +1,18 @@
+from pyramid.view import (
+ view_config,
+ view_defaults
+ )
+
+
+@view_defaults(renderer='home.jinja2')
+class TutorialViews:
+ def __init__(self, request):
+ self.request = request
+
+ @view_config(route_name='home')
+ def home(self):
+ return {'name': 'Home View'}
+
+ @view_config(route_name='hello')
+ def hello(self):
+ return {'name': 'Hello View'}
diff --git a/docs/quick_tutorial/json.rst b/docs/quick_tutorial/json.rst
new file mode 100644
index 000000000..ece8a61c0
--- /dev/null
+++ b/docs/quick_tutorial/json.rst
@@ -0,0 +1,103 @@
+.. _qtut_json:
+
+========================================
+14: Ajax Development With JSON Renderers
+========================================
+
+Modern web apps are more than rendered HTML. Dynamic pages now use
+JavaScript to update the UI in the browser by requesting server data as
+JSON. Pyramid supports this with a *JSON renderer*.
+
+Background
+==========
+
+As we saw in :doc:`templating`, view declarations can specify a
+renderer. Output from the view is then run through the renderer,
+which generates and returns the ``Response``. We first used a Chameleon
+renderer, then a Jinja2 renderer.
+
+Renderers aren't limited, however, to templates that generate HTML.
+Pyramid supplies a JSON renderer which takes Python data,
+serializes it to JSON, and performs some other functions such as
+setting the content type. In fact, you can write your own renderer (or
+extend a built-in renderer) containing custom logic for your unique
+application.
+
+Steps
+=====
+
+#. First we copy the results of the ``view_classes`` step:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r view_classes json; cd json
+ $ $VENV/bin/python setup.py develop
+
+#. We add a new route for ``hello_json`` in
+ ``json/tutorial/__init__.py``:
+
+ .. literalinclude:: json/tutorial/__init__.py
+ :linenos:
+
+#. Rather than implement a new view, we will "stack" another decorator
+ on the ``hello`` view:
+
+ .. literalinclude:: json/tutorial/views.py
+ :linenos:
+
+#. We need a new functional test at the end of
+ ``json/tutorial/tests.py``:
+
+ .. literalinclude:: json/tutorial/tests.py
+ :linenos:
+
+#. Run the tests:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/nosetests tutorial
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/howdy.json in your browser and you
+ will see the resulting JSON response.
+
+Analysis
+========
+
+Earlier we changed our view functions and methods to return Python
+data. This change to a data-oriented view layer made test writing
+easier, decoupling the templating from the view logic.
+
+Since Pyramid has a JSON renderer as well as the templating renderers,
+it is an easy step to return JSON. In this case we kept the exact same
+view and arranged to return a JSON encoding of the view data. We did
+this by:
+
+- Adding a route to map ``/howdy.json`` to a route name
+
+- Providing a ``@view_config`` that associated that route name with an
+ existing view
+
+- *overriding* the view defaults in the view config that mentions the
+ ``hello_json`` route, so that when the route is matched, we use the JSON
+ renderer rather than the ``home.pt`` template renderer that would otherwise
+ be used.
+
+In fact, for pure Ajax-style web applications, we could re-use the existing
+route by using Pyramid's view predicates to match on the
+``Accepts:`` header sent by modern Ajax implementation.
+
+Pyramid's JSON renderer uses the base Python JSON encoder,
+thus inheriting its strengths and weaknesses. For example,
+Python can't natively JSON encode DateTime objects. There are a number
+of solutions for this in Pyramid, including extending the JSON renderer
+with a custom renderer.
+
+.. seealso:: :ref:`views_which_use_a_renderer`,
+ :ref:`json_renderer`, and
+ :ref:`adding_and_overriding_renderers`
diff --git a/docs/quick_tutorial/json/development.ini b/docs/quick_tutorial/json/development.ini
new file mode 100644
index 000000000..62e0c5123
--- /dev/null
+++ b/docs/quick_tutorial/json/development.ini
@@ -0,0 +1,41 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/json/setup.py b/docs/quick_tutorial/json/setup.py
new file mode 100644
index 000000000..2221b72e9
--- /dev/null
+++ b/docs/quick_tutorial/json/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon'
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/json/tutorial/__init__.py b/docs/quick_tutorial/json/tutorial/__init__.py
new file mode 100644
index 000000000..6652544c3
--- /dev/null
+++ b/docs/quick_tutorial/json/tutorial/__init__.py
@@ -0,0 +1,11 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_route('home', '/')
+ config.add_route('hello', '/howdy')
+ config.add_route('hello_json', 'howdy.json')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/json/tutorial/home.pt b/docs/quick_tutorial/json/tutorial/home.pt
new file mode 100644
index 000000000..a0cc08e7a
--- /dev/null
+++ b/docs/quick_tutorial/json/tutorial/home.pt
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${name}</title>
+</head>
+<body>
+<h1>Hi ${name}</h1>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/json/tutorial/tests.py b/docs/quick_tutorial/json/tutorial/tests.py
new file mode 100644
index 000000000..c3cdacbdb
--- /dev/null
+++ b/docs/quick_tutorial/json/tutorial/tests.py
@@ -0,0 +1,50 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.home()
+ self.assertEqual('Home View', response['name'])
+
+ def test_hello(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.hello()
+ self.assertEqual('Hello View', response['name'])
+
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+ app = main({})
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def test_home(self):
+ res = self.testapp.get('/', status=200)
+ self.assertIn(b'<h1>Hi Home View', res.body)
+
+ def test_hello(self):
+ res = self.testapp.get('/howdy', status=200)
+ self.assertIn(b'<h1>Hi Hello View', res.body)
+
+ def test_hello_json(self):
+ res = self.testapp.get('/howdy.json', status=200)
+ self.assertIn(b'{"name": "Hello View"}', res.body)
+ self.assertEqual(res.content_type, 'application/json')
+
diff --git a/docs/quick_tutorial/json/tutorial/views.py b/docs/quick_tutorial/json/tutorial/views.py
new file mode 100644
index 000000000..f15e55d1b
--- /dev/null
+++ b/docs/quick_tutorial/json/tutorial/views.py
@@ -0,0 +1,19 @@
+from pyramid.view import (
+ view_config,
+ view_defaults
+ )
+
+
+@view_defaults(renderer='home.pt')
+class TutorialViews:
+ def __init__(self, request):
+ self.request = request
+
+ @view_config(route_name='home')
+ def home(self):
+ return {'name': 'Home View'}
+
+ @view_config(route_name='hello')
+ @view_config(route_name='hello_json', renderer='json')
+ def hello(self):
+ return {'name': 'Hello View'}
diff --git a/docs/quick_tutorial/logging.rst b/docs/quick_tutorial/logging.rst
new file mode 100644
index 000000000..0167e5249
--- /dev/null
+++ b/docs/quick_tutorial/logging.rst
@@ -0,0 +1,79 @@
+.. _qtut_logging:
+
+============================================
+16: Collecting Application Info With Logging
+============================================
+
+Capture debugging and error output from your web applications using
+standard Python logging.
+
+Background
+==========
+
+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 generated, in your ``development.ini``, 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.)
+
+Objectives
+==========
+
+- Inspect the configuration setup used for logging
+
+- Add logging statements to your view code
+
+Steps
+=====
+
+#. First we copy the results of the ``view_classes`` step:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r view_classes logging; cd logging
+ $ $VENV/bin/python setup.py develop
+
+#. Extend ``logging/tutorial/views.py`` to log a message:
+
+ .. literalinclude:: logging/tutorial/views.py
+ :linenos:
+
+#. Make sure the tests still pass:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/nosetests tutorial
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ and http://localhost:6543/howdy
+ in your browser. Note, both in the console and in the debug
+ toolbar, the message that you logged.
+
+Analysis
+========
+
+Our ``development.ini`` configuration file wires up Python standard
+logging for our Pyramid application:
+
+.. literalinclude:: logging/development.ini
+ :language: ini
+
+In this, our ``tutorial`` Python package is setup as a logger
+and configured to log messages at a ``DEBUG`` or higher level. When you
+visit http://localhost:6543 your console will now show::
+
+ 2013-08-09 10:42:42,968 DEBUG [tutorial.views][MainThread] In home view
+
+Also, if you have configured your Pyramid application to use the
+``pyramid_debugtoolbar``, logging statements appear in one of its menus.
+
+.. seealso:: See Also: :ref:`logging_chapter`
diff --git a/docs/quick_tutorial/logging/development.ini b/docs/quick_tutorial/logging/development.ini
new file mode 100644
index 000000000..62e0c5123
--- /dev/null
+++ b/docs/quick_tutorial/logging/development.ini
@@ -0,0 +1,41 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/logging/setup.py b/docs/quick_tutorial/logging/setup.py
new file mode 100644
index 000000000..2221b72e9
--- /dev/null
+++ b/docs/quick_tutorial/logging/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon'
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/logging/tutorial/__init__.py b/docs/quick_tutorial/logging/tutorial/__init__.py
new file mode 100644
index 000000000..c3e1c9eef
--- /dev/null
+++ b/docs/quick_tutorial/logging/tutorial/__init__.py
@@ -0,0 +1,10 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_route('home', '/')
+ config.add_route('hello', '/howdy')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/logging/tutorial/home.pt b/docs/quick_tutorial/logging/tutorial/home.pt
new file mode 100644
index 000000000..a0cc08e7a
--- /dev/null
+++ b/docs/quick_tutorial/logging/tutorial/home.pt
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${name}</title>
+</head>
+<body>
+<h1>Hi ${name}</h1>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/logging/tutorial/tests.py b/docs/quick_tutorial/logging/tutorial/tests.py
new file mode 100644
index 000000000..4381235ec
--- /dev/null
+++ b/docs/quick_tutorial/logging/tutorial/tests.py
@@ -0,0 +1,44 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.home()
+ self.assertEqual('Home View', response['name'])
+
+ def test_hello(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.hello()
+ self.assertEqual('Hello View', response['name'])
+
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+ app = main({})
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def test_home(self):
+ res = self.testapp.get('/', status=200)
+ self.assertIn(b'<h1>Hi Home View', res.body)
+
+ def test_hello(self):
+ res = self.testapp.get('/howdy', status=200)
+ self.assertIn(b'<h1>Hi Hello View', res.body)
diff --git a/docs/quick_tutorial/logging/tutorial/views.py b/docs/quick_tutorial/logging/tutorial/views.py
new file mode 100644
index 000000000..63d95f405
--- /dev/null
+++ b/docs/quick_tutorial/logging/tutorial/views.py
@@ -0,0 +1,23 @@
+import logging
+log = logging.getLogger(__name__)
+
+from pyramid.view import (
+ view_config,
+ view_defaults
+ )
+
+
+@view_defaults(renderer='home.pt')
+class TutorialViews:
+ def __init__(self, request):
+ self.request = request
+
+ @view_config(route_name='home')
+ def home(self):
+ log.debug('In home view')
+ return {'name': 'Home View'}
+
+ @view_config(route_name='hello')
+ def hello(self):
+ log.debug('In hello view')
+ return {'name': 'Hello View'}
diff --git a/docs/quick_tutorial/more_view_classes.rst b/docs/quick_tutorial/more_view_classes.rst
new file mode 100644
index 000000000..2792869ac
--- /dev/null
+++ b/docs/quick_tutorial/more_view_classes.rst
@@ -0,0 +1,182 @@
+.. _qtut_more_view_classes:
+
+==========================
+15: More With View Classes
+==========================
+
+Group views into a class, sharing configuration, state, and logic.
+
+Background
+==========
+
+As part of its mission to help build more ambitious web applications,
+Pyramid provides many more features for views and view classes.
+
+The Pyramid documentation discusses views as a Python "callable". This
+callable can be a function, an object with an ``__call__``,
+or a Python class. In this last case, methods on the class can be
+decorated with ``@view_config`` to register the class methods with the
+:term:`configurator` as a view.
+
+So far our views have been simple, free-standing functions. Many times
+your views are related: different ways to look at or work on the same
+data or a REST API that handles multiple operations. Grouping these
+together as a
+:ref:`view class <class_as_view>` makes sense:
+
+- Group views
+
+- Centralize some repetitive defaults
+
+- Share some state and helpers
+
+Pyramid views have
+:ref:`view predicates <view_configuration_parameters>` that
+help determine which view is matched to a request. These predicates
+provide many axes of flexibility.
+
+The following shows a simple example with four operations operations:
+view a home page which leads to a form, save a change,
+and press the delete button.
+
+Objectives
+==========
+
+- Group related views into a view class
+
+- Centralize configuration with class-level ``@view_defaults``
+
+- Dispatch one route/URL to multiple views based on request data
+
+- Share stated and logic between views and templates via the view class
+
+Steps
+=====
+
+#. First we copy the results of the previous step:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r templating more_view_classes; cd more_view_classes
+ $ $VENV/bin/python setup.py develop
+
+#. Our route in ``more_view_classes/tutorial/__init__.py`` needs some
+ replacement patterns:
+
+ .. literalinclude:: more_view_classes/tutorial/__init__.py
+ :linenos:
+
+#. Our ``more_view_classes/tutorial/views.py`` now has a view class with
+ several views:
+
+ .. literalinclude:: more_view_classes/tutorial/views.py
+ :linenos:
+
+#. Our primary view needs a template at
+ ``more_view_classes/tutorial/home.pt``:
+
+ .. literalinclude:: more_view_classes/tutorial/home.pt
+ :language: html
+
+#. Ditto for our other view from the previous section at
+ ``more_view_classes/tutorial/hello.pt``:
+
+ .. literalinclude:: more_view_classes/tutorial/hello.pt
+ :language: html
+
+#. We have an edit view that also needs a template at
+ ``more_view_classes/tutorial/edit.pt``:
+
+ .. literalinclude:: more_view_classes/tutorial/edit.pt
+ :language: html
+
+#. And finally the delete view's template at
+ ``more_view_classes/tutorial/delete.pt``:
+
+ .. literalinclude:: more_view_classes/tutorial/delete.pt
+ :language: html
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/howdy/jane/doe in your browser. Click
+ the ``Save`` and ``Delete`` buttons and watch the output in the
+ console window.
+
+Analysis
+========
+
+As you can see, the four views are logically grouped together.
+Specifically:
+
+- We have a ``home`` view available at http://localhost:6543/ with
+ a clickable link to the ``hello`` view.
+
+- The second view is returned when you go to ``/howdy/jane/doe``. This
+ URL is
+ mapped to the ``hello`` route that we centrally set using the optional
+ ``@view_defaults``.
+
+- The third view is returned when the form is submitted with a ``POST``
+ method. This rule is specified in the ``@view_config`` for that view.
+
+- The fourth view is returned when clicking on a button such
+ as ``<input type="submit" name="form.delete" value="Delete"/>``.
+
+In this step we show using the following information as criteria to
+decide which view to use:
+
+- Method of the HTTP request (``GET``, ``POST``, etc.)
+
+- Parameter information in the request (submitted form field names)
+
+We also centralize part of the view configuration to the class level
+with ``@view_defaults``, then in one view, override that default just
+for that one view. Finally, we put this commonality between views to
+work in the view class by sharing:
+
+- State assigned in ``TutorialViews.__init__``
+
+- A computed value
+
+These are then available both in the view methods but also in the
+templates (e.g. ``${view.view_name}`` and ``${view.full_name}``.
+
+As a note, we made a switch in our templates on how we generate URLs.
+We previously hardcode the URLs, such as::
+
+ <a href="/howdy/jane/doe">Howdy</a>
+
+In ``home.pt`` we switched to::
+
+ <a href="${request.route_url('hello', first='jane',
+ last='doe')}">form</a>
+
+Pyramid has rich facilities to help generate URLs in a flexible,
+non-error-prone fashion.
+
+Extra Credit
+============
+
+#. Why could our template do ``${view.full_name}`` and not have to do
+ ``${view.full_name()}``?
+
+#. The ``edit`` and ``delete`` views are both submitted to with
+ ``POST``. Why does the ``edit`` view configuration not catch the
+ the ``POST`` used by ``delete``?
+
+#. We used Python ``@property`` on ``full_name``. If we reference this
+ many times in a template or view code, it would re-compute this
+ every time. Does Pyramid provide something that will cache the initial
+ computation on a property?
+
+#. Can you associate more than one route with the same view?
+
+#. There is also a ``request.route_path`` API. How does this differ from
+ ``request.route_url``?
+
+.. seealso:: :ref:`class_as_view`, `Weird Stuff You Can Do With
+ URL Dispatch <http://www.plope.com/weird_pyramid_urldispatch>`_
diff --git a/docs/quick_tutorial/more_view_classes/development.ini b/docs/quick_tutorial/more_view_classes/development.ini
new file mode 100644
index 000000000..62e0c5123
--- /dev/null
+++ b/docs/quick_tutorial/more_view_classes/development.ini
@@ -0,0 +1,41 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/more_view_classes/setup.py b/docs/quick_tutorial/more_view_classes/setup.py
new file mode 100644
index 000000000..2221b72e9
--- /dev/null
+++ b/docs/quick_tutorial/more_view_classes/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon'
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/more_view_classes/tutorial/__init__.py b/docs/quick_tutorial/more_view_classes/tutorial/__init__.py
new file mode 100644
index 000000000..9c1bcec06
--- /dev/null
+++ b/docs/quick_tutorial/more_view_classes/tutorial/__init__.py
@@ -0,0 +1,10 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_route('home', '/')
+ config.add_route('hello', '/howdy/{first}/{last}')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/more_view_classes/tutorial/delete.pt b/docs/quick_tutorial/more_view_classes/tutorial/delete.pt
new file mode 100644
index 000000000..67cc8bf09
--- /dev/null
+++ b/docs/quick_tutorial/more_view_classes/tutorial/delete.pt
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${page_title}</title>
+</head>
+<body>
+<h1>${view.view_name} - ${page_title}</h1>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/more_view_classes/tutorial/edit.pt b/docs/quick_tutorial/more_view_classes/tutorial/edit.pt
new file mode 100644
index 000000000..1bd204065
--- /dev/null
+++ b/docs/quick_tutorial/more_view_classes/tutorial/edit.pt
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${view.view_name} - ${page_title}</title>
+</head>
+<body>
+<h1>${view.view_name} - ${page_title}</h1>
+<p>You submitted <code>${new_name}</code></p>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/more_view_classes/tutorial/hello.pt b/docs/quick_tutorial/more_view_classes/tutorial/hello.pt
new file mode 100644
index 000000000..8a39aed09
--- /dev/null
+++ b/docs/quick_tutorial/more_view_classes/tutorial/hello.pt
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${view.view_name} - ${page_title}</title>
+</head>
+<body>
+<h1>${view.view_name} - ${page_title}</h1>
+<p>Welcome, ${view.full_name}</p>
+<form method="POST"
+ action="${request.current_route_url()}">
+ <input name="new_name"/>
+ <input type="submit" name="form.edit" value="Save"/>
+ <input type="submit" name="form.delete" value="Delete"/>
+</form>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/more_view_classes/tutorial/home.pt b/docs/quick_tutorial/more_view_classes/tutorial/home.pt
new file mode 100644
index 000000000..fa9016705
--- /dev/null
+++ b/docs/quick_tutorial/more_view_classes/tutorial/home.pt
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${view.view_name} - ${page_title}</title>
+</head>
+<body>
+<h1>${view.view_name} - ${page_title}</h1>
+
+<p>Go to the <a href="${request.route_url('hello', first='jane',
+ last='doe')}">form</a>.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/more_view_classes/tutorial/tests.py b/docs/quick_tutorial/more_view_classes/tutorial/tests.py
new file mode 100644
index 000000000..dca8d7f7b
--- /dev/null
+++ b/docs/quick_tutorial/more_view_classes/tutorial/tests.py
@@ -0,0 +1,31 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.home()
+ self.assertEqual('Home View', response['page_title'])
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+ app = main({})
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def test_home(self):
+ res = self.testapp.get('/', status=200)
+ self.assertIn(b'TutorialViews - Home View', res.body)
diff --git a/docs/quick_tutorial/more_view_classes/tutorial/views.py b/docs/quick_tutorial/more_view_classes/tutorial/views.py
new file mode 100644
index 000000000..fdba04ba8
--- /dev/null
+++ b/docs/quick_tutorial/more_view_classes/tutorial/views.py
@@ -0,0 +1,39 @@
+from pyramid.view import (
+ view_config,
+ view_defaults
+ )
+
+
+@view_defaults(route_name='hello')
+class TutorialViews:
+ def __init__(self, request):
+ self.request = request
+ self.view_name = 'TutorialViews'
+
+ @property
+ def full_name(self):
+ first = self.request.matchdict['first']
+ last = self.request.matchdict['last']
+ return first + ' ' + last
+
+ @view_config(route_name='home', renderer='home.pt')
+ def home(self):
+ return {'page_title': 'Home View'}
+
+
+ # Retrieving /howdy/first/last the first time
+ @view_config(renderer='hello.pt')
+ def hello(self):
+ return {'page_title': 'Hello View'}
+
+ # Posting to /home via the "Edit" submit button
+ @view_config(request_method='POST', renderer='edit.pt')
+ def edit(self):
+ new_name = self.request.params['new_name']
+ return {'page_title': 'Edit View', 'new_name': new_name}
+
+ # Posting to /home via the "Delete" submit button
+ @view_config(request_param='form.delete', renderer='delete.pt')
+ def delete(self):
+ print ('Deleted')
+ return {'page_title': 'Delete View'}
diff --git a/docs/quick_tutorial/package.rst b/docs/quick_tutorial/package.rst
new file mode 100644
index 000000000..90d022b29
--- /dev/null
+++ b/docs/quick_tutorial/package.rst
@@ -0,0 +1,112 @@
+============================================
+02: Python Packages for Pyramid Applications
+============================================
+
+Most modern Python development is done using Python packages, an approach
+Pyramid puts to good use. In this step we re-do "Hello World" as a
+minimum Python package inside a minimum Python project.
+
+Background
+==========
+
+Python developers can organize a collection of modules and files into a
+namespaced unit called a :ref:`package <python:tut-packages>`. If a
+directory is on ``sys.path`` and has a special file named
+``__init__.py``, it is treated as a Python package.
+
+Packages can be bundled up, made available for installation,
+and installed through a (muddled, but improving) toolchain oriented
+around a ``setup.py`` file for a
+`setuptools project <http://pythonhosted.org/setuptools/setuptools.html>`_.
+Explaining it all in this
+tutorial will induce madness. For this tutorial, this is all you need to
+know:
+
+- We will have a directory for each tutorial step as a
+ setuptools *project*
+
+- This project will contain a ``setup.py`` which injects the features
+ of the setuptool's project machinery into the directory
+
+- In this project we will make a ``tutorial`` subdirectory into a Python
+ *package* using an ``__init__.py`` Python module file
+
+- We will run ``python setup.py develop`` to install our project in
+ development mode
+
+In summary:
+
+- You'll do your development in a Python *package*
+
+- That package will be part of a setuptools *project*
+
+Objectives
+==========
+
+- Make a Python "package" directory with an ``__init__.py``
+
+- Get a minimum Python "project" in place by making a ``setup.py``
+
+- Install our ``tutorial`` project in development mode
+
+Steps
+=====
+
+#. Make an area for this tutorial step:
+
+ .. code-block:: bash
+
+ $ cd ..; mkdir package; cd package
+
+#. In ``package/setup.py``, enter the following:
+
+ .. literalinclude:: package/setup.py
+
+#. Make the new project installed for development then make a directory
+ for the actual code:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/python setup.py develop
+ $ mkdir tutorial
+
+#. Enter the following into ``package/tutorial/__init__.py``:
+
+ .. literalinclude:: package/tutorial/__init__.py
+
+#. Enter the following into ``package/tutorial/app.py``:
+
+ .. literalinclude:: package/tutorial/app.py
+
+#. Run the WSGI application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/python tutorial/app.py
+
+#. Open http://localhost:6543/ in your browser.
+
+Analysis
+========
+
+Python packages give us an organized unit of project development.
+Python projects, via ``setup.py``, gives us special features when
+our package is installed (in this case, in local development mode.)
+
+In this step we have a Python package called ``tutorial``. We use the
+same name in each step of the tutorial, to avoid unnecessary re-typing.
+
+Above this ``tutorial`` directory we have the files that handle the
+packaging of this, well, package. At the moment, all we need is a
+bare-bones ``ini/setup.py``.
+
+Everything else is the same about our application. We simply made a
+Python package with a ``setup.py`` and installed it in development mode.
+
+Note that the way we're running the app (``python tutorial/app.py``) is a bit
+of an odd duck. We would never do this unless we were writing a tutorial that
+tries to capture how this stuff works a step at a time. It's generally a bad
+idea to run a Python module inside a package directly as a script.
+
+.. seealso:: :ref:`Python Packages <python:tut-packages>`,
+ `setuptools Entry Points <http://pythonhosted.org/setuptools/pkg_resources.html#entry-points>`_
diff --git a/docs/quick_tutorial/package/setup.py b/docs/quick_tutorial/package/setup.py
new file mode 100644
index 000000000..bcfcfa684
--- /dev/null
+++ b/docs/quick_tutorial/package/setup.py
@@ -0,0 +1,9 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/package/tutorial/__init__.py b/docs/quick_tutorial/package/tutorial/__init__.py
new file mode 100644
index 000000000..d310fdde9
--- /dev/null
+++ b/docs/quick_tutorial/package/tutorial/__init__.py
@@ -0,0 +1 @@
+# package \ No newline at end of file
diff --git a/docs/quick_tutorial/package/tutorial/app.py b/docs/quick_tutorial/package/tutorial/app.py
new file mode 100644
index 000000000..210075023
--- /dev/null
+++ b/docs/quick_tutorial/package/tutorial/app.py
@@ -0,0 +1,17 @@
+from wsgiref.simple_server import make_server
+from pyramid.config import Configurator
+from pyramid.response import Response
+
+
+def hello_world(request):
+ print ('Incoming request')
+ return Response('<body><h1>Hello World!</h1></body>')
+
+
+if __name__ == '__main__':
+ config = Configurator()
+ config.add_route('hello', '/')
+ config.add_view(hello_world, route_name='hello')
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 6543, app)
+ server.serve_forever() \ No newline at end of file
diff --git a/docs/quick_tutorial/request_response.rst b/docs/quick_tutorial/request_response.rst
new file mode 100644
index 000000000..504803804
--- /dev/null
+++ b/docs/quick_tutorial/request_response.rst
@@ -0,0 +1,103 @@
+.. _qtut_request_response:
+
+=======================================
+10: Handling Web Requests and Responses
+=======================================
+
+Web applications handle incoming requests and return outgoing responses.
+Pyramid makes working with requests and responses convenient and
+reliable.
+
+Objectives
+==========
+
+- Learn the background on Pyramid's choices for requests and responses
+
+- Grab data out of the request
+
+- Change information in the response headers
+
+Background
+==========
+
+Developing for the web means processing web requests. As this is a
+critical 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, etc.) For request handling, 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>`.
+
+Steps
+=====
+
+#. First we copy the results of the ``view_classes`` step:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r view_classes request_response; cd request_response
+ $ $VENV/bin/python setup.py develop
+
+#. Simplify the routes in ``request_response/tutorial/__init__.py``:
+
+ .. literalinclude:: request_response/tutorial/__init__.py
+
+#. We only need one view in ``request_response/tutorial/views.py``:
+
+ .. literalinclude:: request_response/tutorial/views.py
+
+#. Update the tests in ``request_response/tutorial/tests.py``:
+
+ .. literalinclude:: request_response/tutorial/tests.py
+
+#. Now run the tests:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/nosetests tutorial
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ in your browser. You will be
+ redirected to http://localhost:6543/plain
+
+#. Open http://localhost:6543/plain?name=alice in your browser.
+
+Analysis
+========
+
+In this view class we have two routes and two views, with the first
+leading to the second by an HTTP redirect. Pyramid can
+:ref:`generate redirects <http_redirect>` by returning a
+special object from a view or raising a special exception.
+
+In this Pyramid view, we get the URL being visited from ``request.url``.
+Also, if you visited http://localhost:6543/plain?name=alice,
+the name is included in the body of the response::
+
+ URL http://localhost:6543/plain?name=alice with name: alice
+
+Finally, we set the response's content type and body, then return the
+Response.
+
+We updated the unit and functional tests to prove that our code
+does the redirection, but also handles sending and not sending
+``/plain?name``.
+
+Extra Credit
+============
+
+#. Could we also ``raise HTTPFound(location='/plain')`` instead of
+ returning it? If so, what's the difference?
+
+.. seealso:: :ref:`webob_chapter`,
+ :ref:`generate redirects <http_redirect>`
diff --git a/docs/quick_tutorial/request_response/development.ini b/docs/quick_tutorial/request_response/development.ini
new file mode 100644
index 000000000..62e0c5123
--- /dev/null
+++ b/docs/quick_tutorial/request_response/development.ini
@@ -0,0 +1,41 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/request_response/setup.py b/docs/quick_tutorial/request_response/setup.py
new file mode 100644
index 000000000..9997984d3
--- /dev/null
+++ b/docs/quick_tutorial/request_response/setup.py
@@ -0,0 +1,13 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/request_response/tutorial/__init__.py b/docs/quick_tutorial/request_response/tutorial/__init__.py
new file mode 100644
index 000000000..77a172888
--- /dev/null
+++ b/docs/quick_tutorial/request_response/tutorial/__init__.py
@@ -0,0 +1,9 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.add_route('home', '/')
+ config.add_route('plain', '/plain')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/request_response/tutorial/tests.py b/docs/quick_tutorial/request_response/tutorial/tests.py
new file mode 100644
index 000000000..7486c2b2d
--- /dev/null
+++ b/docs/quick_tutorial/request_response/tutorial/tests.py
@@ -0,0 +1,54 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.home()
+ self.assertEqual(response.status, '302 Found')
+
+ def test_plain_without_name(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.plain()
+ self.assertIn(b'No Name Provided', response.body)
+
+ def test_plain_with_name(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ request.GET['name'] = 'Jane Doe'
+ inst = TutorialViews(request)
+ response = inst.plain()
+ self.assertIn(b'Jane Doe', response.body)
+
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+
+ app = main({})
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def test_plain_without_name(self):
+ res = self.testapp.get('/plain', status=200)
+ self.assertIn(b'No Name Provided', res.body)
+
+ def test_plain_with_name(self):
+ res = self.testapp.get('/plain?name=Jane%20Doe', status=200)
+ self.assertIn(b'Jane Doe', res.body)
diff --git a/docs/quick_tutorial/request_response/tutorial/views.py b/docs/quick_tutorial/request_response/tutorial/views.py
new file mode 100644
index 000000000..8c7ff5f37
--- /dev/null
+++ b/docs/quick_tutorial/request_response/tutorial/views.py
@@ -0,0 +1,22 @@
+from pyramid.httpexceptions import HTTPFound
+from pyramid.response import Response
+from pyramid.view import view_config
+
+
+class TutorialViews:
+ def __init__(self, request):
+ self.request = request
+
+ @view_config(route_name='home')
+ def home(self):
+ return HTTPFound(location='/plain')
+
+ @view_config(route_name='plain')
+ def plain(self):
+ name = self.request.params.get('name', 'No Name Provided')
+
+ body = 'URL %s with name: %s' % (self.request.url, name)
+ return Response(
+ content_type='text/plain',
+ body=body
+ )
diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst
new file mode 100644
index 000000000..40e818807
--- /dev/null
+++ b/docs/quick_tutorial/requirements.rst
@@ -0,0 +1,250 @@
+.. _qtut_requirements:
+
+============
+Requirements
+============
+
+Let's get our tutorial environment setup. Most of the setup work is in
+standard Python development practices (install Python,
+make an isolated environment, and setup packaging tools.)
+
+.. note::
+
+ Pyramid encourages standard Python development practices with
+ packaging tools, virtual environments, logging, and so on. There
+ are many variations, implementations, and opinions across the Python
+ community. For consistency, ease of documentation maintenance,
+ and to minimize confusion, the Pyramid *documentation* has adopted
+ specific conventions.
+
+This *Quick Tutorial* is based on:
+
+* **Python 3.3**. Pyramid fully supports Python 3.2+ and Python 2.6+.
+ This tutorial uses **Python 3.3** but runs fine under Python 2.7.
+
+* **pyvenv**. We believe in virtual environments. For this tutorial,
+ we use Python 3.3's built-in solution, the ``pyvenv`` command.
+ For Python 2.7, you can install ``virtualenv``.
+
+* **setuptools and easy_install**. We use
+ `setuptools <https://pypi.python.org/pypi/setuptools/>`_
+ and its ``easy_install`` for package management.
+
+* **Workspaces, projects, and packages.** Our home directory
+ will contain a *tutorial workspace* with our Python virtual
+ environment(s) and *Python projects* (a directory with packaging
+ information and *Python packages* of working code.)
+
+* **Unix commands**. Commands in this tutorial use UNIX syntax and
+ paths. Windows users should adjust commands accordingly.
+
+.. note::
+
+ Pyramid was one of the first web frameworks to fully support Python 3 in
+ October 2011.
+
+Steps
+=====
+
+#. :ref:`install-python-3.3-or-greater`
+#. :ref:`create-a-project-directory-structure`
+#. :ref:`set-an-environment-variable`
+#. :ref:`create-a-virtual-environment`
+#. :ref:`install-setuptools-(python-packaging-tools)`
+#. :ref:`install-pyramid`
+
+.. _install-python-3.3-or-greater:
+
+Install Python 3.3 or greater
+-----------------------------
+
+Download the latest standard Python 3.3+ release (not development
+release) from
+`python.org <http://www.python.org/download/releases/>`_. On that page, you
+must click the latest version, then scroll down to the "Downloads" section
+for your operating system.
+
+Windows and Mac OS X users can download and run an installer.
+
+Windows users should also install the `Python for Windows extensions
+<http://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
+version.
+
+Linux users can either use their package manager to install Python 3.3
+or may
+`build Python 3.3 from source
+<http://pyramid.readthedocs.org/en/master/narr/install.html#package-manager-
+method>`_.
+
+
+.. _create-a-project-directory-structure:
+
+Create a project directory structure
+------------------------------------
+
+We will arrive at a directory structure of
+``workspace->project->package``, with our workspace named
+``quick_tutorial``. The following diagram shows how this is structured
+and where our virtual environment will reside:
+
+.. figure:: ../_static/directory_structure_pyramid.png
+ :alt: Final directory structure
+
+ Final directory structure.
+
+For Linux, the commands to do so are as follows:
+
+.. code-block:: bash
+
+ # Mac and Linux
+ $ cd ~
+ $ mkdir -p projects/quick_tutorial
+ $ cd projects/quick_tutorial
+
+For Windows:
+
+.. code-block:: posh
+
+ # Windows
+ c:\> cd \
+ c:\> mkdir projects\quick_tutorial
+ c:\> cd projects\quick_tutorial
+
+In the above figure, your user home directory is represented by ``~``. In
+your home directory, all of your projects are in the ``projects`` directory.
+This is a general convention not specific to Pyramid that many developers use.
+Windows users will do well to use ``c:\`` as the location for ``projects`` in
+order to avoid spaces in any of the path names.
+
+Next within ``projects`` is your workspace directory, here named
+``quick_tutorial``. A workspace is a common term used by integrated
+development environments (IDE) like PyCharm and PyDev that stores
+isolated Python environments (virtualenvs) and specific project files
+and repositories.
+
+
+.. _set-an-environment-variable:
+
+Set an Environment Variable
+---------------------------
+
+This tutorial will refer frequently to the location of the virtual
+environment. We set an environment variable to save typing later.
+
+.. code-block:: bash
+
+ # Mac and Linux
+ $ export VENV=~/projects/quick_tutorial/env33/
+
+ # Windows
+ # TODO: This command does not work
+ c:\> set VENV=c:\projects\quick_tutorial\env33
+
+
+.. _create-a-virtual-environment:
+
+Create a Virtual Environment
+----------------------------
+
+.. warning:: The current state of isolated Python environments using
+ ``pyvenv`` on Windows is suboptimal in comparison to Mac and Linux. See
+ http://stackoverflow.com/q/15981111/95735 for a discussion of the issue
+ and `PEP 453 <http://www.python.org/dev/peps/pep-0453/>`_ for a proposed
+ resolution.
+
+``pyvenv`` is a tool to create isolated Python 3.3 environments, each
+with its own Python binary and independent set of installed Python
+packages in its site directories. Let's create one, using the location
+we just specified in the environment variable.
+
+.. code-block:: bash
+
+ # Mac and Linux
+ $ pyvenv $VENV
+
+ # Windows
+ c:\> c:\Python33\python -m venv %VENV%
+
+.. seealso:: See also Python 3's :mod:`venv module <python3:venv>`,
+ Python 2's `virtualenv <http://www.virtualenv.org/en/latest/>`_
+ package,
+ :ref:`Installing Pyramid on a Windows System <installing_windows>`
+
+
+.. _install-setuptools-(python-packaging-tools):
+
+Install ``setuptools`` (Python packaging tools)
+-----------------------------------------------
+
+The following command will download a script to install ``setuptools``, then
+pipe it to your environment's version of Python.
+
+.. code-block:: bash
+
+ # Mac and Linux
+ $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | $VENV/bin/python
+
+ # Windows
+ # Use your browser to download:
+ # https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.p
+ # ...into c:\projects\quick_tutorial\ez_setup.py
+ c:\> %VENV%\Scripts\python ez_setup.py
+
+If ``wget`` complains with a certificate error, then run this command instead:
+
+.. code-block:: bash
+
+ # Mac and Linux
+ $ wget --no-check-certificate https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | $VENV/bin/python
+
+
+.. _install-pyramid:
+
+Install Pyramid
+---------------
+
+We have our Python standard prerequisites out of the way. The Pyramid
+part is pretty easy:
+
+.. parsed-literal::
+
+ # Mac and Linux
+ $ $VENV/bin/easy_install "pyramid==\ |release|\ "
+
+ # Windows
+ c:\\> %VENV%\\Scripts\\easy_install "pyramid==\ |release|\ "
+
+Our Python virtual environment now has the Pyramid software available.
+
+You can optionally install some of the extra Python packages used
+during this tutorial:
+
+.. code-block:: bash
+
+ # Mac and Linux
+ $ $VENV/bin/easy_install nose webtest deform sqlalchemy \
+ pyramid_chameleon pyramid_debugtoolbar waitress \
+ pyramid_jinja2 pyramid_tm zope.sqlalchemy pysqlite
+
+ # Windows
+ c:\> %VENV%\Scripts\easy_install nose webtest deform sqlalchemy pyramid_chameleon
+
+
+
+.. note::
+
+ Why ``easy_install`` and not ``pip``? Pyramid encourages use of namespace
+ packages which, until recently, ``pip`` didn't permit. Also, Pyramid has
+ some optional C extensions for performance. With ``easy_install``, Windows
+ users can get these extensions without needing a C compiler.
+
+.. seealso:: See Also: :ref:`installing_unix`. For instructions to set up your
+ Python environment for development using Windows or Python 2, see Pyramid's
+ :ref:`Before You Install <installing_chapter>`.
+
+ See also Python 3's :mod:`venv module <python3:venv>`, the `setuptools` `installation instructions
+ <https://pypi.python.org/pypi/setuptools/0.9.8#installation-instructions>`_,
+ and `easy_install help <https://pypi.python.org/pypi/setuptools/0.9.8#using-setuptools-and-easyinstall>`_.
+
diff --git a/docs/quick_tutorial/routing.rst b/docs/quick_tutorial/routing.rst
new file mode 100644
index 000000000..54dff5c39
--- /dev/null
+++ b/docs/quick_tutorial/routing.rst
@@ -0,0 +1,121 @@
+.. _qtut_routing:
+
+==========================================
+11: Dispatching URLs To Views With Routing
+==========================================
+
+Routing matches incoming URL patterns to view code. Pyramid's routing
+has a number of useful features.
+
+Background
+==========
+
+Writing web applications usually means sophisticated URL design. We
+just saw some Pyramid machinery for requests and views. Let's look at
+features that help in routing.
+
+Previously we saw the basics of routing URLs to views in
+
+- Your project's "setup" code registers a route name to be used when
+ matching part of the URL
+
+- Elsewhere, a view is configured to be called for that route name
+
+.. note::
+
+ Why do this twice? Other Python web frameworks let you create a
+ route and associate it with a view in one step. As
+ illustrated in :ref:`routes_need_ordering`, multiple routes might match the
+ same URL pattern. Rather than provide ways to help guess, Pyramid lets you
+ be explicit in ordering. Pyramid also gives facilities to avoid the
+ problem. It's relatively easy to build a system that uses implicit route
+ ordering with Pyramid too. See `The Groundhog series of screencasts
+ <http://bfg.repoze.org/videos#groundhog1>`_ if you're interested in
+ doing so.
+
+Objectives
+==========
+
+- Define a route that extracts part of the URL into a Python dictionary
+
+- Use that dictionary data in a view
+
+Steps
+=====
+
+#. First we copy the results of the ``view_classes`` step:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r view_classes routing; cd routing
+ $ $VENV/bin/python setup.py develop
+
+#. Our ``routing/tutorial/__init__.py`` needs a route with a replacement
+ pattern:
+
+ .. literalinclude:: routing/tutorial/__init__.py
+ :linenos:
+
+#. We just need one view in ``routing/tutorial/views.py``:
+
+ .. literalinclude:: routing/tutorial/views.py
+ :linenos:
+
+#. We just need one view in ``routing/tutorial/home.pt``:
+
+ .. literalinclude:: routing/tutorial/home.pt
+ :language: html
+ :linenos:
+
+#. Update ``routing/tutorial/tests.py``:
+
+ .. literalinclude:: routing/tutorial/tests.py
+ :linenos:
+
+#. Now run the tests:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/nosetests tutorial
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/howdy/amy/smith in your browser.
+
+Analysis
+========
+
+In ``__init__.py`` we see an important change in our route declaration:
+
+.. code-block:: python
+
+ config.add_route('hello', '/howdy/{first}/{last}')
+
+With this we tell the :term:`configurator` that our URL has
+a "replacement pattern". With this, URLs such as ``/howdy/amy/smith``
+will assign ``amy`` to ``first`` and ``smith`` to ``last``. We can then
+use this data in our view:
+
+.. code-block:: python
+
+ self.request.matchdict['first']
+ self.request.matchdict['last']
+
+``request.matchdict`` contains values from the URL that match the
+"replacement patterns" (the curly braces) in the route declaration.
+This information can then be used anywhere in Pyramid that has access
+to the request.
+
+Extra Credit
+============
+
+#. What happens if you to go the URL
+ http://localhost:6543/howdy? Is this the result that you
+ expected?
+
+.. seealso:: `Weird Stuff You Can Do With URL
+ Dispatch <http://www.plope.com/weird_pyramid_urldispatch>`_
diff --git a/docs/quick_tutorial/routing/development.ini b/docs/quick_tutorial/routing/development.ini
new file mode 100644
index 000000000..62e0c5123
--- /dev/null
+++ b/docs/quick_tutorial/routing/development.ini
@@ -0,0 +1,41 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/routing/setup.py b/docs/quick_tutorial/routing/setup.py
new file mode 100644
index 000000000..2221b72e9
--- /dev/null
+++ b/docs/quick_tutorial/routing/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon'
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/routing/tutorial/__init__.py b/docs/quick_tutorial/routing/tutorial/__init__.py
new file mode 100644
index 000000000..4b2dac36d
--- /dev/null
+++ b/docs/quick_tutorial/routing/tutorial/__init__.py
@@ -0,0 +1,9 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_route('home', '/howdy/{first}/{last}')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/routing/tutorial/home.pt b/docs/quick_tutorial/routing/tutorial/home.pt
new file mode 100644
index 000000000..f2b991059
--- /dev/null
+++ b/docs/quick_tutorial/routing/tutorial/home.pt
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${name}</title>
+</head>
+<body>
+<h1>${name}</h1>
+<p>First: ${first}, Last: ${last}</p>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/routing/tutorial/tests.py b/docs/quick_tutorial/routing/tutorial/tests.py
new file mode 100644
index 000000000..572f389fb
--- /dev/null
+++ b/docs/quick_tutorial/routing/tutorial/tests.py
@@ -0,0 +1,36 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ request.matchdict['first'] = 'First'
+ request.matchdict['last'] = 'Last'
+ inst = TutorialViews(request)
+ response = inst.home()
+ self.assertEqual(response['first'], 'First')
+ self.assertEqual(response['last'], 'Last')
+
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+ app = main({})
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def test_home(self):
+ res = self.testapp.get('/howdy/Jane/Doe', status=200)
+ self.assertIn(b'Jane', res.body)
+ self.assertIn(b'Doe', res.body)
diff --git a/docs/quick_tutorial/routing/tutorial/views.py b/docs/quick_tutorial/routing/tutorial/views.py
new file mode 100644
index 000000000..8a9211e92
--- /dev/null
+++ b/docs/quick_tutorial/routing/tutorial/views.py
@@ -0,0 +1,20 @@
+from pyramid.view import (
+ view_config,
+ view_defaults
+ )
+
+
+@view_defaults(renderer='home.pt')
+class TutorialViews:
+ def __init__(self, request):
+ self.request = request
+
+ @view_config(route_name='home')
+ def home(self):
+ first = self.request.matchdict['first']
+ last = self.request.matchdict['last']
+ return {
+ 'name': 'Home View',
+ 'first': first,
+ 'last': last
+ }
diff --git a/docs/quick_tutorial/scaffolds.rst b/docs/quick_tutorial/scaffolds.rst
new file mode 100644
index 000000000..8ca2d27df
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds.rst
@@ -0,0 +1,86 @@
+.. _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 warmup, 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 SQLAlchemy project using url dispatch
+ starter: Pyramid starter project
+ zodb: Pyramid ZODB project using traversal
+
+#. Tell ``pcreate`` to use the ``starter`` scaffold to make our project:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pcreate --scaffold starter scaffolds
+
+#. Use normal Python development to setup our project for development:
+
+ .. code-block:: bash
+
+ $ cd scaffolds
+ $ $VENV/bin/python setup.py develop
+
+#. Startup 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 startup, ``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
new file mode 100644
index 000000000..35a34f332
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/CHANGES.txt
@@ -0,0 +1,4 @@
+0.0
+---
+
+- Initial version
diff --git a/docs/quick_tutorial/scaffolds/MANIFEST.in b/docs/quick_tutorial/scaffolds/MANIFEST.in
new file mode 100644
index 000000000..91d3f763b
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/MANIFEST.in
@@ -0,0 +1,2 @@
+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
new file mode 100644
index 000000000..7776dd2d5
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/README.txt
@@ -0,0 +1 @@
+scaffolds README
diff --git a/docs/quick_tutorial/scaffolds/development.ini b/docs/quick_tutorial/scaffolds/development.ini
new file mode 100644
index 000000000..b31d06194
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/development.ini
@@ -0,0 +1,60 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:scaffolds
+
+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
+host = 0.0.0.0
+port = 6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, scaffolds
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_scaffolds]
+level = DEBUG
+handlers =
+qualname = scaffolds
+
+[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
diff --git a/docs/quick_tutorial/scaffolds/production.ini b/docs/quick_tutorial/scaffolds/production.ini
new file mode 100644
index 000000000..1418e6bf6
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/production.ini
@@ -0,0 +1,54 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:scaffolds
+
+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
+host = 0.0.0.0
+port = 6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, scaffolds
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+
+[logger_scaffolds]
+level = WARN
+handlers =
+qualname = scaffolds
+
+[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
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/__init__.py b/docs/quick_tutorial/scaffolds/scaffolds/__init__.py
new file mode 100644
index 000000000..ad5ecbc6f
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/scaffolds/__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_chameleon')
+ 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/scaffolds/scaffolds/static/favicon.ico b/docs/quick_tutorial/scaffolds/scaffolds/static/favicon.ico
new file mode 100644
index 000000000..71f837c9e
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/scaffolds/static/favicon.ico
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png b/docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png
new file mode 100644
index 000000000..1fbc873da
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png b/docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png
new file mode 100644
index 000000000..0596f2020
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css b/docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css
new file mode 100644
index 000000000..b7c8493d8
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css
@@ -0,0 +1,8 @@
+* 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
new file mode 100644
index 000000000..2369cfb7d
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.png
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css b/docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css
new file mode 100644
index 000000000..4b1c017cd
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css
@@ -0,0 +1,372 @@
+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/pyramid/scaffolds/alchemy/+package+/static/pyramid-small.png b/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png
index a5bc0ade7..a5bc0ade7 100644
--- a/pyramid/scaffolds/alchemy/+package+/static/pyramid-small.png
+++ b/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.png b/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.png
new file mode 100644
index 000000000..347e05549
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.png
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif b/docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif
new file mode 100644
index 000000000..0341802e5
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif
Binary files differ
diff --git a/docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt b/docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt
new file mode 100644
index 000000000..b43a174e3
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt
@@ -0,0 +1,73 @@
+<!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
new file mode 100644
index 000000000..4f906ffa9
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/scaffolds/tests.py
@@ -0,0 +1,17 @@
+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
new file mode 100644
index 000000000..db90d8364
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/scaffolds/views.py
@@ -0,0 +1,6 @@
+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.cfg b/docs/quick_tutorial/scaffolds/setup.cfg
new file mode 100644
index 000000000..c980261e3
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/setup.cfg
@@ -0,0 +1,27 @@
+[nosetests]
+match = ^test
+nocapture = 1
+cover-package = scaffolds
+with-coverage = 1
+cover-erase = 1
+
+[compile_catalog]
+directory = scaffolds/locale
+domain = scaffolds
+statistics = true
+
+[extract_messages]
+add_comments = TRANSLATORS:
+output_file = scaffolds/locale/scaffolds.pot
+width = 80
+
+[init_catalog]
+domain = scaffolds
+input_file = scaffolds/locale/scaffolds.pot
+output_dir = scaffolds/locale
+
+[update_catalog]
+domain = scaffolds
+input_file = scaffolds/locale/scaffolds.pot
+output_dir = scaffolds/locale
+previous = true
diff --git a/docs/quick_tutorial/scaffolds/setup.py b/docs/quick_tutorial/scaffolds/setup.py
new file mode 100644
index 000000000..ec95946a5
--- /dev/null
+++ b/docs/quick_tutorial/scaffolds/setup.py
@@ -0,0 +1,42 @@
+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.rst b/docs/quick_tutorial/sessions.rst
new file mode 100644
index 000000000..ba26d0133
--- /dev/null
+++ b/docs/quick_tutorial/sessions.rst
@@ -0,0 +1,100 @@
+.. _qtut_sessions:
+
+=================================
+17: Transient Data Using Sessions
+=================================
+
+Store and retrieve non-permanent data in Pyramid sessions.
+
+Background
+==========
+
+When people use your web application, they frequently perform a task
+that requires semi-permanent data to be saved. For example, a shopping
+cart. This is called a :term:`session`.
+
+Pyramid has basic built-in support for sessions, with add-ons such as
+*dogpile.cache* (or your own custom sessioning engine) that provide
+richer session support. Let's take a look at the
+:ref:`built-in sessioning support <sessions_chapter>`.
+
+Objectives
+==========
+
+- Make a session factory using a built-in, simple Pyramid sessioning
+ system
+
+- Change our code to use a session
+
+Steps
+=====
+
+#. First we copy the results of the ``view_classes`` step:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r view_classes sessions; cd sessions
+ $ $VENV/bin/python setup.py develop
+
+#. Our ``sessions/tutorial/__init__.py`` needs a choice of session
+ factory to get registered with the :term:`configurator`:
+
+ .. literalinclude:: sessions/tutorial/__init__.py
+ :linenos:
+
+#. Our views in ``sessions/tutorial/views.py`` can now use
+ ``request.session``:
+
+ .. literalinclude:: sessions/tutorial/views.py
+ :linenos:
+
+#. The template at ``sessions/tutorial/home.pt`` can display the value:
+
+ .. literalinclude:: sessions/tutorial/home.pt
+ :language: html
+ :linenos:
+
+#. Make sure the tests still pass:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/nosetests tutorial
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ and http://localhost:6543/howdy
+ in your browser. As you reload and switch between those URLs, note
+ that the counter increases and is *not* specific to the URL.
+
+#. Restart the application and revisit the page. Note that counter
+ still increases from where it left off.
+
+Analysis
+========
+
+Pyramid's :term:`request` object now has a ``session`` attribute
+that we can use in our view code. It acts like a dictionary.
+
+Since all the views are using the same counter, we made the counter a
+Python property at the view class level. With this, each reload will
+increase the counter displayed in our template.
+
+In web development, "flash messages" are notes for the user that need
+to appear on a screen after a future web request. For example,
+when you add an item using a form ``POST``, the site usually issues a
+second HTTP Redirect web request to view the new item. You might want a
+message to appear after that second web request saying "Your item was
+added." You can't just return it in the web response for the POST,
+as it will be tossed out during the second web requests.
+
+Flash messages are a technique where messages can be stored between
+requests, using sessions, then removed when they finally get displayed.
+
+.. seealso::
+ :ref:`sessions_chapter`,
+ :ref:`flash_messages`, and
+ :ref:`session_module`.
diff --git a/docs/quick_tutorial/sessions/development.ini b/docs/quick_tutorial/sessions/development.ini
new file mode 100644
index 000000000..62e0c5123
--- /dev/null
+++ b/docs/quick_tutorial/sessions/development.ini
@@ -0,0 +1,41 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/sessions/setup.py b/docs/quick_tutorial/sessions/setup.py
new file mode 100644
index 000000000..2221b72e9
--- /dev/null
+++ b/docs/quick_tutorial/sessions/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon'
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/sessions/tutorial/__init__.py b/docs/quick_tutorial/sessions/tutorial/__init__.py
new file mode 100644
index 000000000..ecf57bb32
--- /dev/null
+++ b/docs/quick_tutorial/sessions/tutorial/__init__.py
@@ -0,0 +1,14 @@
+from pyramid.config import Configurator
+from pyramid.session import UnencryptedCookieSessionFactoryConfig
+
+
+def main(global_config, **settings):
+ my_session_factory = UnencryptedCookieSessionFactoryConfig(
+ 'itsaseekreet')
+ config = Configurator(settings=settings,
+ session_factory=my_session_factory)
+ config.include('pyramid_chameleon')
+ config.add_route('home', '/')
+ config.add_route('hello', '/howdy')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/sessions/tutorial/home.pt b/docs/quick_tutorial/sessions/tutorial/home.pt
new file mode 100644
index 000000000..0b27ba1d8
--- /dev/null
+++ b/docs/quick_tutorial/sessions/tutorial/home.pt
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${name}</title>
+</head>
+<body>
+<h1>Hi ${name}</h1>
+<p>Count: ${view.counter}</p>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/sessions/tutorial/tests.py b/docs/quick_tutorial/sessions/tutorial/tests.py
new file mode 100644
index 000000000..4381235ec
--- /dev/null
+++ b/docs/quick_tutorial/sessions/tutorial/tests.py
@@ -0,0 +1,44 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.home()
+ self.assertEqual('Home View', response['name'])
+
+ def test_hello(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.hello()
+ self.assertEqual('Hello View', response['name'])
+
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+ app = main({})
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def test_home(self):
+ res = self.testapp.get('/', status=200)
+ self.assertIn(b'<h1>Hi Home View', res.body)
+
+ def test_hello(self):
+ res = self.testapp.get('/howdy', status=200)
+ self.assertIn(b'<h1>Hi Hello View', res.body)
diff --git a/docs/quick_tutorial/sessions/tutorial/views.py b/docs/quick_tutorial/sessions/tutorial/views.py
new file mode 100644
index 000000000..a4659d265
--- /dev/null
+++ b/docs/quick_tutorial/sessions/tutorial/views.py
@@ -0,0 +1,29 @@
+from pyramid.view import (
+ view_config,
+ view_defaults
+ )
+
+
+@view_defaults(renderer='home.pt')
+class TutorialViews:
+ def __init__(self, request):
+ self.request = request
+
+ @property
+ def counter(self):
+ session = self.request.session
+ if 'counter' in session:
+ session['counter'] += 1
+ else:
+ session['counter'] = 1
+
+ return session['counter']
+
+
+ @view_config(route_name='home')
+ def home(self):
+ return {'name': 'Home View'}
+
+ @view_config(route_name='hello')
+ def hello(self):
+ return {'name': 'Hello View'}
diff --git a/docs/quick_tutorial/static_assets.rst b/docs/quick_tutorial/static_assets.rst
new file mode 100644
index 000000000..19d33f00f
--- /dev/null
+++ b/docs/quick_tutorial/static_assets.rst
@@ -0,0 +1,91 @@
+.. _qtut_static_assets:
+
+==========================================
+13: CSS/JS/Images Files With Static Assets
+==========================================
+
+Of course the Web is more than just markup. You need static assets:
+CSS, JS, and images. Let's point our web app at a directory where
+Pyramid will serve some static assets.
+
+Objectives
+==========
+
+- Publish a directory of static assets at a URL
+
+- Use Pyramid to help generate URLs to files in that directory
+
+Steps
+=====
+
+#. First we copy the results of the ``view_classes`` step:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r view_classes static_assets; cd static_assets
+ $ $VENV/bin/python setup.py develop
+
+#. We add a call ``config.add_static_view in
+ ``static_assets/tutorial/__init__.py``:
+
+ .. literalinclude:: static_assets/tutorial/__init__.py
+ :linenos:
+
+#. We can add a CSS link in the ``<head>`` of our template at
+ ``static_assets/tutorial/home.pt``:
+
+ .. literalinclude:: static_assets/tutorial/home.pt
+ :language: html
+
+#. Add a CSS file at
+ ``static_assets/tutorial/static/app.css``:
+
+ .. literalinclude:: static_assets/tutorial/static/app.css
+ :language: css
+
+#. Make sure we haven't broken any existing code by running the tests:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/nosetests tutorial
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ in your browser and note the new font.
+
+Analysis
+========
+
+We changed our WSGI application to map requests under
+http://localhost:6543/static/ to files and directories inside a
+``static`` directory inside our ``tutorial`` package. This directory
+contained ``app.css``.
+
+We linked to the CSS in our template. We could have hard-coded this
+link to ``/static/app.css``. But what if the site is later moved under
+``/somesite/static/``? Or perhaps the web developer changes the
+arrangement on disk? Pyramid gives a helper that provides flexibility
+on URL generation:
+
+.. code-block:: html
+
+ ${request.static_url('tutorial:static/app.css')}
+
+This matches the ``path='tutorial:static'`` in our
+``config.add_static_view`` registration. By using ``request.static_url``
+to generate the full URL to the static assets, you both ensure you stay
+in sync with the configuration and gain refactoring flexibility later.
+
+Extra Credit
+============
+
+#. There is also a ``request.static_path`` API. How does this differ from
+ ``request.static_url``?
+
+.. seealso:: :ref:`assets_chapter`,
+ :ref:`preventing_http_caching`, and
+ :ref:`influencing_http_caching`
diff --git a/docs/quick_tutorial/static_assets/development.ini b/docs/quick_tutorial/static_assets/development.ini
new file mode 100644
index 000000000..62e0c5123
--- /dev/null
+++ b/docs/quick_tutorial/static_assets/development.ini
@@ -0,0 +1,41 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/static_assets/setup.py b/docs/quick_tutorial/static_assets/setup.py
new file mode 100644
index 000000000..2221b72e9
--- /dev/null
+++ b/docs/quick_tutorial/static_assets/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon'
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/static_assets/tutorial/__init__.py b/docs/quick_tutorial/static_assets/tutorial/__init__.py
new file mode 100644
index 000000000..e244c2997
--- /dev/null
+++ b/docs/quick_tutorial/static_assets/tutorial/__init__.py
@@ -0,0 +1,11 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_route('home', '/')
+ config.add_route('hello', '/howdy')
+ config.add_static_view(name='static', path='tutorial:static')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/static_assets/tutorial/home.pt b/docs/quick_tutorial/static_assets/tutorial/home.pt
new file mode 100644
index 000000000..5d347f057
--- /dev/null
+++ b/docs/quick_tutorial/static_assets/tutorial/home.pt
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${name}</title>
+ <link rel="stylesheet"
+ href="${request.static_url('tutorial:static/app.css') }"/>
+</head>
+<body>
+<h1>Hi ${name}</h1>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/static_assets/tutorial/static/app.css b/docs/quick_tutorial/static_assets/tutorial/static/app.css
new file mode 100644
index 000000000..f8acf3164
--- /dev/null
+++ b/docs/quick_tutorial/static_assets/tutorial/static/app.css
@@ -0,0 +1,4 @@
+body {
+ margin: 2em;
+ font-family: sans-serif;
+} \ No newline at end of file
diff --git a/docs/quick_tutorial/static_assets/tutorial/tests.py b/docs/quick_tutorial/static_assets/tutorial/tests.py
new file mode 100644
index 000000000..4381235ec
--- /dev/null
+++ b/docs/quick_tutorial/static_assets/tutorial/tests.py
@@ -0,0 +1,44 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.home()
+ self.assertEqual('Home View', response['name'])
+
+ def test_hello(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.hello()
+ self.assertEqual('Hello View', response['name'])
+
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+ app = main({})
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def test_home(self):
+ res = self.testapp.get('/', status=200)
+ self.assertIn(b'<h1>Hi Home View', res.body)
+
+ def test_hello(self):
+ res = self.testapp.get('/howdy', status=200)
+ self.assertIn(b'<h1>Hi Hello View', res.body)
diff --git a/docs/quick_tutorial/static_assets/tutorial/views.py b/docs/quick_tutorial/static_assets/tutorial/views.py
new file mode 100644
index 000000000..a56c0adbf
--- /dev/null
+++ b/docs/quick_tutorial/static_assets/tutorial/views.py
@@ -0,0 +1,18 @@
+from pyramid.view import (
+ view_config,
+ view_defaults
+ )
+
+
+@view_defaults(renderer='home.pt')
+class TutorialViews:
+ def __init__(self, request):
+ self.request = request
+
+ @view_config(route_name='home')
+ def home(self):
+ return {'name': 'Home View'}
+
+ @view_config(route_name='hello')
+ def hello(self):
+ return {'name': 'Hello View'}
diff --git a/docs/quick_tutorial/templating.rst b/docs/quick_tutorial/templating.rst
new file mode 100644
index 000000000..d73067f48
--- /dev/null
+++ b/docs/quick_tutorial/templating.rst
@@ -0,0 +1,123 @@
+.. _qtut_templating:
+
+===================================
+08: HTML Generation With Templating
+===================================
+
+Most web frameworks don't embed HTML in programming code. Instead,
+they pass data into a templating system. In this step we look at the
+basics of using HTML templates in Pyramid.
+
+Background
+==========
+
+Ouch. We have been making our own ``Response`` and filling the response
+body with HTML. You usually won't embed an HTML string directly in
+Python, but instead, will use a templating language.
+
+Pyramid doesn't mandate a particular database system, form library,
+etc. It encourages replaceability. This applies equally to templating,
+which is fortunate: developers have strong views about template
+languages. As of Pyramid 1.5a2, Pyramid doesn't even bundle a template
+language!
+
+It does, however, have strong ties to Jinja2, Mako, and Chameleon. In
+this step we see how to add ``pyramid_chameleon`` to your project,
+then change your views to use templating.
+
+Objectives
+==========
+
+- Enable the ``pyramid_chameleon`` Pyramid add-on
+
+- Generate HTML from template files
+
+- Connect the templates as "renderers" for view code
+
+- Change the view code to simply return data
+
+Steps
+=====
+
+#. Let's begin by using the previous package as a starting point for a
+ new project:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r views templating; cd templating
+
+#. This step depends on ``pyramid_chameleon``, so add it as a dependency
+ in ``templating/setup.py``:
+
+ .. literalinclude:: templating/setup.py
+ :linenos:
+
+#. Now we can activate the development-mode distribution:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/python setup.py develop
+
+#. We need to connect ``pyramid_chameleon`` as a renderer by making a
+ call in the setup of ``templating/tutorial/__init__.py``:
+
+ .. literalinclude:: templating/tutorial/__init__.py
+ :linenos:
+
+#. Our ``templating/tutorial/views.py`` no longer has HTML in it:
+
+ .. literalinclude:: templating/tutorial/views.py
+ :linenos:
+
+#. Instead we have ``templating/tutorial/home.pt`` as a template:
+
+ .. literalinclude:: templating/tutorial/home.pt
+ :language: html
+
+#. For convenience, change ``templating/development.ini`` to reload
+ templates automatically with ``pyramid.reload_templates``:
+
+ .. literalinclude:: templating/development.ini
+ :language: ini
+
+#. Our unit tests in ``templating/tutorial/tests.py`` can focus on
+ data:
+
+ .. literalinclude:: templating/tutorial/tests.py
+ :linenos:
+
+#. Now run the tests:
+
+ .. code-block:: bash
+
+
+ $ $VENV/bin/nosetests tutorial
+ .
+ ----------------------------------------------------------------------
+ Ran 4 tests in 0.141s
+
+ OK
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ and http://localhost:6543/howdy
+ in your browser.
+
+Analysis
+========
+
+Ahh, that looks better. We have a view that is focused on Python code.
+Our ``@view_config`` decorator specifies a :term:`renderer` that points
+our template file. Our view then simply returns data which is then
+supplied to our template. Note that we used the same template for both
+views.
+
+Note the effect on testing. We can focus on having a data-oriented
+contract with our view code.
+
+.. seealso:: :ref:`templates_chapter`, :ref:`debugging_templates`, and
+ :ref:`available_template_system_bindings`.
diff --git a/docs/quick_tutorial/templating/development.ini b/docs/quick_tutorial/templating/development.ini
new file mode 100644
index 000000000..62e0c5123
--- /dev/null
+++ b/docs/quick_tutorial/templating/development.ini
@@ -0,0 +1,41 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/templating/setup.py b/docs/quick_tutorial/templating/setup.py
new file mode 100644
index 000000000..2221b72e9
--- /dev/null
+++ b/docs/quick_tutorial/templating/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon'
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/templating/tutorial/__init__.py b/docs/quick_tutorial/templating/tutorial/__init__.py
new file mode 100644
index 000000000..c3e1c9eef
--- /dev/null
+++ b/docs/quick_tutorial/templating/tutorial/__init__.py
@@ -0,0 +1,10 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_route('home', '/')
+ config.add_route('hello', '/howdy')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/templating/tutorial/home.pt b/docs/quick_tutorial/templating/tutorial/home.pt
new file mode 100644
index 000000000..a0cc08e7a
--- /dev/null
+++ b/docs/quick_tutorial/templating/tutorial/home.pt
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${name}</title>
+</head>
+<body>
+<h1>Hi ${name}</h1>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/templating/tutorial/tests.py b/docs/quick_tutorial/templating/tutorial/tests.py
new file mode 100644
index 000000000..d06a62982
--- /dev/null
+++ b/docs/quick_tutorial/templating/tutorial/tests.py
@@ -0,0 +1,44 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ from .views import home
+
+ request = testing.DummyRequest()
+ response = home(request)
+ # Our view now returns data
+ self.assertEqual('Home View', response['name'])
+
+ def test_hello(self):
+ from .views import hello
+
+ request = testing.DummyRequest()
+ response = hello(request)
+ # Our view now returns data
+ self.assertEqual('Hello View', response['name'])
+
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+ app = main({})
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def test_home(self):
+ res = self.testapp.get('/', status=200)
+ self.assertIn(b'<h1>Hi Home View', res.body)
+
+ def test_hello(self):
+ res = self.testapp.get('/howdy', status=200)
+ self.assertIn(b'<h1>Hi Hello View', res.body)
diff --git a/docs/quick_tutorial/templating/tutorial/views.py b/docs/quick_tutorial/templating/tutorial/views.py
new file mode 100644
index 000000000..979d69c43
--- /dev/null
+++ b/docs/quick_tutorial/templating/tutorial/views.py
@@ -0,0 +1,13 @@
+from pyramid.view import view_config
+
+
+# First view, available at http://localhost:6543/
+@view_config(route_name='home', renderer='home.pt')
+def home(request):
+ return {'name': 'Home View'}
+
+
+# /howdy
+@view_config(route_name='hello', renderer='home.pt')
+def hello(request):
+ return {'name': 'Hello View'}
diff --git a/docs/quick_tutorial/tutorial_approach.rst b/docs/quick_tutorial/tutorial_approach.rst
new file mode 100644
index 000000000..52d768306
--- /dev/null
+++ b/docs/quick_tutorial/tutorial_approach.rst
@@ -0,0 +1,45 @@
+=================
+Tutorial Approach
+=================
+
+This tutorial uses conventions to keep the introduction focused and
+concise. 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::
+
+ $ cd request_response
+ $ $VENV/bin/python setup.py develop
+
+...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
+==============
+
+As we develop our tutorial our directory tree will resemble the
+structure below::
+
+ quicktutorial/
+ request_response/
+ development.ini
+ setup.py
+ tutorial/
+ __init__.py
+ home.pt
+ tests.py
+ views.py
+
+Each of the first-level directories (e.g. ``request_response``) is a
+*Python project* (except, as noted, 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. \ No newline at end of file
diff --git a/docs/quick_tutorial/unit_testing.rst b/docs/quick_tutorial/unit_testing.rst
new file mode 100644
index 000000000..73b33c588
--- /dev/null
+++ b/docs/quick_tutorial/unit_testing.rst
@@ -0,0 +1,119 @@
+.. _qtut_unit_testing:
+
+===========================
+05: Unit Tests and ``nose``
+===========================
+
+Provide unit testing for our project's Python code.
+
+Background
+==========
+
+As the mantra says, "Untested code is broken code." The Python
+community has had a long culture of writing test scripts which ensure
+that your code works correctly as you write it and maintain it in the
+future. Pyramid has always had a deep commitment to testing,
+with 100% test coverage from the earliest pre-releases.
+
+Python includes a
+:ref:`unit testing framework <python:unittest-minimal-example>` in its
+standard library. Over the years a number of Python projects, such as
+`nose <https://pypi.python.org/pypi/nose/>`_, have extended this
+framework with alternative test runners that provide more convenience
+and functionality. The Pyramid developers use ``nose``, which we'll thus
+use in this tutorial.
+
+Don't worry, this tutorial won't be pedantic about "test-driven
+development" (TDD.) We'll do just enough to ensure that, in each step,
+we haven't majorly 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
+`coverage <https://pypi.python.org/pypi/coverage>`_ for another section.
+
+Objectives
+==========
+
+- Write unit tests that ensure the quality of our code
+
+- Install a Python package (``nose``) which helps in our testing
+
+Steps
+=====
+
+#. First we copy the results of the previous step, as well as install
+ the ``nose`` package:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r debugtoolbar unit_testing; cd unit_testing
+ $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/easy_install nose
+
+#. Now we write a simple unit test in ``unit_testing/tutorial/tests.py``:
+
+ .. literalinclude:: unit_testing/tutorial/tests.py
+ :linenos:
+
+#. Now run the tests:
+
+ .. code-block:: bash
+
+
+ $ $VENV/bin/nosetests tutorial
+ .
+ ----------------------------------------------------------------------
+ Ran 1 test in 0.141s
+
+ OK
+
+Analysis
+========
+
+Our ``tests.py`` imports the Python standard unit testing framework. To
+make writing Pyramid-oriented tests more convenient, Pyramid supplies
+some ``pyramid.testing`` helpers 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.
+
+The ``tests.HelloWorldViewTests.test_hello_world`` test is a small
+example of a unit test. First, we import the view inside each test. Why
+not import at the top, like in normal Python code? Because imports can
+cause effects that break a test. We'd like our tests to be in *units*,
+hence the name *unit* testing. Each test should isolate itself to the
+correct degree.
+
+Our test then makes a fake incoming web request, then calls our Pyramid
+view. We test the HTTP status code on the response to make sure it
+matches our expectations.
+
+Note that our use of ``pyramid.testing.setUp()`` and
+``pyramid.testing.tearDown()`` aren't actually necessary here; they are only
+necessary when your test needs to make use of the ``config`` object (it's a
+Configurator) to add stuff to the configuration state before calling the view.
+
+Extra Credit
+============
+
+#. Change the test to assert that the response status code should be
+ ``404`` (meaning, not found.) Run ``nosetests`` again. Read the
+ error report and see if you can decipher what it is telling you.
+
+#. As a more realistic example, put the ``tests.py`` back as you found
+ it and put an error in your view, such as a reference to a
+ non-existing variable. Run the tests and see how this is more
+ convenient than reloading your browser and going back to your code.
+
+#. Finally, for the most realistic test, read about Pyramid ``Response``
+ objects and see how to change the response code. Run the tests and
+ see how testing confirms the "contract" that your code claims to
+ support.
+
+#. How could we add a unit test assertion to test the HTML value of the
+ response body?
+
+#. Why do we import the ``hello_world`` view function *inside* the
+ ``test_hello_world`` method instead of at the top of the module?
+
+.. seealso:: See Also: :ref:`testing_chapter`
diff --git a/docs/quick_tutorial/unit_testing/development.ini b/docs/quick_tutorial/unit_testing/development.ini
new file mode 100644
index 000000000..470d92c57
--- /dev/null
+++ b/docs/quick_tutorial/unit_testing/development.ini
@@ -0,0 +1,40 @@
+[app:main]
+use = egg:tutorial
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/unit_testing/setup.py b/docs/quick_tutorial/unit_testing/setup.py
new file mode 100644
index 000000000..9997984d3
--- /dev/null
+++ b/docs/quick_tutorial/unit_testing/setup.py
@@ -0,0 +1,13 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/unit_testing/tutorial/__init__.py b/docs/quick_tutorial/unit_testing/tutorial/__init__.py
new file mode 100644
index 000000000..2b4e84f30
--- /dev/null
+++ b/docs/quick_tutorial/unit_testing/tutorial/__init__.py
@@ -0,0 +1,13 @@
+from pyramid.config import Configurator
+from pyramid.response import Response
+
+
+def hello_world(request):
+ return Response('<body><h1>Hello World!</h1></body>')
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.add_route('hello', '/')
+ config.add_view(hello_world, route_name='hello')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/unit_testing/tutorial/tests.py b/docs/quick_tutorial/unit_testing/tutorial/tests.py
new file mode 100644
index 000000000..66029b421
--- /dev/null
+++ b/docs/quick_tutorial/unit_testing/tutorial/tests.py
@@ -0,0 +1,18 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_hello_world(self):
+ from tutorial import hello_world
+
+ request = testing.DummyRequest()
+ response = hello_world(request)
+ self.assertEqual(response.status_code, 200)
diff --git a/docs/quick_tutorial/view_classes.rst b/docs/quick_tutorial/view_classes.rst
new file mode 100644
index 000000000..58ab43e40
--- /dev/null
+++ b/docs/quick_tutorial/view_classes.rst
@@ -0,0 +1,98 @@
+.. _qtut_view_classes:
+
+======================================
+09: Organizing Views With View Classes
+======================================
+
+Change our view functions to be methods on a view class,
+then move some declarations to the class level.
+
+Background
+==========
+
+So far our views have been simple, free-standing functions. Many times
+your views are related: different ways to look at or work on the same
+data or a REST API that handles multiple operations. Grouping these
+together as a
+:ref:`view class <class_as_view>` makes sense:
+
+- Group views
+
+- Centralize some repetitive defaults
+
+- Share some state and helpers
+
+In this step we just do the absolute minimum to convert the existing
+views to a view class. In a later tutorial step we'll examine view
+classes in depth.
+
+Objectives
+==========
+
+- Group related views into a view class
+
+- Centralize configuration with class-level ``@view_defaults``
+
+Steps
+=====
+
+
+#. First we copy the results of the previous step:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r templating view_classes; cd view_classes
+ $ $VENV/bin/python setup.py develop
+
+#. Our ``view_classes/tutorial/views.py`` now has a view class with
+ our two views:
+
+ .. literalinclude:: view_classes/tutorial/views.py
+ :linenos:
+
+#. Our unit tests in ``view_classes/tutorial/tests.py`` don't run,
+ so let's modify the to import the view class and make an instance
+ before getting a response:
+
+ .. literalinclude:: view_classes/tutorial/tests.py
+ :linenos:
+
+#. Now run the tests:
+
+ .. code-block:: bash
+
+
+ $ $VENV/bin/nosetests tutorial
+ .
+ ----------------------------------------------------------------------
+ Ran 4 tests in 0.141s
+
+ OK
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ and http://localhost:6543/howdy
+ in your browser.
+
+Analysis
+========
+
+To ease the transition to view classes, we didn't introduce any new
+functionality. We simply changed the view functions to methods on a
+view class, then updated the tests.
+
+In our ``TutorialViews`` view class you can see that our two view
+classes are logically grouped together as methods on a common class.
+Since the two views shared the same template, we could move that to a
+``@view_defaults`` decorator on at the class level.
+
+The tests needed to change. Obviously we needed to import the view
+class. But you can also see the pattern in the tests of instantiating
+the view class with the dummy request first, then calling the view
+method being tested.
+
+.. seealso:: :ref:`class_as_view`
diff --git a/docs/quick_tutorial/view_classes/development.ini b/docs/quick_tutorial/view_classes/development.ini
new file mode 100644
index 000000000..62e0c5123
--- /dev/null
+++ b/docs/quick_tutorial/view_classes/development.ini
@@ -0,0 +1,41 @@
+[app:main]
+use = egg:tutorial
+pyramid.reload_templates = true
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/view_classes/setup.py b/docs/quick_tutorial/view_classes/setup.py
new file mode 100644
index 000000000..2221b72e9
--- /dev/null
+++ b/docs/quick_tutorial/view_classes/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon'
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/view_classes/tutorial/__init__.py b/docs/quick_tutorial/view_classes/tutorial/__init__.py
new file mode 100644
index 000000000..c3e1c9eef
--- /dev/null
+++ b/docs/quick_tutorial/view_classes/tutorial/__init__.py
@@ -0,0 +1,10 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_route('home', '/')
+ config.add_route('hello', '/howdy')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/view_classes/tutorial/home.pt b/docs/quick_tutorial/view_classes/tutorial/home.pt
new file mode 100644
index 000000000..a0cc08e7a
--- /dev/null
+++ b/docs/quick_tutorial/view_classes/tutorial/home.pt
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Quick Tour: ${name}</title>
+</head>
+<body>
+<h1>Hi ${name}</h1>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/view_classes/tutorial/tests.py b/docs/quick_tutorial/view_classes/tutorial/tests.py
new file mode 100644
index 000000000..4381235ec
--- /dev/null
+++ b/docs/quick_tutorial/view_classes/tutorial/tests.py
@@ -0,0 +1,44 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.home()
+ self.assertEqual('Home View', response['name'])
+
+ def test_hello(self):
+ from .views import TutorialViews
+
+ request = testing.DummyRequest()
+ inst = TutorialViews(request)
+ response = inst.hello()
+ self.assertEqual('Hello View', response['name'])
+
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+ app = main({})
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def test_home(self):
+ res = self.testapp.get('/', status=200)
+ self.assertIn(b'<h1>Hi Home View', res.body)
+
+ def test_hello(self):
+ res = self.testapp.get('/howdy', status=200)
+ self.assertIn(b'<h1>Hi Hello View', res.body)
diff --git a/docs/quick_tutorial/view_classes/tutorial/views.py b/docs/quick_tutorial/view_classes/tutorial/views.py
new file mode 100644
index 000000000..58db53c4a
--- /dev/null
+++ b/docs/quick_tutorial/view_classes/tutorial/views.py
@@ -0,0 +1,17 @@
+from pyramid.view import (
+ view_config,
+ view_defaults
+ )
+
+@view_defaults(renderer='home.pt')
+class TutorialViews:
+ def __init__(self, request):
+ self.request = request
+
+ @view_config(route_name='home')
+ def home(self):
+ return {'name': 'Home View'}
+
+ @view_config(route_name='hello')
+ def hello(self):
+ return {'name': 'Hello View'}
diff --git a/docs/quick_tutorial/views.rst b/docs/quick_tutorial/views.rst
new file mode 100644
index 000000000..15785e902
--- /dev/null
+++ b/docs/quick_tutorial/views.rst
@@ -0,0 +1,122 @@
+.. _qtut_views:
+
+=================================
+07: Basic Web Handling With Views
+=================================
+
+Organize a views module with decorators and multiple views.
+
+Background
+==========
+
+For the examples so far, the ``hello_world`` function is a "view". In
+Pyramid, views are the primary way to accept web requests and return
+responses.
+
+So far our examples place everything in one file:
+
+- The view function
+
+- Its registration with the configurator
+
+- The route to map it to a URL
+
+- The WSGI application launcher
+
+Let's move the views out to their own ``views.py`` module and change
+our startup code to scan that module, looking for decorators that setup
+the views. Let's also add a second view and update our tests.
+
+Objectives
+==========
+
+- Views in a module that is scanned by the configurator
+
+- Decorators that do declarative configuration
+
+Steps
+=====
+
+#. Let's begin by using the previous package as a starting point for a
+ new distribution, then making it active:
+
+ .. code-block:: bash
+
+ $ cd ..; cp -r function_testing views; cd views
+ $ $VENV/bin/python setup.py develop
+
+#. Our ``views/tutorial/__init__.py`` gets a lot shorter:
+
+ .. literalinclude:: views/tutorial/__init__.py
+ :linenos:
+
+#. Let's add a module ``views/tutorial/views.py`` that is focused on
+ handling requests and responses:
+
+ .. literalinclude:: views/tutorial/views.py
+ :linenos:
+
+#. Update the tests to cover the two new views:
+
+ .. literalinclude:: views/tutorial/tests.py
+ :linenos:
+
+#. Now run the tests:
+
+ .. code-block:: bash
+
+
+ $ $VENV/bin/nosetests tutorial
+ .
+ ----------------------------------------------------------------------
+ Ran 4 tests in 0.141s
+
+ OK
+
+#. Run your Pyramid application with:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/pserve development.ini --reload
+
+#. Open http://localhost:6543/ and http://localhost:6543/howdy
+ in your browser.
+
+Analysis
+========
+
+We added some more URLs, but we also removed the view code from the
+application startup code in ``tutorial/__init__.py``.
+Our views, and their view registrations (via decorators) are now in a
+module ``views.py`` which is scanned via ``config.scan('.views')``.
+
+We have 2 views, each leading to the other. If you start at
+http://localhost:6543/, you get a response with a link to the next
+view. The ``hello_view`` (available at the URL ``/howdy``) has a link
+back to the first view.
+
+This step also shows that the name appearing in the URL,
+the name of the "route" that maps a URL to a view,
+and the name of the view, can all be different. More on routes later.
+
+Earlier we saw ``config.add_view`` as one way to configure a view. This
+section introduces ``@view_config``. Pyramid's configuration supports
+:term:`imperative configuration`, such as the
+``config.add_view`` in the previous example. You can also use
+:term:`declarative configuration`, in which a Python
+:term:`python:decorator`
+is placed on the line above the view. Both approaches result in the
+same final configuration, thus usually, it is simply a matter of taste.
+
+Extra Credit
+============
+
+#. What does the dot in ``.views`` signify?
+
+#. Why might ``assertIn`` be a better choice in testing the text in
+ responses than ``assertEqual``?
+
+.. seealso:: :ref:`views_chapter`,
+ :ref:`view_config_chapter`, and
+ :ref:`debugging_view_configuration`
+
diff --git a/docs/quick_tutorial/views/development.ini b/docs/quick_tutorial/views/development.ini
new file mode 100644
index 000000000..470d92c57
--- /dev/null
+++ b/docs/quick_tutorial/views/development.ini
@@ -0,0 +1,40 @@
+[app:main]
+use = egg:tutorial
+pyramid.includes =
+ pyramid_debugtoolbar
+
+[server:main]
+use = egg:pyramid#wsgiref
+host = 0.0.0.0
+port = 6543
+
+# Begin logging configuration
+
+[loggers]
+keys = root, tutorial
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[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
diff --git a/docs/quick_tutorial/views/setup.py b/docs/quick_tutorial/views/setup.py
new file mode 100644
index 000000000..9997984d3
--- /dev/null
+++ b/docs/quick_tutorial/views/setup.py
@@ -0,0 +1,13 @@
+from setuptools import setup
+
+requires = [
+ 'pyramid',
+]
+
+setup(name='tutorial',
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+) \ No newline at end of file
diff --git a/docs/quick_tutorial/views/tutorial/__init__.py b/docs/quick_tutorial/views/tutorial/__init__.py
new file mode 100644
index 000000000..013d4538f
--- /dev/null
+++ b/docs/quick_tutorial/views/tutorial/__init__.py
@@ -0,0 +1,9 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ config = Configurator(settings=settings)
+ config.add_route('home', '/')
+ config.add_route('hello', '/howdy')
+ config.scan('.views')
+ return config.make_wsgi_app() \ No newline at end of file
diff --git a/docs/quick_tutorial/views/tutorial/tests.py b/docs/quick_tutorial/views/tutorial/tests.py
new file mode 100644
index 000000000..f1757757c
--- /dev/null
+++ b/docs/quick_tutorial/views/tutorial/tests.py
@@ -0,0 +1,44 @@
+import unittest
+
+from pyramid import testing
+
+
+class TutorialViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_home(self):
+ from .views import home
+
+ request = testing.DummyRequest()
+ response = home(request)
+ self.assertEqual(response.status_code, 200)
+ self.assertIn(b'Visit', response.body)
+
+ def test_hello(self):
+ from .views import hello
+
+ request = testing.DummyRequest()
+ response = hello(request)
+ self.assertEqual(response.status_code, 200)
+ self.assertIn(b'Go back', response.body)
+
+
+class TutorialFunctionalTests(unittest.TestCase):
+ def setUp(self):
+ from tutorial import main
+ app = main({})
+ from webtest import TestApp
+
+ self.testapp = TestApp(app)
+
+ def test_home(self):
+ res = self.testapp.get('/', status=200)
+ self.assertIn(b'<body>Visit', res.body)
+
+ def test_hello(self):
+ res = self.testapp.get('/howdy', status=200)
+ self.assertIn(b'<body>Go back', res.body)
diff --git a/docs/quick_tutorial/views/tutorial/views.py b/docs/quick_tutorial/views/tutorial/views.py
new file mode 100644
index 000000000..6ff149d7b
--- /dev/null
+++ b/docs/quick_tutorial/views/tutorial/views.py
@@ -0,0 +1,14 @@
+from pyramid.response import Response
+from pyramid.view import view_config
+
+
+# First view, available at http://localhost:6543/
+@view_config(route_name='home')
+def home(request):
+ return Response('<body>Visit <a href="/howdy">hello</a></body>')
+
+
+# /howdy
+@view_config(route_name='hello')
+def hello(request):
+ return Response('<body>Go back <a href="/">home</a></body>')
diff --git a/docs/tutorials/bfg/index.rst b/docs/tutorials/bfg/index.rst
deleted file mode 100644
index 1abb26466..000000000
--- a/docs/tutorials/bfg/index.rst
+++ /dev/null
@@ -1,204 +0,0 @@
-.. index::
- single: converting a BFG app
- single: bfg2pyramid
-
-.. _converting_a_bfg_app:
-
-Converting a :mod:`repoze.bfg` Application to :app:`Pyramid`
-============================================================
-
-Prior iterations of :app:`Pyramid` were released as a package named
-:mod:`repoze.bfg`. :mod:`repoze.bfg` users are encouraged to upgrade
-their deployments to :app:`Pyramid`, as, after the first final release
-of :app:`Pyramid`, further feature development on :mod:`repoze.bfg`
-will cease.
-
-Most existing :mod:`repoze.bfg` applications can be converted to a
-:app:`Pyramid` application in a completely automated fashion.
-However, if your application depends on packages which are not "core"
-parts of :mod:`repoze.bfg` but which nonetheless have ``repoze.bfg``
-in their names (e.g. ``repoze.bfg.skins``,
-``repoze.bfg.traversalwrapper``, ``repoze.bfg.jinja2``), you will need
-to find an analogue for each. For example, by the time you read this,
-there will be a ``pyramid_jinja2`` package, which can be used instead
-of ``repoze.bfg.jinja2``. If an analogue does not seem to exist for a
-``repoze.bfg`` add-on package that your application uses, please email
-the `Pylons-devel <http://groups.google.com/group/pylons-devel>`_
-maillist; we'll convert the package to a :app:`Pyramid` analogue for
-you.
-
-Here's how to convert a :mod:`repoze.bfg` application to a
-:app:`Pyramid` application:
-
-#. Ensure that your application works under :mod:`repoze.bfg` *version
- 1.3 or better*. See
- `http://docs.repoze.org/bfg/1.3/narr/install.html
- <http://docs.repoze.org/bfg/1.3/narr/install.html>`_ for
- :mod:`repoze.bfg` 1.3 installation instructions. If your
- application has an automated test suite, run it while your
- application is using :mod:`repoze.bfg` 1.3+. Otherwise, test it
- manually. It is only safe to proceed to the next step once your
- application works under :mod:`repoze.bfg` 1.3+.
-
- If your application has a proper set of dependencies, and a
- standard automated test suite, you might test your
- :mod:`repoze.bfg` application against :mod:`repoze.bfg` 1.3 like
- so:
-
- .. code-block:: bash
-
- $ $VENV/bin/python setup.py test
-
- ``bfgenv`` above will be the virtualenv into which you've installed
- :mod:`repoze.bfg` 1.3.
-
-#. Install :app:`Pyramid` into a *separate* virtualenv as per the
- instructions in :ref:`installing_chapter`. The :app:`Pyramid`
- virtualenv should be separate from the one you've used to install
- :mod:`repoze.bfg`. A quick way to do this:
-
- .. code-block:: bash
-
- $ cd ~
- $ virtualenv pyramidenv
- $ cd pyramidenv
- $ $VENV/bin/easy_install pyramid
-
-#. Put a *copy* of your :mod:`repoze.bfg` application into a temporary
- location (perhaps by checking a fresh copy of the application out
- of a version control repository). For example:
-
- .. code-block:: bash
-
- $ cd /tmp
- $ svn co http://my.server/my/bfg/application/trunk bfgapp
-
-#. Use the ``bfg2pyramid`` script present in the ``bin`` directory of
- the :app:`Pyramid` virtualenv to convert all :mod:`repoze.bfg`
- Python import statements into compatible :app:`Pyramid` import
- statements. ``bfg2pyramid`` will also fix ZCML directive usages of
- common :mod:`repoze.bfg` directives. You invoke ``bfg2pyramid`` by
- passing it the *path* of the copy of your application. The path
- passed should contain a "setup.py" file, representing your
- :mod:`repoze.bfg` application's setup script. ``bfg2pyramid`` will
- change the copy of the application *in place*.
-
- .. code-block:: bash
-
- $ ~/pyramidenv/bfg2pyramid /tmp/bfgapp
-
- ``bfg2pyramid`` will convert the following :mod:`repoze.bfg`
- application aspects to :app:`Pyramid` compatible analogues:
-
- - Python ``import`` statements naming :mod:`repoze.bfg` APIs will
- be converted to :app:`Pyramid` compatible ``import`` statements.
- Every Python file beneath the top-level path will be visited and
- converted recursively, except Python files which live in
- directories which start with a ``.`` (dot).
-
- - Each ZCML file found (recursively) within the path will have the
- default ``xmlns`` attribute attached to the ``configure`` tag
- changed from ``http://namespaces.repoze.org/bfg`` to
- ``http://pylonshq.com/pyramid``. Every ZCML file beneath the
- top-level path (files ending with ``.zcml``) will be visited and
- converted recursively, except ZCML files which live in
- directories which start with a ``.`` (dot).
-
- - ZCML files which contain directives that have attributes which
- name a ``repoze.bfg`` API module or attribute of an API module
- (e.g. ``context="repoze.bfg.exceptions.NotFound"``) will be
- converted to :app:`Pyramid` compatible ZCML attributes
- (e.g. ``context="pyramid.exceptions.NotFound``). Every ZCML file
- beneath the top-level path (files ending with ``.zcml``) will be
- visited and converted recursively, except ZCML files which live
- in directories which start with a ``.`` (dot).
-
-#. Edit the ``setup.py`` file of the application you've just converted
- (if you've been using the example paths, this will be
- ``/tmp/bfgapp/setup.py``) to depend on the ``pyramid`` distribution
- instead the of ``repoze.bfg`` distribution in its
- ``install_requires`` list. If you used a scaffold to
- create the :mod:`repoze.bfg` application, you can do so by changing
- the ``requires`` line near the top of the ``setup.py`` file. The
- original may look like this:
-
- .. code-block:: text
-
- requires = ['repoze.bfg', ... other dependencies ...]
-
- Edit the ``setup.py`` so it has:
-
- .. code-block:: text
-
- requires = ['pyramid', ... other dependencies ...]
-
- All other install-requires and tests-requires dependencies save for
- the one on ``repoze.bfg`` can remain the same.
-
-#. Convert any ``install_requires`` dependencies your application has
- on other add-on packages which have ``repoze.bfg`` in their names
- to :app:`Pyramid` compatible analogues (e.g. ``repoze.bfg.jinja2``
- should be replaced with ``pyramid_jinja2``). You may need to
- adjust configuration options and/or imports in your
- :mod:`repoze.bfg` application after replacing these add-ons. Read
- the documentation of the :app:`Pyramid` add-on package for
- information.
-
-#. *Only if you use ZCML and add-ons which use ZCML*: The default
- ``xmlns`` of the ``configure`` tag in ZCML has changed. The
- ``bfg2pyramid`` script effects the default namespace change (it
- changes the ``configure`` tag default ``xmlns`` from
- ``http://namespaces.repoze.org/bfg`` to
- ``http://pylonshq.com/pyramid``).
-
- This means that uses of add-ons which define ZCML directives in the
- ``http://namespaces.repoze.org/bfg`` namespace will begin to "fail"
- (they're actually not really failing, but your ZCML assumes that
- they will always be used within a ``configure`` tag which names the
- ``http://namespaces.repoze.org/bfg`` namespace as its default
- ``xmlns``). Symptom: when you attempt to start the application, an
- error such as ``ConfigurationError: ('Unknown directive',
- u'http://namespaces.repoze.org/bfg', u'workflow')`` is printed to
- the console and the application fails to start. In such a case,
- either add an ``xmlns="http://namespaces.repoze.org/bfg"``
- attribute to each tag which causes a failure, or define a namespace
- alias in the configure tag and prefix each failing tag. For
- example, change this "failing" tag instance::
-
- <configure xmlns="http://pylonshq.com/pyramid">
- <failingtag attr="foo"/>
- </configure>
-
- To this, which will begin to succeed::
-
- <configure xmlns="http://pylonshq.com/pyramid"
- xmlns:bfg="http://namespaces.repoze.org/bfg">
- <bfg:failingtag attr="foo"/>
- </configure>
-
- You will also need to add the ``pyramid_zcml`` package to your
- ``setup.py`` ``install_requires`` list. In Pyramid, ZCML configuration
- became an optional add-on supported by the ``pyramid_zcml`` package.
-
-#. Retest your application using :app:`Pyramid`. This might be as
- easy as:
-
- .. code-block:: bash
-
- $ cd /tmp/bfgapp
- $ $VENV/bin/python setup.py test
-
-#. Fix any test failures.
-
-#. Fix any code which generates deprecation warnings.
-
-#. Start using the converted version of your application. Celebrate.
-
-Two terminological changes have been made to Pyramid which make its
-documentation and newer APIs different than those of ``repoze.bfg``. The
-concept that BFG called ``model`` is called ``resource`` in Pyramid and the
-concept that BFG called ``resource`` is called ``asset`` in Pyramid. Various
-APIs have changed as a result (although all have backwards compatible shims).
-Additionally, the environment variables that influenced server behavior which
-used to be prefixed with ``BFG_`` (such as ``BFG_DEBUG_NOTFOUND``) must now
-be prefixed with ``PYRAMID_``.
diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst
index 25ac9aabd..cdf52b73e 100644
--- a/docs/tutorials/wiki/basiclayout.rst
+++ b/docs/tutorials/wiki/basiclayout.rst
@@ -34,7 +34,10 @@ point happens to be the ``main`` function within the file named
factory` and the settings keywords parsed by :term:`PasteDeploy`. The root
factory is named ``root_factory``.
-#. *Line 15*. Register a "static view" which answers requests whose URL path
+#. *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 path
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
@@ -47,7 +50,7 @@ point happens to be the ``main`` function within the file named
package. Alternatively the scaffold could have used an *absolute* asset
specification as the path (``tutorial:static``).
-#. *Line 16*. Perform a :term:`scan`. A scan will find :term:`configuration
+#. *Line 17*. 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
@@ -56,7 +59,7 @@ point happens to be the ``main`` function within the file named
The scaffold could have equivalently said ``config.scan('tutorial')``, but
it chose to omit the package name argument.
-#. *Line 17*. Use the
+#. *Line 18*. Use the
:meth:`pyramid.config.Configurator.make_wsgi_app` method
to return a :term:`WSGI` application.
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index 23ee142af..e06468267 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -306,9 +306,9 @@ by the view (row 45). The view will use the ``body`` and
none of our tutorial views return in their dictionary.
``request`` is one of several
names that are available "by default" in a template when a template
- renderer is used. See :ref:`chameleon_template_renderers` for
+ renderer is used. See :ref:`renderer_system_values` for
information about other names that are available by default
- when a Chameleon template is used as a renderer.
+ when a template is used as a renderer.
Static Assets
-------------
diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py
index 5d87fedbf..5ab4f73cd 100644
--- a/docs/tutorials/wiki/src/authorization/setup.py
+++ b/docs/tutorials/wiki/src/authorization/setup.py
@@ -10,6 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
'pyramid_zodbconn',
'transaction',
'pyramid_tm',
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
index 8ea8f8fa3..39b94abd1 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
@@ -21,6 +21,7 @@ def main(global_config, **settings):
config = Configurator(root_factory=root_factory, settings=settings)
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py
index 75ba02611..da79881ab 100644
--- a/docs/tutorials/wiki/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki/src/basiclayout/setup.py
@@ -10,6 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
'pyramid_zodbconn',
'transaction',
'pyramid_tm',
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
index c3bb87a62..f2a86df47 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
@@ -12,6 +12,7 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(root_factory=root_factory, settings=settings)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py
index 75ba02611..da79881ab 100644
--- a/docs/tutorials/wiki/src/models/setup.py
+++ b/docs/tutorials/wiki/src/models/setup.py
@@ -10,6 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
'pyramid_zodbconn',
'transaction',
'pyramid_tm',
diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py
index c3bb87a62..f2a86df47 100644
--- a/docs/tutorials/wiki/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py
@@ -12,6 +12,7 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(root_factory=root_factory, settings=settings)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py
index 5ff7b545c..2e7ed2398 100644
--- a/docs/tutorials/wiki/src/tests/setup.py
+++ b/docs/tutorials/wiki/src/tests/setup.py
@@ -10,6 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
'pyramid_zodbconn',
'transaction',
'pyramid_tm',
diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
index 8ea8f8fa3..bd3c5619f 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
@@ -19,6 +19,7 @@ def main(global_config, **settings):
'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config = Configurator(root_factory=root_factory, settings=settings)
+ config.include('pyramid_chameleon')
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py
index 5d87fedbf..5ab4f73cd 100644
--- a/docs/tutorials/wiki/src/views/setup.py
+++ b/docs/tutorials/wiki/src/views/setup.py
@@ -10,6 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
'pyramid_zodbconn',
'transaction',
'pyramid_tm',
diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py
index c3bb87a62..f2a86df47 100644
--- a/docs/tutorials/wiki/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py
@@ -12,6 +12,7 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(root_factory=root_factory, settings=settings)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst
index e40dc286b..e724f3e18 100644
--- a/docs/tutorials/wiki/tests.rst
+++ b/docs/tutorials/wiki/tests.rst
@@ -59,8 +59,8 @@ Change the ``requires`` list in ``setup.py`` to include ``WebTest``.
.. literalinclude:: src/tests/setup.py
:linenos:
:language: python
- :lines: 11-21
- :emphasize-lines: 10
+ :lines: 11-22
+ :emphasize-lines: 11
After we've added a dependency on WebTest in ``setup.py``, we need to rerun
``setup.py develop`` to get WebTest installed into our virtualenv. Assuming
diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst
index 01c301e74..cf20db6d7 100644
--- a/docs/tutorials/wiki2/authorization.rst
+++ b/docs/tutorials/wiki2/authorization.rst
@@ -83,7 +83,7 @@ statement at the head:
Add the following class definition:
.. literalinclude:: src/authorization/tutorial/models.py
- :lines: 36-40
+ :lines: 33-37
:linenos:
:language: python
@@ -203,7 +203,7 @@ Go back to ``tutorial/tutorial/__init__.py`` and add these two
routes:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 30-31
+ :lines: 31-32
:linenos:
:language: python
@@ -329,7 +329,7 @@ when we're done:
.. literalinclude:: src/authorization/tutorial/__init__.py
:linenos:
- :emphasize-lines: 2-3,7,21-23,25-27,30-31
+ :emphasize-lines: 2-3,7,21-23,25-27,31-32
:language: python
(Only the highlighted lines need to be added.)
@@ -339,7 +339,7 @@ when we're done:
.. literalinclude:: src/authorization/tutorial/models.py
:linenos:
- :emphasize-lines: 1-4,36-40
+ :emphasize-lines: 1-4,33-37
:language: python
(Only the highlighted lines need to be added.)
diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst
index 0193afab4..05781c044 100644
--- a/docs/tutorials/wiki2/basiclayout.rst
+++ b/docs/tutorials/wiki2/basiclayout.rst
@@ -82,11 +82,18 @@ dictionary of settings parsed from the ``.ini`` file, which contains
deployment-related values such as ``pyramid.reload_templates``,
``db_string``, etc.
+Next, include :term:`Chameleon` templating bindings so that we can use
+renderers with the ``.pt`` extension within our project.
+
+ .. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 17
+ :language: py
+
``main`` now calls :meth:`pyramid.config.Configurator.add_static_view` with
two arguments: ``static`` (the name), and ``static`` (the path):
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 17
+ :lines: 18
:language: py
This registers a static resource view which will match any URL that starts
@@ -104,7 +111,7 @@ via the :meth:`pyramid.config.Configurator.add_route` method that will be
used when the URL is ``/``:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 18
+ :lines: 19
:language: py
Since this route has a ``pattern`` equalling ``/`` it is the route that will
@@ -118,7 +125,7 @@ view configuration will be registered, which will allow one of our
application URLs to be mapped to some code.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 19
+ :lines: 20
:language: py
Finally, ``main`` is finished configuring things, so it uses the
@@ -126,7 +133,7 @@ Finally, ``main`` is finished configuring things, so it uses the
:term:`WSGI` application:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 20
+ :lines: 21
:language: py
View Declarations via ``views.py``
@@ -225,10 +232,17 @@ To give a simple example of a model class, we define one named ``MyModel``:
:linenos:
:language: py
-Our example model has an ``__init__`` method that takes two arguments
-(``name``, and ``value``). It stores these values as ``self.name`` and
-``self.value`` on the instance created by the ``__init__`` function itself.
-The ``MyModel`` class also has a ``__tablename__`` attribute. This informs
+Our example model does not require an ``__init__`` method because SQLAlchemy
+supplies for us a default constructor if one is not already present,
+which accepts keyword arguments of the same name as that of the mapped attributes.
+
+.. note:: Example usage of MyModel:
+
+ .. code-block:: python
+
+ johnny = MyModel(name="John Doe", value=10)
+
+The ``MyModel`` class has a ``__tablename__`` attribute. This informs
SQLAlchemy which table to use to store the data representing instances of this
class.
diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst
index 60427a911..e30af12b2 100644
--- a/docs/tutorials/wiki2/definingmodels.rst
+++ b/docs/tutorials/wiki2/definingmodels.rst
@@ -24,7 +24,7 @@ following:
.. literalinclude:: src/models/tutorial/models.py
:linenos:
:language: py
- :emphasize-lines: 20-22,25,27,29
+ :emphasize-lines: 20-22,25
(The highlighted lines are the ones that need to be changed.)
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index a1e2313f3..49dbed50f 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -30,7 +30,7 @@ Open ``tutorial/setup.py`` and edit it to look like the following:
.. literalinclude:: src/views/setup.py
:linenos:
:language: python
- :emphasize-lines: 19
+ :emphasize-lines: 20
(Only the highlighted line needs to be added.)
@@ -272,9 +272,9 @@ by the view (row 45). The view will use the ``body`` and
none of our tutorial views return in their dictionary.
``request`` is one of several
names that are available "by default" in a template when a template
- renderer is used. See :ref:`chameleon_template_renderers` for
+ renderer is used. See :ref:`renderer_system_values` for
information about other names that are available by default
- when a Chameleon template is used as a renderer.
+ when a template is used as a renderer.
Static Assets
-------------
@@ -335,7 +335,7 @@ something like:
.. literalinclude:: src/views/tutorial/__init__.py
:linenos:
:language: python
- :emphasize-lines: 18-21
+ :emphasize-lines: 19-22
(The highlighted lines are the ones that need to be added or edited.)
diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py
index e8fa8f396..09bd63d33 100644
--- a/docs/tutorials/wiki2/src/authorization/setup.py
+++ b/docs/tutorials/wiki2/src/authorization/setup.py
@@ -10,10 +10,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
'SQLAlchemy',
'transaction',
- 'pyramid_tm',
- 'pyramid_debugtoolbar',
'zope.sqlalchemy',
'waitress',
'docutils',
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
index d08e55bf9..2ada42171 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
@@ -25,6 +25,7 @@ def main(global_config, **settings):
root_factory='tutorial.models.RootFactory')
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('view_wiki', '/')
config.add_route('login', '/login')
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models.py b/docs/tutorials/wiki2/src/authorization/tutorial/models.py
index 91e5a0019..4f7e1e024 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models.py
@@ -29,9 +29,6 @@ class Page(Base):
name = Column(Text, unique=True)
data = Column(Text)
- def __init__(self, name, data):
- self.name = name
- self.data = data
class RootFactory(object):
__acl__ = [ (Allow, Everyone, 'view'),
diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py
index e7d318128..15e7e5923 100644
--- a/docs/tutorials/wiki2/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki2/src/basiclayout/setup.py
@@ -10,10 +10,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
'SQLAlchemy',
'transaction',
- 'pyramid_tm',
- 'pyramid_debugtoolbar',
'zope.sqlalchemy',
'waitress',
]
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
index aac7c5e69..867049e4f 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
@@ -14,6 +14,7 @@ def main(global_config, **settings):
DBSession.configure(bind=engine)
Base.metadata.bind = engine
config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.scan()
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py
index aeeb9df64..0cdd4bbc3 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py
@@ -22,7 +22,3 @@ class MyModel(Base):
id = Column(Integer, primary_key=True)
name = Column(Text, unique=True)
value = Column(Integer)
-
- def __init__(self, name, value):
- self.name = name
- self.value = value
diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py
index e7d318128..15e7e5923 100644
--- a/docs/tutorials/wiki2/src/models/setup.py
+++ b/docs/tutorials/wiki2/src/models/setup.py
@@ -10,10 +10,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
'SQLAlchemy',
'transaction',
- 'pyramid_tm',
- 'pyramid_debugtoolbar',
'zope.sqlalchemy',
'waitress',
]
diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py
index aac7c5e69..867049e4f 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py
@@ -14,6 +14,7 @@ def main(global_config, **settings):
DBSession.configure(bind=engine)
Base.metadata.bind = engine
config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.scan()
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models.py b/docs/tutorials/wiki2/src/models/tutorial/models.py
index 9a078d757..f028c917a 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/models.py
@@ -23,7 +23,3 @@ class Page(Base):
id = Column(Integer, primary_key=True)
name = Column(Text, unique=True)
data = Column(Text)
-
- def __init__(self, name, data):
- self.name = name
- self.data = data
diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py
index c3da36b39..d8486e462 100644
--- a/docs/tutorials/wiki2/src/tests/setup.py
+++ b/docs/tutorials/wiki2/src/tests/setup.py
@@ -10,10 +10,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
'SQLAlchemy',
'transaction',
- 'pyramid_tm',
- 'pyramid_debugtoolbar',
'zope.sqlalchemy',
'waitress',
'docutils',
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
index d08e55bf9..cee89184b 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
@@ -23,6 +23,7 @@ def main(global_config, **settings):
authz_policy = ACLAuthorizationPolicy()
config = Configurator(settings=settings,
root_factory='tutorial.models.RootFactory')
+ config.include('pyramid_chameleon')
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models.py b/docs/tutorials/wiki2/src/tests/tutorial/models.py
index 91e5a0019..4f7e1e024 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/models.py
@@ -29,9 +29,6 @@ class Page(Base):
name = Column(Text, unique=True)
data = Column(Text)
- def __init__(self, name, data):
- self.name = name
- self.data = data
class RootFactory(object):
__acl__ = [ (Allow, Everyone, 'view'),
diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py
index e8fa8f396..09bd63d33 100644
--- a/docs/tutorials/wiki2/src/views/setup.py
+++ b/docs/tutorials/wiki2/src/views/setup.py
@@ -10,10 +10,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
'SQLAlchemy',
'transaction',
- 'pyramid_tm',
- 'pyramid_debugtoolbar',
'zope.sqlalchemy',
'waitress',
'docutils',
diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py
index c95bfdbf8..37cae1997 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py
@@ -14,6 +14,7 @@ def main(global_config, **settings):
DBSession.configure(bind=engine)
Base.metadata.bind = engine
config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('view_wiki', '/')
config.add_route('view_page', '/{pagename}')
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models.py b/docs/tutorials/wiki2/src/views/tutorial/models.py
index 9a078d757..f028c917a 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/models.py
@@ -23,7 +23,3 @@ class Page(Base):
id = Column(Integer, primary_key=True)
name = Column(Text, unique=True)
data = Column(Text)
-
- def __init__(self, name, data):
- self.name = name
- self.data = data
diff --git a/docs/tutorials/wiki2/tests.rst b/docs/tutorials/wiki2/tests.rst
index 33b5d35c1..9aca0c5b7 100644
--- a/docs/tutorials/wiki2/tests.rst
+++ b/docs/tutorials/wiki2/tests.rst
@@ -54,8 +54,8 @@ Change the ``requires`` list in ``setup.py`` to include ``WebTest``.
.. literalinclude:: src/tests/setup.py
:linenos:
:language: python
- :lines: 11-21
- :emphasize-lines: 10
+ :lines: 11-22
+ :emphasize-lines: 11
After we've added a dependency on WebTest in ``setup.py``, we need to rerun
``setup.py develop`` to get WebTest installed into our virtualenv. Assuming
diff --git a/docs/whatsnew-1.0.rst b/docs/whatsnew-1.0.rst
index d1f3046ca..8750863e7 100644
--- a/docs/whatsnew-1.0.rst
+++ b/docs/whatsnew-1.0.rst
@@ -92,7 +92,7 @@ BFG Conversion Script
The ``bfg2pyramid`` conversion script performs a mostly automated conversion
of an existing :mod:`repoze.bfg` application to Pyramid. The process is
-described in :ref:`converting_a_bfg_app`.
+described in "Converting a BFG Application to Pyramid".
Scaffold Improvements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -203,8 +203,8 @@ Mako
~~~~
In addition to Chameleon templating, Pyramid now also provides built-in
-support for :term:`Mako` templating. See :ref:`mako_templates` for more
-information.
+support for :term:`Mako` templating. See
+:ref:`available_template_system_bindings` for more information.
URL Dispatch
~~~~~~~~~~~~
diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst
index cc63017df..086c12ca2 100644
--- a/docs/whatsnew-1.1.rst
+++ b/docs/whatsnew-1.1.rst
@@ -540,11 +540,10 @@ Deprecations and Behavior Differences
within a static view returns the index.html properly. See also
https://github.com/Pylons/pyramid/issues/67.
-- Deprecated the
- :meth:`pyramid.config.Configurator.set_renderer_globals_factory` method and
- the ``renderer_globals`` Configurator constructor parameter. Users should
- convert code using this feature to use a BeforeRender event. See the section
- :ref:`beforerender_event` in the Hooks chapter.
+- Deprecated the ``pyramid.config.Configurator.set_renderer_globals_factory``
+ method and the ``renderer_globals`` Configurator constructor parameter.
+ Users should convert code using this feature to use a BeforeRender event. See
+ the section :ref:`beforerender_event` in the Hooks chapter.
- In Pyramid 1.0, the :class:`pyramid.events.subscriber` directive behaved
contrary to the documentation when passed more than one interface object to
diff --git a/docs/whatsnew-1.5.rst b/docs/whatsnew-1.5.rst
index 2a01f43bc..57f93cbff 100644
--- a/docs/whatsnew-1.5.rst
+++ b/docs/whatsnew-1.5.rst
@@ -7,6 +7,130 @@ incompatibilities between the two versions and deprecations added to
:app:`Pyramid` 1.5, as well as software dependency changes and notable
documentation additions.
+Major Backwards Incompatibilities
+---------------------------------
+
+- Pyramid no longer depends on or configures the Mako and Chameleon templating
+ system renderers by default. Disincluding these templating systems by
+ default means that the Pyramid core has fewer dependencies and can run on
+ future platforms without immediate concern for the compatibility of its
+ templating add-ons. It also makes maintenance slightly more effective, as
+ different people can maintain the templating system add-ons that they
+ understand and care about without needing commit access to the Pyramid core,
+ and it allows users who just don't want to see any packages they don't use
+ come along for the ride when they install Pyramid.
+
+ This means that upon upgrading to Pyramid 1.5a2+, projects that use either
+ of these templating systems will see a traceback that ends something like
+ this when their application attempts to render a Chameleon or Mako template::
+
+ ValueError: No such renderer factory .pt
+
+ Or::
+
+ ValueError: No such renderer factory .mako
+
+ Or::
+
+ ValueError: No such renderer factory .mak
+
+ Support for Mako templating has been moved into an add-on package named
+ ``pyramid_mako``, and support for Chameleon templating has been moved into
+ an add-on package named ``pyramid_chameleon``. These packages are drop-in
+ replacements for the old built-in support for these templating langauges.
+ All you have to do is install them and make them active in your configuration
+ to register renderer factories for ``.pt`` and/or ``.mako`` (or ``.mak``) to
+ make your application work again.
+
+ To re-add support for Chameleon and/or Mako template renderers into your
+ existing projects, follow the below steps.
+
+ If you depend on Mako templates:
+
+ * Make sure the ``pyramid_mako`` package is installed. One way to do this
+ is by adding ``pyramid_mako`` to the ``install_requires`` section of your
+ package's ``setup.py`` file and afterwards rerunning ``setup.py develop``::
+
+ setup(
+ #...
+ install_requires=[
+ 'pyramid_mako', # new dependency
+ 'pyramid',
+ #...
+ ],
+ )
+
+ * Within the portion of your application which instantiates a Pyramid
+ :class:`~pyramid.config.Configurator` (often the ``main()`` function in
+ your project's ``__init__.py`` file), tell Pyramid to include the
+ ``pyramid_mako`` includeme::
+
+ config = Configurator(.....)
+ config.include('pyramid_mako')
+
+ If you depend on Chameleon templates:
+
+ * Make sure the ``pyramid_chameleon`` package is installed. One way to do
+ this is by adding ``pyramid_chameleon`` to the ``install_requires`` section
+ of your package's ``setup.py`` file and afterwards rerunning
+ ``setup.py develop``::
+
+ setup(
+ #...
+ install_requires=[
+ 'pyramid_chameleon', # new dependency
+ 'pyramid',
+ #...
+ ],
+ )
+
+ * Within the portion of your application which instantiates a Pyramid
+ :class:`~pyramid.config.Configurator` (often the ``main()`` function in
+ your project's ``__init__.py`` file), tell Pyramid to include the
+ ``pyramid_chameleon`` includeme::
+
+ config = Configurator(.....)
+ config.include('pyramid_chameleon')
+
+ Note that it's also fine to install these packages into *older* Pyramids for
+ forward compatibility purposes. Even if you don't upgrade to Pyramid 1.5
+ immediately, performing the above steps in a Pyramid 1.4 installation is
+ perfectly fine, won't cause any difference, and will give you forward
+ compatibility when you eventually do upgrade to Pyramid 1.5.
+
+ With the removal of Mako and Chameleon support from the core, some
+ unit tests that use the ``pyramid.renderers.render*`` methods may begin to
+ fail. If any of your unit tests are invoking either
+ ``pyramid.renderers.render()`` or ``pyramid.renderers.render_to_response()``
+ with either Mako or Chameleon templates then the
+ ``pyramid.config.Configurator`` instance in effect during
+ the unit test should be also be updated to include the addons, as shown
+ above. For example::
+
+ class ATest(unittest.TestCase):
+ def setUp(self):
+ self.config = pyramid.testing.setUp()
+ self.config.include('pyramid_mako')
+
+ def test_it(self):
+ result = pyramid.renderers.render('mypkg:templates/home.mako', {})
+
+ Or::
+
+ class ATest(unittest.TestCase):
+ def setUp(self):
+ self.config = pyramid.testing.setUp()
+ self.config.include('pyramid_chameleon')
+
+ def test_it(self):
+ result = pyramid.renderers.render('mypkg:templates/home.pt', {})
+
+- If you're using the Pyramid debug toolbar, when you upgrade Pyramid to
+ 1.5a2+, you'll also need to upgrade the ``pyramid_debugtoolbar`` package to
+ at least version 1.0.8, as older toolbar versions are not compatible with
+ Pyramid 1.5a2+ due to the removal of Mako support from the core. It's
+ fine to use this newer version of the toolbar code with older Pyramids too.
+
Feature Additions
-----------------
@@ -181,8 +305,19 @@ The feature additions in Pyramid 1.5 follow.
- The ``alchemy`` scaffold tests now provide better coverage. See
https://github.com/Pylons/pyramid/pull/1029
-Backwards Incompatibilities
----------------------------
+- Users can now provide dotted Python names to as the ``factory`` argument
+ the Configurator methods named
+ :meth:`~pyramid.config.Configurator.add_view_predicate`,
+ :meth:`~pyramid.config.Configurator.add_route_predicate` and
+ :meth:`~pyramid.config.Configurator.add_subscriber_predicate`. Instead of
+ passing the predicate factory directly, you can pass a dotted name which
+ refers to the factory.
+
+- :func:`pyramid.path.package_name` no longer thows an exception when resolving
+ the package name for namespace packages that have no ``__file__`` attribute.
+
+Other Backwards Incompatibilities
+---------------------------------
- Modified the :meth:`~pyramid.request.Reuqest.current_route_url` method. The
method previously returned the URL without the query string by default, it
@@ -210,6 +345,65 @@ Backwards Incompatibilities
attributes: ``virtual_path_tuple`` and ``physical_path_tuple``. These should
be the tuple form of the resource's path (physical and virtual).
+- Removed the ``request.response_*`` varying attributes (such
+ as``request.response_headers``) . These attributes had been deprecated
+ since Pyramid 1.1, and as per the deprecation policy, have now been removed.
+
+- ``request.response`` will no longer be mutated when using the
+ :func:`pyramid.renderers.render` API. Almost all renderers mutate the
+ ``request.response`` response object (for example, the JSON renderer sets
+ ``request.response.content_type`` to ``application/json``), but this is
+ only necessary when the renderer is generating a response; it was a bug
+ when it was done as a side effect of calling
+ :func:`pyramid.renderers.render`.
+
+- Removed the ``bfg2pyramid`` fixer script.
+
+- The :class:`pyramid.events.NewResponse` event is now sent **after** response
+ callbacks are executed. It previously executed before response callbacks
+ were executed. Rationale: it's more useful to be able to inspect the response
+ after response callbacks have done their jobs instead of before.
+
+- Removed the class named ``pyramid.view.static`` that had been deprecated
+ since Pyramid 1.1. Instead use :class:`pyramid.static.static_view` with the
+ ``use_subpath=True`` argument.
+
+- Removed the ``pyramid.view.is_response`` function that had been deprecated
+ since Pyramid 1.1. Use the :meth:`pyramid.request.Request.is_response`
+ method instead.
+
+- Removed the ability to pass the following arguments to
+ :meth:`pyramid.config.Configurator.add_route`: ``view``, ``view_context``.
+ ``view_for``, ``view_permission``, ``view_renderer``, and ``view_attr``.
+ Using these arguments had been deprecated since Pyramid 1.1. Instead of
+ passing view-related arguments to ``add_route``, use a separate call to
+ :meth:`pyramid.config.Configurator.add_view` to associate a view with a route
+ using its ``route_name`` argument. Note that this impacts the
+ :meth:`pyramid.config.Configurator.add_static_view` function too, because
+ it delegates to``add_route``.
+
+- Removed the ability to influence and query a :class:`pyramid.request.Request`
+ object as if it were a dictionary. Previously it was possible to use methods
+ like ``__getitem__``, ``get``, ``items``, and other dictlike methods to
+ access values in the WSGI environment. This behavior had been deprecated
+ since Pyramid 1.1. Use methods of ``request.environ`` (a real dictionary)
+ instead.
+
+- Removed ancient backwards compatibily hack in
+ ``pyramid.traversal.DefaultRootFactory`` which populated the ``__dict__`` of
+ the factory with the matchdict values for compatibility with BFG 0.9.
+
+- The ``renderer_globals_factory`` argument to the
+ :class:`pyramid.config.Configurator` constructor and the
+ coresponding argument to :meth:`~pyramid.config.Configurator.setup_registry`
+ has been removed. The ``set_renderer_globals_factory`` method of
+ :class:`~pyramid.config.Configurator` has also been removed. The (internal)
+ ``pyramid.interfaces.IRendererGlobals`` interface was also removed. These
+ arguments, methods and interfaces had been deprecated since 1.1. Use a
+ ``BeforeRender`` event subscriber as documented in the "Hooks" chapter of the
+ Pyramid narrative documentation instead of providing renderer globals values
+ to the configurator.
+
Deprecations
------------
@@ -219,6 +413,10 @@ Deprecations
``foo#defname.mak`` in the view configuration definition and return a dict
only.
+- The :meth:`pyramid.config.Configurator.set_request_property` method now issues
+ a deprecation warning when used. It had been docs-deprecated in 1.4
+ but did not issue a deprecation warning when used.
+
Documentation Enhancements
--------------------------
@@ -231,5 +429,5 @@ Documentation Enhancements
Dependency Changes
------------------
-No dependency changes from Pyramid 1.4.X were made in Pyramid 1.5.
+- Pyramid no longer depends upon ``Mako`` or ``Chameleon``.
diff --git a/pyramid/authorization.py b/pyramid/authorization.py
index 1fd05e244..5e7baa19d 100644
--- a/pyramid/authorization.py
+++ b/pyramid/authorization.py
@@ -122,6 +122,9 @@ class ACLAuthorizationPolicy(object):
allowed_here = set()
denied_here = set()
+ if acl and callable(acl):
+ acl = acl()
+
for ace_action, ace_principal, ace_permissions in acl:
if not is_nonstr_iter(ace_permissions):
ace_permissions = [ace_permissions]
diff --git a/pyramid/chameleon_text.py b/pyramid/chameleon_text.py
deleted file mode 100644
index d2a943a28..000000000
--- a/pyramid/chameleon_text.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from zope.interface import implementer
-
-from pyramid.interfaces import ITemplateRenderer
-
-from pyramid.decorator import reify
-from pyramid import renderers
-
-def renderer_factory(info):
- return renderers.template_renderer_factory(info, TextTemplateRenderer)
-
-@implementer(ITemplateRenderer)
-class TextTemplateRenderer(object):
- def __init__(self, path, lookup, macro=None):
- self.path = path
- self.lookup = lookup
- # text template renderers have no macros, so we ignore the
- # macro arg
-
- @reify # avoid looking up reload_templates before manager pushed
- def template(self):
- from chameleon.zpt.template import PageTextTemplateFile
- return PageTextTemplateFile(self.path,
- auto_reload=self.lookup.auto_reload,
- debug=self.lookup.debug,
- translate=self.lookup.translate)
-
- def implementation(self):
- return self.template
-
- def __call__(self, value, system):
- try:
- system.update(value)
- except (TypeError, ValueError):
- raise ValueError('renderer was passed non-dictionary as value')
- result = self.template(**system)
- return result
-
diff --git a/pyramid/chameleon_zpt.py b/pyramid/chameleon_zpt.py
deleted file mode 100644
index 4ea5d506d..000000000
--- a/pyramid/chameleon_zpt.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from zope.interface import implementer
-
-from pyramid.interfaces import ITemplateRenderer
-from pyramid.decorator import reify
-from pyramid import renderers
-
-from chameleon.zpt.template import PageTemplateFile
-
-def renderer_factory(info):
- return renderers.template_renderer_factory(info, ZPTTemplateRenderer)
-
-class PyramidPageTemplateFile(PageTemplateFile):
- def cook(self, body):
- PageTemplateFile.cook(self, body)
- if self.macro:
- # render only the portion of the template included in a
- # define-macro named the value of self.macro
- macro_renderer = self.macros[self.macro].include
- self._render = macro_renderer
-
-@implementer(ITemplateRenderer)
-class ZPTTemplateRenderer(object):
- def __init__(self, path, lookup, macro=None):
- self.path = path
- self.lookup = lookup
- self.macro = macro
-
- @reify # avoid looking up reload_templates before manager pushed
- def template(self):
- tf = PyramidPageTemplateFile(
- self.path,
- auto_reload=self.lookup.auto_reload,
- debug=self.lookup.debug,
- translate=self.lookup.translate,
- macro=self.macro,
- )
- return tf
-
- def implementation(self):
- return self.template
-
- def __call__(self, value, system):
- try:
- system.update(value)
- except (TypeError, ValueError):
- raise ValueError('renderer was passed non-dictionary as value')
- result = self.template(**system)
- return result
-
diff --git a/pyramid/compat.py b/pyramid/compat.py
index 222810b3b..bfa345b88 100644
--- a/pyramid/compat.py
+++ b/pyramid/compat.py
@@ -160,7 +160,7 @@ if PY3: # pragma: no cover
return d.values()
def iterkeys_(d):
return d.keys()
-else:
+else: # pragma: no cover
def iteritems_(d):
return d.iteritems()
def itervalues_(d):
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index d4557d6b1..19c47cbd9 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -4,7 +4,6 @@ import logging
import operator
import os
import sys
-import warnings
import venusian
from webob.exc import WSGIHTTPException as WebobWSGIHTTPException
@@ -63,7 +62,6 @@ from pyramid.config.adapters import AdaptersConfiguratorMixin
from pyramid.config.assets import AssetsConfiguratorMixin
from pyramid.config.factories import FactoriesConfiguratorMixin
from pyramid.config.i18n import I18NConfiguratorMixin
-from pyramid.config.rendering import DEFAULT_RENDERERS
from pyramid.config.rendering import RenderingConfiguratorMixin
from pyramid.config.routes import RoutesConfiguratorMixin
from pyramid.config.security import SecurityConfiguratorMixin
@@ -173,15 +171,6 @@ class Configurator(
See :ref:`changing_the_request_factory`. By default it is ``None``,
which means use the default request factory.
- If ``renderer_globals_factory`` is passed, it should be a :term:`renderer
- globals` factory implementation or a :term:`dotted Python name` to the
- same. See :ref:`adding_renderer_globals`. By default, it is ``None``,
- which means use no renderer globals factory.
-
- .. deprecated:: 1.1
- Use a BeforeRender event subscriber as per :ref:`beforerender_event`
- in place of ``renderer_globals_factory``.
-
If ``default_permission`` is passed, it should be a
:term:`permission` string to be used as the default permission for
all view configuration registrations performed against this
@@ -273,7 +262,6 @@ class Configurator(
debug_logger=None,
locale_negotiator=None,
request_factory=None,
- renderer_globals_factory=None,
default_permission=None,
session_factory=None,
default_view_mapper=None,
@@ -304,7 +292,6 @@ class Configurator(
debug_logger=debug_logger,
locale_negotiator=locale_negotiator,
request_factory=request_factory,
- renderer_globals_factory=renderer_globals_factory,
default_permission=default_permission,
session_factory=session_factory,
default_view_mapper=default_view_mapper,
@@ -320,7 +307,6 @@ class Configurator(
debug_logger=None,
locale_negotiator=None,
request_factory=None,
- renderer_globals_factory=None,
default_permission=None,
session_factory=None,
default_view_mapper=None,
@@ -347,7 +333,6 @@ class Configurator(
self._fix_registry()
self._set_settings(settings)
- self._register_response_adapters()
if isinstance(debug_logger, string_types):
debug_logger = logging.getLogger(debug_logger)
@@ -357,9 +342,8 @@ class Configurator(
registry.registerUtility(debug_logger, IDebugLogger)
- for name, renderer in DEFAULT_RENDERERS:
- self.add_renderer(name, renderer)
-
+ self.add_default_response_adapters()
+ self.add_default_renderers()
self.add_default_view_predicates()
self.add_default_route_predicates()
@@ -378,12 +362,12 @@ class Configurator(
self.commit()
- # self.commit() should not be called after this point because the
- # following registrations should be treated as analogues of methods
- # called by the user after configurator construction. Rationale:
- # user-supplied implementations should be preferred rather than
- # add-on author implementations with the help of automatic conflict
- # resolution.
+ # self.commit() should not be called within this method after this
+ # point because the following registrations should be treated as
+ # analogues of methods called by the user after configurator
+ # construction. Rationale: user-supplied implementations should be
+ # preferred rather than add-on author implementations with the help of
+ # automatic conflict resolution.
if authentication_policy and not authorization_policy:
authorization_policy = ACLAuthorizationPolicy() # default
@@ -410,17 +394,6 @@ class Configurator(
if request_factory:
self.set_request_factory(request_factory)
- if renderer_globals_factory:
- warnings.warn(
- 'Passing ``renderer_globals_factory`` as a Configurator '
- 'constructor parameter is deprecated as of Pyramid 1.1. '
- 'Use a BeforeRender event subscriber as documented in the '
- '"Hooks" chapter of the Pyramid narrative documentation '
- 'instead',
- DeprecationWarning,
- 2)
- self.set_renderer_globals_factory(renderer_globals_factory,
- warn=False)
if default_permission:
self.set_default_permission(default_permission)
@@ -512,7 +485,7 @@ class Configurator(
'%s predicate named %s' % (type, name),
'%s predicate' % type)
intr['name'] = name
- intr['factory'] = factory
+ intr['factory'] = self.maybe_dotted(factory)
intr['weighs_more_than'] = weighs_more_than
intr['weighs_less_than'] = weighs_less_than
def register():
diff --git a/pyramid/config/adapters.py b/pyramid/config/adapters.py
index 5573b6748..f6a652e3d 100644
--- a/pyramid/config/adapters.py
+++ b/pyramid/config/adapters.py
@@ -1,3 +1,5 @@
+from webob import Response as WebobResponse
+
from functools import update_wrapper
from zope.interface import Interface
@@ -147,7 +149,8 @@ class AdaptersConfiguratorMixin(object):
Python identifier (it will be used as a ``**predicates`` keyword
argument to :meth:`~pyramid.config.Configurator.add_subscriber`).
- ``factory`` should be a :term:`predicate factory`.
+ ``factory`` should be a :term:`predicate factory` or :term:`dotted
+ Python name` which refers to a predicate factory.
See :ref:`subscriber_predicates` for more information.
@@ -192,10 +195,9 @@ class AdaptersConfiguratorMixin(object):
intr['type'] = type_or_iface
self.action(discriminator, register, introspectables=(intr,))
- def _register_response_adapters(self):
+ def add_default_response_adapters(self):
# cope with WebOb response objects that aren't decorated with IResponse
- from webob import Response as WebobResponse
- self.registry.registerSelfAdapter((WebobResponse,), IResponse)
+ self.add_response_adapter(None, WebobResponse)
@action_method
def add_traverser(self, adapter, iface=None):
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py
index d30df3b74..774125821 100644
--- a/pyramid/config/factories.py
+++ b/pyramid/config/factories.py
@@ -1,3 +1,4 @@
+from zope.deprecation import deprecate
from zope.interface import implementer
from pyramid.interfaces import (
@@ -179,12 +180,15 @@ class FactoriesConfiguratorMixin(object):
introspectables=(intr,))
@action_method
+ @deprecate('set_request_propery() is deprecated as of Pyramid 1.5; use '
+ 'add_request_method() with the property=True argument instead')
def set_request_property(self, callable, name=None, reify=False):
""" Add a property to the request object.
- .. deprecated:: 1.4
+ .. deprecated:: 1.5
:meth:`pyramid.config.Configurator.add_request_method` should be
- used instead.
+ used instead. (This method was docs-deprecated in 1.4 and
+ issues a real deprecation warning in 1.5).
.. versionadded:: 1.3
"""
diff --git a/pyramid/config/i18n.py b/pyramid/config/i18n.py
index 9eb59e1c7..69af0f9bc 100644
--- a/pyramid/config/i18n.py
+++ b/pyramid/config/i18n.py
@@ -1,18 +1,13 @@
import os
import sys
-from translationstring import ChameleonTranslate
-
from pyramid.interfaces import (
- IChameleonTranslate,
ILocaleNegotiator,
ITranslationDirectories,
)
from pyramid.exceptions import ConfigurationError
-from pyramid.i18n import get_localizer
from pyramid.path import package_path
-from pyramid.threadlocal import get_current_request
from pyramid.util import action_method
class I18NConfiguratorMixin(object):
@@ -108,18 +103,5 @@ class I18NConfiguratorMixin(object):
tdirs.insert(0, directory)
- if directories:
- # We actually only need an IChameleonTranslate function
- # utility to be registered zero or one times. We register the
- # same function once for each added translation directory,
- # which does too much work, but has the same effect.
- ctranslate = ChameleonTranslate(translator)
- self.registry.registerUtility(ctranslate, IChameleonTranslate)
-
self.action(None, register, introspectables=introspectables)
-def translator(msg):
- request = get_current_request()
- localizer = get_localizer(request)
- return localizer.translate(msg)
-
diff --git a/pyramid/config/rendering.py b/pyramid/config/rendering.py
index 356bf033e..68671d08e 100644
--- a/pyramid/config/rendering.py
+++ b/pyramid/config/rendering.py
@@ -1,31 +1,21 @@
-import warnings
-
from pyramid.interfaces import (
IRendererFactory,
- IRendererGlobalsFactory,
PHASE1_CONFIG,
)
from pyramid.util import action_method
-
-from pyramid import (
- renderers,
- chameleon_text,
- chameleon_zpt,
- )
-
-from pyramid.mako_templating import renderer_factory as mako_renderer_factory
+from pyramid import renderers
DEFAULT_RENDERERS = (
- ('.txt', chameleon_text.renderer_factory),
- ('.pt', chameleon_zpt.renderer_factory),
- ('.mak', mako_renderer_factory),
- ('.mako', mako_renderer_factory),
('json', renderers.json_renderer_factory),
('string', renderers.string_renderer_factory),
)
class RenderingConfiguratorMixin(object):
+ def add_default_renderers(self):
+ for name, renderer in DEFAULT_RENDERERS:
+ self.add_renderer(name, renderer)
+
@action_method
def add_renderer(self, name, factory):
"""
@@ -59,44 +49,3 @@ class RenderingConfiguratorMixin(object):
self.action((IRendererFactory, name), register, order=PHASE1_CONFIG,
introspectables=(intr,))
- @action_method
- def set_renderer_globals_factory(self, factory, warn=True):
- """ The object passed as ``factory`` should be an callable (or
- a :term:`dotted Python name` which refers to an callable) that
- will be used by the :app:`Pyramid` rendering machinery as a
- renderers global factory (see :ref:`adding_renderer_globals`).
-
- The ``factory`` callable must accept a single argument named
- ``system`` (which will be a dictionary) and it must return a
- dictionary. When an application uses a renderer, the
- factory's return dictionary will be merged into the ``system``
- dictionary, and therefore will be made available to the code
- which uses the renderer.
-
- .. deprecated:: 1.1
- Use a BeforeRender event subscriber as documented in the
- :ref:`hooks_chapter` chapter instead.
-
- .. note::
-
- Using the ``renderer_globals_factory`` argument
- to the :class:`pyramid.config.Configurator` constructor
- can be used to achieve the same purpose.
- """
- if warn:
- warnings.warn(
- 'Calling the ``set_renderer_globals`` method of a Configurator '
- 'is deprecated as of Pyramid 1.1. Use a BeforeRender event '
- 'subscriber as documented in the "Hooks" chapter of the '
- 'Pyramid narrative documentation instead',
- DeprecationWarning,
- 3)
-
- factory = self.maybe_dotted(factory)
- def register():
- self.registry.registerUtility(factory, IRendererGlobalsFactory)
- intr = self.introspectable('renderer globals factory', None,
- self.object_description(factory),
- 'renderer globals factory')
- intr['factory'] = factory
- self.action(IRendererGlobalsFactory, register)
diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py
index 0ed370c94..9dca9e51e 100644
--- a/pyramid/config/routes.py
+++ b/pyramid/config/routes.py
@@ -25,8 +25,6 @@ class RoutesConfiguratorMixin(object):
def add_route(self,
name,
pattern=None,
- view=None,
- view_for=None,
permission=None,
factory=None,
for_=None,
@@ -38,11 +36,7 @@ class RoutesConfiguratorMixin(object):
request_param=None,
traverse=None,
custom_predicates=(),
- view_permission=None,
renderer=None,
- view_renderer=None,
- view_context=None,
- view_attr=None,
use_global_views=False,
path=None,
pregenerator=None,
@@ -255,6 +249,8 @@ class RoutesConfiguratorMixin(object):
custom_predicates
+ .. deprecated:: 1.5
+
This value should be a sequence of references to custom
predicate callables. Use custom predicates when no set of
predefined predicates does what you need. Custom predicates
@@ -282,98 +278,19 @@ class RoutesConfiguratorMixin(object):
.. versionadded:: 1.4
- View-Related Arguments
-
- .. warning::
-
- The arguments described below have been deprecated as of
- :app:`Pyramid` 1.1. *Do not use these for new development; they
- should only be used to support older code bases which depend upon
- them.* Use a separate call to
- :meth:`pyramid.config.Configurator.add_view` to associate a view
- with a route using the ``route_name`` argument.
-
- view
-
- .. deprecated:: 1.1
-
- A Python object or :term:`dotted Python name` to the same
- object that will be used as a view callable when this route
- matches. e.g. ``mypackage.views.my_view``.
-
- view_context
-
- .. deprecated:: 1.1
-
- A class or an :term:`interface` or :term:`dotted Python
- name` to the same object which the :term:`context` of the
- view should match for the view named by the route to be
- used. This argument is only useful if the ``view``
- attribute is used. If this attribute is not specified, the
- default (``None``) will be used.
-
- If the ``view`` argument is not provided, this argument has
- no effect.
-
- This attribute can also be spelled as ``for_`` or ``view_for``.
-
- view_permission
-
- .. deprecated:: 1.1
-
- The permission name required to invoke the view associated
- with this route. e.g. ``edit``. (see
- :ref:`using_security_with_urldispatch` for more information
- about permissions).
-
- If the ``view`` attribute is not provided, this argument has
- no effect.
-
- This argument can also be spelled as ``permission``.
-
- view_renderer
-
- .. deprecated:: 1.1
-
- This is either a single string term (e.g. ``json``) or a
- string implying a path or :term:`asset specification`
- (e.g. ``templates/views.pt``). If the renderer value is a
- single term (does not contain a dot ``.``), the specified
- term will be used to look up a renderer implementation, and
- that renderer implementation will be used to construct a
- response from the view return value. If the renderer term
- contains a dot (``.``), the specified term will be treated
- as a path, and the filename extension of the last element in
- the path will be used to look up the renderer
- implementation, which will be passed the full path. The
- renderer implementation will be used to construct a response
- from the view return value. See
- :ref:`views_which_use_a_renderer` for more information.
-
- If the ``view`` argument is not provided, this argument has
- no effect.
-
- This argument can also be spelled as ``renderer``.
-
- view_attr
-
- .. deprecated:: 1.1
-
- The view machinery defaults to using the ``__call__`` method
- of the view callable (or the function itself, if the view
- callable is a function) to obtain a response dictionary.
- The ``attr`` value allows you to vary the method attribute
- used to obtain the response. For example, if your view was
- a class, and the class has a method named ``index`` and you
- wanted to use this method instead of the class' ``__call__``
- method to return the response, you'd say ``attr="index"`` in
- the view configuration for the view. This is
- most useful when the view definition is a class.
-
- If the ``view`` argument is not provided, this argument has no
- effect.
-
"""
+ if custom_predicates:
+ warnings.warn(
+ ('The "custom_predicates" argument to Configurator.add_route '
+ 'is deprecated as of Pyramid 1.5. Use '
+ '"config.add_route_predicate" and use the registered '
+ 'route predicate as a predicate argument to add_route '
+ 'instead. See "Adding A Third Party View, Route, or '
+ 'Subscriber Predicate" in the "Hooks" chapter of the '
+ 'documentation for more information.'),
+ DeprecationWarning,
+ stacklevel=3
+ )
# these are route predicates; if they do not match, the next route
# in the routelist will be tried
if request_method is not None:
@@ -499,19 +416,6 @@ class RoutesConfiguratorMixin(object):
self.action(('route', name), register_route_request_iface,
order=PHASE2_CONFIG, introspectables=introspectables)
- # deprecated adding views from add_route; must come after
- # route registration for purposes of autocommit ordering
- if any([view, view_context, view_permission, view_renderer,
- view_for, for_, permission, renderer, view_attr]):
- self._add_view_from_route(
- route_name=name,
- view=view,
- permission=view_permission or permission,
- context=view_context or view_for or for_,
- renderer=view_renderer or renderer,
- attr=view_attr,
- )
-
@action_method
def add_route_predicate(self, name, factory, weighs_more_than=None,
weighs_less_than=None):
@@ -523,7 +427,8 @@ class RoutesConfiguratorMixin(object):
Python identifier (it will be used as a keyword argument to
``add_view``).
- ``factory`` should be a :term:`predicate factory`.
+ ``factory`` should be a :term:`predicate factory` or :term:`dotted
+ Python name` which refers to a predicate factory.
See :ref:`view_and_route_predicates` for more information.
@@ -561,49 +466,3 @@ class RoutesConfiguratorMixin(object):
self.registry.registerUtility(mapper, IRoutesMapper)
return mapper
- def _add_view_from_route(self,
- route_name,
- view,
- context,
- permission,
- renderer,
- attr,
- ):
- if view:
- self.add_view(
- permission=permission,
- context=context,
- view=view,
- name='',
- route_name=route_name,
- renderer=renderer,
- attr=attr,
- )
- else:
- # prevent mistakes due to misunderstanding of how hybrid calls to
- # add_route and add_view interact
- if attr:
- raise ConfigurationError(
- 'view_attr argument not permitted without view '
- 'argument')
- if context:
- raise ConfigurationError(
- 'view_context argument not permitted without view '
- 'argument')
- if permission:
- raise ConfigurationError(
- 'view_permission argument not permitted without view '
- 'argument')
- if renderer:
- raise ConfigurationError(
- 'view_renderer argument not permitted without '
- 'view argument')
-
- warnings.warn(
- 'Passing view-related arguments to add_route() is deprecated as of '
- 'Pyramid 1.1. Use add_view() to associate a view with a route '
- 'instead. See "Deprecations" in "What\'s New in Pyramid 1.1" '
- 'within the general Pyramid documentation for further details.',
- DeprecationWarning,
- 4)
-
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 707c84043..233bbac12 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -1,6 +1,7 @@
import inspect
import operator
import os
+import warnings
from zope.interface import (
Interface,
@@ -1027,6 +1028,8 @@ class ViewsConfiguratorMixin(object):
custom_predicates
+ .. deprecated:: 1.5
+
This value should be a sequence of references to custom predicate
callables. Use custom predicates when no set of predefined
predicates do what you need. Custom predicates can be combined with
@@ -1050,6 +1053,19 @@ class ViewsConfiguratorMixin(object):
.. versionadded: 1.4a1
"""
+ if custom_predicates:
+ warnings.warn(
+ ('The "custom_predicates" argument to Configurator.add_view '
+ 'is deprecated as of Pyramid 1.5. Use '
+ '"config.add_view_predicate" and use the registered '
+ 'view predicate as a predicate argument to add_view instead. '
+ 'See "Adding A Third Party View, Route, or Subscriber '
+ 'Predicate" in the "Hooks" chapter of the documentation '
+ 'for more information.'),
+ DeprecationWarning,
+ stacklevel=4
+ )
+
view = self.maybe_dotted(view)
context = self.maybe_dotted(context)
for_ = self.maybe_dotted(for_)
@@ -1133,6 +1149,8 @@ class ViewsConfiguratorMixin(object):
attr, self.object_description(view))
else:
view_desc = self.object_description(view)
+
+ tmpl_intr = None
view_intr = self.introspectable('views',
discriminator,
@@ -1183,7 +1201,8 @@ class ViewsConfiguratorMixin(object):
renderer = renderers.RendererHelper(
name=None,
package=self.package,
- registry=self.registry)
+ registry=self.registry
+ )
if permission is None:
# intent: will be None if no default permission is registered
@@ -1314,6 +1333,22 @@ class ViewsConfiguratorMixin(object):
multiview,
(IExceptionViewClassifier, request_iface, context),
IMultiView, name=name)
+ renderer_type = getattr(renderer, 'type', None) # gard against None
+ intrspc = self.introspector
+ if (
+ renderer_type is not None and
+ tmpl_intr is not None and
+ intrspc is not None and
+ intrspc.get('renderer factories', renderer_type) is not None
+ ):
+ # allow failure of registered template factories to be deferred
+ # until view execution, like other bad renderer factories; if
+ # we tried to relate this to an existing renderer factory
+ # without checking if it the factory actually existed, we'd end
+ # up with a KeyError at startup time, which is inconsistent
+ # with how other bad renderer registrations behave (they throw
+ # a ValueError at view execution time)
+ tmpl_intr.relate('renderer factories', renderer.type)
if mapper:
mapper_intr = self.introspectable(
@@ -1339,7 +1374,6 @@ class ViewsConfiguratorMixin(object):
tmpl_intr['name'] = renderer.name
tmpl_intr['type'] = renderer.type
tmpl_intr['renderer'] = renderer
- tmpl_intr.relate('renderer factories', renderer.type)
introspectables.append(tmpl_intr)
if permission is not None:
# if a permission exists, register a permission introspectable
@@ -1369,7 +1403,8 @@ class ViewsConfiguratorMixin(object):
Python identifier (it will be used as a keyword argument to
``add_view`` by others).
- ``factory`` should be a :term:`predicate factory`.
+ ``factory`` should be a :term:`predicate factory` or :term:`dotted
+ Python name` which refers to a predicate factory.
See :ref:`view_and_route_predicates` for more information.
"""
@@ -1907,27 +1942,16 @@ class StaticURLInfo(object):
# Mutate extra to allow factory, etc to be passed through here.
# Treat permission specially because we'd like to default to
- # permissiveness (see docs of config.add_static_view). We need
- # to deal with both ``view_permission`` and ``permission``
- # because ``permission`` is used in the docs for add_static_view,
- # but ``add_route`` prefers ``view_permission``
- permission = extra.pop('view_permission', None)
- if permission is None:
- permission = extra.pop('permission', None)
+ # permissiveness (see docs of config.add_static_view).
+ permission = extra.pop('permission', None)
if permission is None:
permission = NO_PERMISSION_REQUIRED
- context = extra.pop('view_context', None)
- if context is None:
- context = extra.pop('view_for', None)
+ context = extra.pop('context', None)
if context is None:
context = extra.pop('for_', None)
- renderer = extra.pop('view_renderer', None)
- if renderer is None:
- renderer = extra.pop('renderer', None)
-
- attr = extra.pop('view_attr', None)
+ renderer = extra.pop('renderer', None)
# register a route using the computed view, permission, and
# pattern, plus any extras passed to us via add_static_view
@@ -1943,7 +1967,6 @@ class StaticURLInfo(object):
permission=permission,
context=context,
renderer=renderer,
- attr=attr
)
def register():
diff --git a/pyramid/encode.py b/pyramid/encode.py
index 65bc95032..9e190bc21 100644
--- a/pyramid/encode.py
+++ b/pyramid/encode.py
@@ -32,6 +32,10 @@ def urlencode(query, doseq=True):
See the Python stdlib documentation for ``urllib.urlencode`` for
more information.
+
+ .. versionchanged:: 1.5
+ In a key/value pair, if the value is ``None`` then it will be
+ dropped from the resulting output.
"""
try:
# presumed to be a dictionary
@@ -50,6 +54,8 @@ def urlencode(query, doseq=True):
x = _enc(x)
result += '%s%s=%s' % (prefix, k, x)
prefix = '&'
+ elif v is None:
+ result += '%s%s=' % (prefix, k)
else:
v = _enc(v)
result += '%s%s=%s' % (prefix, k, v)
diff --git a/pyramid/events.py b/pyramid/events.py
index 31af8e1fc..5179ab08a 100644
--- a/pyramid/events.py
+++ b/pyramid/events.py
@@ -192,10 +192,7 @@ class BeforeRender(dict):
event['mykey'] = 'foo'
An object of this type is sent as an event just before a :term:`renderer`
- is invoked (but *after* the -- deprecated -- application-level renderer
- globals factory added via
- :class:`pyramid.config.Configurator.set_renderer_globals_factory`, if
- any, has injected its own keys into the renderer globals dictionary).
+ is invoked.
If a subscriber adds a key via ``__setitem__`` that already exists in
the renderer globals dictionary, it will overwrite the older value there.
@@ -231,9 +228,9 @@ class BeforeRender(dict):
# {'mykey': 'somevalue'} is returned from the view
print(event.rendering_val['mykey'])
- In other words, :attr:`rendering_val` is the (non-system) value returned by a
- view or passed to ``render*`` as ``value``. This feature is new in Pyramid
- 1.2.
+ In other words, :attr:`rendering_val` is the (non-system) value returned
+ by a view or passed to ``render*`` as ``value``. This feature is new in
+ Pyramid 1.2.
For a description of the values present in the renderer globals dictionary,
see :ref:`renderer_system_values`.
diff --git a/pyramid/fixers/__init__.py b/pyramid/fixers/__init__.py
deleted file mode 100644
index 5bb534f79..000000000
--- a/pyramid/fixers/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# package
diff --git a/pyramid/fixers/fix_bfg_imports.py b/pyramid/fixers/fix_bfg_imports.py
deleted file mode 100644
index 0046aad30..000000000
--- a/pyramid/fixers/fix_bfg_imports.py
+++ /dev/null
@@ -1,220 +0,0 @@
-import os
-import re
-import sys
-
-from lib2to3.refactor import get_fixers_from_package
-from lib2to3.refactor import RefactoringTool
-from lib2to3.fixer_util import Name
-from lib2to3.fixer_util import attr_chain
-from lib2to3 import fixer_base
-
-MAPPING = {'repoze.bfg':'pyramid'}
-
-MODULE_NAMES = (
- 'compat',
- 'configuration',
- 'authentication',
- 'authorization',
- 'chameleon_text',
- 'chameleon_zpt',
- 'decorator',
- 'encode',
- 'events',
- 'exceptions',
- 'i18n',
- 'includes',
- 'interfaces',
- 'location',
- 'log',
- 'paster',
- 'path',
- 'registry',
- 'renderers',
- 'request',
- 'resource',
- 'router',
- 'scripting',
- 'security',
- 'settings',
- 'static',
- 'testing',
- 'tests',
- 'tests.test_configuration',
- 'tests.ccbugapp',
- 'tests.exceptionviewapp',
- 'tests.exceptionviewapp.models',
- 'tests.fixtureapp',
- 'tests.fixtureapp.models',
- 'tests.grokkedapp',
- 'tests.hybridapp',
- 'tests.localeapp',
- 'tests.restbugapp',
- 'tests.routesapp',
- 'threadlocal',
- 'traversal',
- 'urldispatch',
- 'url',
- 'view',
- 'wsgi',
- 'zcml',
- )
-
-for name in MODULE_NAMES:
- frm = 'repoze.bfg.' + name
- to = 'pyramid.' + name
- MAPPING[frm] = to
-
-def alternates(members):
- return "(" + "|".join(map(str, members)) + ")"
-
-def build_pattern(mapping=MAPPING):
- mod_list = []
-
- for key in mapping:
- splitted = key.split('.')
- joined = " '.' ".join(["'%s'" %s for s in splitted])
- mod_list.append(joined)
-
- mod_list = ' | '.join(
- ['module_name=dotted_name< %s >' %s for s in mod_list])
-
- yield """name_import=import_name< 'import' ((%s) |
- multiple_imports=dotted_as_names< any* (%s) any* >) >
- """ % (mod_list, mod_list)
- yield """import_from< 'from' (%s) 'import' ['(']
- ( any | import_as_name< any 'as' any > |
- import_as_names< any* >) [')'] >
- """ % mod_list
- yield """import_name< 'import' (dotted_as_name< (%s) 'as' any > |
- multiple_imports=dotted_as_names<
- any* dotted_as_name< (%s) 'as' any > any* >) >
- """ % (mod_list, mod_list)
-
- # Find usages of module members in code e.g. ``repoze.bfg`` or
- # ``repoze.bfg.configuration``
- # 'repoze' trailer< '.' 'bfg' > trailer< '.' 'configuration' >
- bare_names = []
- for key in mapping:
- splitted = key.split('.')
- tmp = ["'%s'" % splitted[0]]
- for thing in splitted[1:]:
- tmp.append(" trailer< '.' '%s' > " % thing)
- bare_name = ''.join(tmp)
- bare_names.append(bare_name)
-
- names = alternates(bare_names)
- yield "power< bare_with_attr=%s >" % names
-
-class FixBfgImports(fixer_base.BaseFix):
-
- mapping = MAPPING
- run_order = 8
-
- def build_pattern(self):
- pattern = "|".join(build_pattern(self.mapping))
- return pattern
-
- def compile_pattern(self):
- # We override this, so MAPPING can be pragmatically altered and the
- # changes will be reflected in PATTERN.
- self.PATTERN = self.build_pattern()
- super(FixBfgImports, self).compile_pattern()
-
- # Don't match the node if it's within another match.
- def match(self, node):
- match = super(FixBfgImports, self).match
- results = match(node)
- if results:
- # Module usage could be in the trailer of an attribute lookup, so we
- # might have nested matches when "bare_with_attr" is present.
- if "bare_with_attr" not in results and \
- any(match(obj) for obj in attr_chain(node, "parent")):
- return False
- return results
- return False
-
- def start_tree(self, tree, filename):
- super(FixBfgImports, self).start_tree(tree, filename)
- self.replace = {}
-
- def transform(self, node, results):
- # Mostly copied from fix_imports.py
- import_mod = results.get("module_name")
- if import_mod:
- try:
- mod_name = import_mod.value
- except AttributeError:
- # XXX: A hack to remove whitespace prefixes and suffixes
- mod_name = str(import_mod).strip()
- new_name = self.mapping[mod_name]
- import_mod.replace(Name(new_name, prefix=import_mod.prefix))
- if "name_import" in results:
- # If it's not a "from x import x, y" or "import x as y" import,
- # marked its usage to be replaced.
- self.replace[mod_name] = new_name
- if "multiple_imports" in results:
- # This is a nasty hack to fix multiple imports on a line (e.g.,
- # "import StringIO, urlparse"). The problem is that I can't
- # figure out an easy way to make a pattern recognize the keys of
- # MAPPING randomly sprinkled in an import statement.
- results = self.match(node)
- if results:
- self.transform(node, results)
- else:
- # Replace usage of the module.
- bare_name_text = ''.join(map(str,results['bare_with_attr'])).strip()
- new_name = self.replace.get(bare_name_text)
- bare_name = results["bare_with_attr"][0]
-
- if new_name:
- node.replace(Name(new_name, prefix=bare_name.prefix))
-
-MODULE_ALTERNATIVES = []
-for name in MODULE_NAMES:
- MODULE_ALTERNATIVES.append(r'\.' + re.escape(name)+r'[\w\.]*?')
-
-MODULE_ALTERNATIVES = '|'.join(MODULE_ALTERNATIVES)
-
-BFG_NS_RE = r'xmlns\s*?=\s*?[\'\"]http://namespaces\.repoze\.org/bfg[\'\"]'
-BFG_IN_ATTR = r'(repoze\.bfg)(%s)' % MODULE_ALTERNATIVES
-BFG_INCLUDE_IN_ATTR = r'repoze\.bfg\.includes'
-ATTR = re.compile(BFG_IN_ATTR, re.MULTILINE)
-INCLUDE_ATTR = re.compile(BFG_INCLUDE_IN_ATTR, re.MULTILINE)
-NS = re.compile(BFG_NS_RE, re.MULTILINE)
-
-def replace(match):
- return 'pyramid%s' % match.group(2)
-
-def fix_zcml(path):
- for root, dirs, files in os.walk(path):
- for file in files:
- if file.endswith('.zcml'):
- absfile = os.path.join(root, file)
- f = open(absfile, 'rb')
- text = f.read()
- f.close()
- newt = NS.sub('xmlns="http://pylonshq.com/pyramid"', text)
- newt = INCLUDE_ATTR.sub('pyramid_zcml', newt)
- newt = ATTR.sub(replace, newt)
- if text != newt:
- newf = open(absfile, 'wb')
- newf.write(newt)
- newf.flush()
- newf.close()
-
- for dir in dirs:
- if dir.startswith('.'):
- dirs.remove(dir)
-
-def main(argv=None):
- if argv is None:
- argv = sys.argv
- path = argv[1]
- fixer_names = get_fixers_from_package('pyramid.fixers')
- tool = RefactoringTool(fixer_names)
- tool.refactor([path], write=True)
- fix_zcml(path)
-
-if __name__ == '__main__':
- main()
-
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 3f43494a8..85b2227b4 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -616,15 +616,6 @@ class IRendererFactory(Interface):
""" Return an object that implements ``IRenderer``. ``info`` is an
object that implement ``IRendererInfo``. """
-class IRendererGlobalsFactory(Interface):
- def __call__(system_values):
- """ Return a dictionary of global renderer values (aka
- top-level template names). The ``system_values`` value passed
- in will be a dictionary that includes at least a ``request``
- key, indicating the current request, and the value
- ``renderer_name``, which will be the name of the renderer in
- use."""
-
class IViewPermission(Interface):
def __call__(context, request):
""" Return True if the permission allows, return False if it denies. """
@@ -853,19 +844,6 @@ class IPackageOverrides(IPEP302Loader):
# traversalwrapper)
VH_ROOT_KEY = 'HTTP_X_VHM_ROOT'
-class IChameleonLookup(Interface):
- translate = Attribute('IChameleonTranslate object')
- debug = Attribute('The ``debug_templates`` setting for this application')
- auto_reload = Attribute('The ``reload_templates`` setting for this app')
- def __call__(self, info):
- """ Return an ITemplateRenderer based on IRendererInfo ``info`` """
-
-class IChameleonTranslate(Interface):
- """ Internal interface representing a chameleon translate function """
- def __call__(msgid, domain=None, mapping=None, context=None,
- target_language=None, default=None):
- """ Translate a mess of arguments to a Unicode object """
-
class ILocalizer(Interface):
""" Localizer for a specific language """
diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py
deleted file mode 100644
index 01456c3d4..000000000
--- a/pyramid/mako_templating.py
+++ /dev/null
@@ -1,252 +0,0 @@
-import os
-import posixpath
-import sys
-import threading
-import warnings
-
-from zope.interface import (
- implementer,
- Interface,
- )
-
-from pyramid.asset import (
- resolve_asset_spec,
- abspath_from_asset_spec,
- )
-
-from pyramid.compat import (
- is_nonstr_iter,
- reraise,
- )
-
-from pyramid.interfaces import ITemplateRenderer
-from pyramid.settings import asbool
-from pyramid.util import DottedNameResolver
-
-def _no_mako(*arg, **kw): # pragma: no cover
- raise NotImplementedError(
- "'mako' package is not importable (maybe downgrade MarkupSafe to "
- "0.16 or below if you're using Python 3.2)"
- )
-
-try:
- from mako.lookup import TemplateLookup
-except (ImportError, SyntaxError, AttributeError): #pragma NO COVER
- class TemplateLookup(object):
- def __init__(self, **kw):
- for name in ('adjust_uri', 'get_template', 'filename_to_uri',
- 'put_string', 'put_template'):
- setattr(self, name, _no_mako)
- self.filesystem_checks = False
-
-try:
- from mako.exceptions import TopLevelLookupException
-except (ImportError, SyntaxError, AttributeError): #pragma NO COVER
- class TopLevelLookupException(Exception):
- pass
-
-try:
- from mako.exceptions import text_error_template
-except (ImportError, SyntaxError, AttributeError): #pragma NO COVER
- def text_error_template(lookup=None):
- _no_mako()
-
-
-class IMakoLookup(Interface):
- pass
-
-class PkgResourceTemplateLookup(TemplateLookup):
- """TemplateLookup subclass that handles asset specification URIs"""
- def adjust_uri(self, uri, relativeto):
- """Called from within a Mako template, avoids adjusting the
- uri if it looks like an asset specification"""
- # Don't adjust asset spec names
- isabs = os.path.isabs(uri)
- if (not isabs) and (':' in uri):
- return uri
- if not(isabs) and ('$' in uri):
- return uri.replace('$', ':')
- if relativeto is not None:
- relativeto = relativeto.replace('$', ':')
- if not(':' in uri) and (':' in relativeto):
- if uri.startswith('/'):
- return uri
- pkg, relto = relativeto.split(':')
- _uri = posixpath.join(posixpath.dirname(relto), uri)
- return '{0}:{1}'.format(pkg, _uri)
- if not(':' in uri) and not(':' in relativeto):
- return posixpath.join(posixpath.dirname(relativeto), uri)
- return TemplateLookup.adjust_uri(self, uri, relativeto)
-
- def get_template(self, uri):
- """Fetch a template from the cache, or check the filesystem
- for it
-
- In addition to the basic filesystem lookup, this subclass will
- use pkg_resource to load a file using the asset
- specification syntax.
-
- """
- isabs = os.path.isabs(uri)
- if (not isabs) and (':' in uri):
- # Windows can't cope with colons in filenames, so we replace the
- # colon with a dollar sign in the filename mako uses to actually
- # store the generated python code in the mako module_directory or
- # in the temporary location of mako's modules
- adjusted = uri.replace(':', '$')
- try:
- if self.filesystem_checks:
- return self._check(adjusted, self._collection[adjusted])
- else:
- return self._collection[adjusted]
- except KeyError:
- pname, path = resolve_asset_spec(uri)
- srcfile = abspath_from_asset_spec(path, pname)
- if os.path.isfile(srcfile):
- return self._load(srcfile, adjusted)
- raise TopLevelLookupException(
- "Can not locate template for uri %r" % uri)
- return TemplateLookup.get_template(self, uri)
-
-registry_lock = threading.Lock()
-
-class MakoRendererFactoryHelper(object):
- def __init__(self, settings_prefix=None):
- self.settings_prefix = settings_prefix
-
- def __call__(self, info):
- defname = None
- asset, ext = info.name.rsplit('.', 1)
- if '#' in asset:
- asset, defname = asset.rsplit('#', 1)
-
- path = '%s.%s' % (asset, ext)
- registry = info.registry
- settings = info.settings
- settings_prefix = self.settings_prefix
-
- if settings_prefix is None:
- settings_prefix = info.type +'.'
-
- lookup = registry.queryUtility(IMakoLookup, name=settings_prefix)
-
- def sget(name, default=None):
- return settings.get(settings_prefix + name, default)
-
- if lookup is None:
- reload_templates = settings.get('pyramid.reload_templates', None)
- if reload_templates is None:
- reload_templates = settings.get('reload_templates', False)
- reload_templates = asbool(reload_templates)
- directories = sget('directories', [])
- module_directory = sget('module_directory', None)
- input_encoding = sget('input_encoding', 'utf-8')
- error_handler = sget('error_handler', None)
- default_filters = sget('default_filters', 'h')
- imports = sget('imports', None)
- strict_undefined = asbool(sget('strict_undefined', False))
- preprocessor = sget('preprocessor', None)
- if not is_nonstr_iter(directories):
- directories = list(filter(None, directories.splitlines()))
- directories = [ abspath_from_asset_spec(d) for d in directories ]
- if module_directory is not None:
- module_directory = abspath_from_asset_spec(module_directory)
- if error_handler is not None:
- dotted = DottedNameResolver(info.package)
- error_handler = dotted.maybe_resolve(error_handler)
- if default_filters is not None:
- if not is_nonstr_iter(default_filters):
- default_filters = list(filter(
- None, default_filters.splitlines()))
- if imports is not None:
- if not is_nonstr_iter(imports):
- imports = list(filter(None, imports.splitlines()))
- if preprocessor is not None:
- dotted = DottedNameResolver(info.package)
- preprocessor = dotted.maybe_resolve(preprocessor)
-
-
- lookup = PkgResourceTemplateLookup(
- directories=directories,
- module_directory=module_directory,
- input_encoding=input_encoding,
- error_handler=error_handler,
- default_filters=default_filters,
- imports=imports,
- filesystem_checks=reload_templates,
- strict_undefined=strict_undefined,
- preprocessor=preprocessor
- )
-
- with registry_lock:
- registry.registerUtility(lookup, IMakoLookup,
- name=settings_prefix)
-
- return MakoLookupTemplateRenderer(path, defname, lookup)
-
-renderer_factory = MakoRendererFactoryHelper('mako.')
-
-class MakoRenderingException(Exception):
- def __init__(self, text):
- self.text = text
-
- def __repr__(self):
- return self.text
-
- __str__ = __repr__
-
-@implementer(ITemplateRenderer)
-class MakoLookupTemplateRenderer(object):
- """ Render a :term:`Mako` template using the template
- implied by the ``path`` argument.The ``path`` argument may be a
- package-relative path, an absolute path, or a :term:`asset
- specification`. If a defname is defined, in the form of
- package:path/to/template#defname.mako, a function named ``defname``
- inside the template will then be rendered.
- """
- warnings = warnings # for testing
-
- def __init__(self, path, defname, lookup):
- self.path = path
- self.defname = defname
- self.lookup = lookup
-
- def implementation(self):
- return self.lookup.get_template(self.path)
-
- def __call__(self, value, system):
- context = system.pop('context', None)
- if context is not None:
- system['_context'] = context
- # tuple returned to be deprecated
- if isinstance(value, tuple):
- self.warnings.warn(
- 'Using a tuple in the form (\'defname\', {}) to render a '
- 'Mako partial will be deprecated in the future. Use a '
- 'Mako template renderer as documented in the "Using A '
- 'Mako def name Within a Renderer Name" chapter of the '
- 'Pyramid narrative documentation instead',
- DeprecationWarning,
- 3)
- self.defname, value = value
- try:
- system.update(value)
- except (TypeError, ValueError):
- raise ValueError('renderer was passed non-dictionary as value')
- template = self.implementation()
- if self.defname is not None:
- template = template.get_def(self.defname)
- try:
- result = template.render_unicode(**system)
- except:
- try:
- exc_info = sys.exc_info()
- errtext = text_error_template().render(
- error=exc_info[1],
- traceback=exc_info[2]
- )
- reraise(MakoRenderingException(errtext), None, exc_info[2])
- finally:
- del exc_info
-
- return result
diff --git a/pyramid/path.py b/pyramid/path.py
index eb92ea62b..470e766f8 100644
--- a/pyramid/path.py
+++ b/pyramid/path.py
@@ -33,8 +33,12 @@ def package_name(pkg_or_module):
name of the package itself."""
if pkg_or_module is None or pkg_or_module.__name__ == '__main__':
return '__main__'
- pkg_filename = pkg_or_module.__file__
pkg_name = pkg_or_module.__name__
+ pkg_filename = getattr(pkg_or_module, '__file__', None)
+ if pkg_filename is None:
+ # Namespace packages do not have __init__.py* files,
+ # and so have no __file__ attribute
+ return pkg_name
splitted = os.path.split(pkg_filename)
if splitted[-1] in init_names:
# it's a package
diff --git a/pyramid/renderers.py b/pyramid/renderers.py
index 602655be8..e90d07b38 100644
--- a/pyramid/renderers.py
+++ b/pyramid/renderers.py
@@ -1,8 +1,5 @@
import json
import os
-import re
-import pkg_resources
-import threading
from zope.interface import (
implementer,
@@ -11,18 +8,12 @@ from zope.interface import (
from zope.interface.registry import Components
from pyramid.interfaces import (
- IChameleonLookup,
- IChameleonTranslate,
IJSONAdapter,
- IRendererGlobalsFactory,
IRendererFactory,
IResponseFactory,
- ITemplateRenderer,
IRendererInfo,
)
-from pyramid.asset import asset_spec_from_abspath
-
from pyramid.compat import (
string_types,
text_type,
@@ -32,10 +23,7 @@ from pyramid.decorator import reify
from pyramid.events import BeforeRender
-from pyramid.path import (
- caller_package,
- package_path,
- )
+from pyramid.path import caller_package
from pyramid.response import Response
from pyramid.threadlocal import get_current_registry
@@ -85,7 +73,23 @@ def render(renderer_name, value, request=None, package=None):
package = caller_package()
helper = RendererHelper(name=renderer_name, package=package,
registry=registry)
- return helper.render(value, None, request=request)
+
+ saved_response = None
+ # save the current response, preventing the renderer from affecting it
+ attrs = request.__dict__ if request is not None else {}
+ if 'response' in attrs:
+ saved_response = attrs['response']
+ del attrs['response']
+
+ result = helper.render(value, None, request=request)
+
+ # restore the original response, overwriting any changes
+ if saved_response is not None:
+ attrs['response'] = saved_response
+ elif 'response' in attrs:
+ del attrs['response']
+
+ return result
def render_to_response(renderer_name, value, request=None, package=None):
""" Using the renderer ``renderer_name`` (a template
@@ -197,11 +201,17 @@ class JSON(object):
adapters with the renderer. See
:ref:`json_serializing_custom_objects` for more information.
- The default serializer uses ``json.JSONEncoder``. A different
- serializer can be specified via the ``serializer`` argument.
- Custom serializers should accept the object, a callback
- ``default``, and any extra ``kw`` keyword arguments passed during
- renderer construction.
+ .. note::
+
+ The default serializer uses ``json.JSONEncoder``. A different
+ serializer can be specified via the ``serializer`` argument. Custom
+ serializers should accept the object, a callback ``default``, and any
+ extra ``kw`` keyword arguments passed during renderer construction.
+ This feature isn't widely used but it can be used to replace the
+ stock JSON serializer with, say, simplejson. If all you want to
+ do, however, is serialize custom objects, you should use the method
+ explained in :ref:`json_serializing_custom_objects` instead
+ of replacing the serializer.
.. versionadded:: 1.4
Prior to this version, there was no public API for supplying options
@@ -360,128 +370,6 @@ class JSONP(JSON):
return body
return _render
-# utility functions, not API
-
-@implementer(IChameleonLookup)
-class ChameleonRendererLookup(object):
- spec_re = re.compile(
- r'(?P<asset>[\w_.:/-]+)'
- r'(?:\#(?P<defname>[\w_]+))?'
- r'(\.(?P<ext>.*))'
- )
-
- def __init__(self, impl, registry):
- self.impl = impl
- self.registry = registry
- self.lock = threading.Lock()
-
- def get_spec(self, name, package):
- if not package:
- # if there's no package, we can't do any conversion
- return name
-
- spec = name
- isabspath = os.path.isabs(name)
- colon_in_name = ':' in name
- isabsspec = colon_in_name and (not isabspath)
- isrelspec = (not isabsspec) and (not isabspath)
-
- # if it's already an absolute spec, we don't need to do anything,
- # but if it's a relative spec or an absolute path, we need to try
- # to convert it to an absolute spec
-
- if isrelspec:
- # convert relative asset spec to absolute asset spec
- pp = package_path(package)
- spec = os.path.join(pp, spec)
- spec = asset_spec_from_abspath(spec, package)
-
- elif isabspath:
- # convert absolute path to absolute asset spec
- spec = asset_spec_from_abspath(spec, package)
-
- return spec
-
- @property # wait until completely necessary to look up translator
- def translate(self):
- return self.registry.queryUtility(IChameleonTranslate)
-
- @property # wait until completely necessary to look up debug_templates
- def debug(self):
- settings = self.registry.settings
- if settings is None:
- return False
- return settings.get('debug_templates', False)
-
- @property # wait until completely necessary to look up reload_templates
- def auto_reload(self):
- settings = self.registry.settings
- if settings is None:
- return False
- return settings.get('reload_templates', False)
-
- def _crack_spec(self, spec):
- asset, macro, ext = self.spec_re.match(spec).group(
- 'asset', 'defname', 'ext'
- )
- return asset, macro, ext
-
- def __call__(self, info):
- spec = self.get_spec(info.name, info.package)
- registry = info.registry
-
- if os.path.isabs(spec):
- # 'spec' is an absolute filename
- if not os.path.exists(spec):
- raise ValueError('Missing template file: %s' % spec)
- renderer = registry.queryUtility(ITemplateRenderer, name=spec)
- if renderer is None:
- renderer = self.impl(spec, self, macro=None)
- # cache the template
- with self.lock:
- registry.registerUtility(renderer,
- ITemplateRenderer, name=spec)
- else:
- # spec is a package:relpath asset spec
- renderer = registry.queryUtility(ITemplateRenderer, name=spec)
- if renderer is None:
- asset, macro, ext = self._crack_spec(spec)
- spec_without_macro = '%s.%s' % (asset, ext)
- try:
- package_name, filename = spec_without_macro.split(':', 1)
- except ValueError: # pragma: no cover
- # somehow we were passed a relative pathname; this
- # should die
- package_name = caller_package(4).__name__
- filename = spec_without_macro
- abspath = pkg_resources.resource_filename(package_name,
- filename)
- if not pkg_resources.resource_exists(package_name, filename):
- raise ValueError(
- 'Missing template asset: %s (%s)' % (
- spec_without_macro, abspath)
- )
- renderer = self.impl(abspath, self, macro=macro)
- settings = info.settings
- if not settings.get('reload_assets'):
- # cache the template
- with self.lock:
- registry.registerUtility(renderer, ITemplateRenderer,
- name=spec)
-
- return renderer
-
-registry_lock = threading.Lock()
-
-def template_renderer_factory(info, impl, lock=registry_lock):
- registry = info.registry
- lookup = registry.queryUtility(IChameleonLookup, name=info.type)
- if lookup is None:
- lookup = ChameleonRendererLookup(impl, registry)
- with lock:
- registry.registerUtility(lookup, IChameleonLookup, name=info.type)
- return lookup(info)
-
@implementer(IRendererInfo)
class RendererHelper(object):
def __init__(self, name=None, package=None, registry=None):
@@ -542,13 +430,6 @@ class RendererHelper(object):
system_values = BeforeRender(system_values, value)
registry = self.registry
- globals_factory = registry.queryUtility(IRendererGlobalsFactory)
-
- if globals_factory is not None:
- renderer_globals = globals_factory(system_values)
- if renderer_globals:
- system_values.update(renderer_globals)
-
registry.notify(system_values)
result = renderer(value, system_values)
@@ -576,26 +457,6 @@ class RendererHelper(object):
else:
response.body = result
- if request is not None:
- # deprecated mechanism to set up request.response_* attrs, see
- # pyramid.request.Request
- attrs = request.__dict__
- content_type = attrs.get('_response_content_type', None)
- if content_type is not None:
- response.content_type = content_type
- headerlist = attrs.get('_response_headerlist', None)
- if headerlist is not None:
- for k, v in headerlist:
- response.headers.add(k, v)
- status = attrs.get('_response_status', None)
- if status is not None:
- response.status = status
- charset = attrs.get('_response_charset', None)
- if charset is not None:
- response.charset = charset
- cache_for = attrs.get('_response_cache_for', None)
- if cache_for is not None:
- response.cache_expires = cache_for
return response
def clone(self, name=None, package=None, registry=None):
diff --git a/pyramid/request.py b/pyramid/request.py
index 9b62bee00..2cf0613f7 100644
--- a/pyramid/request.py
+++ b/pyramid/request.py
@@ -1,7 +1,5 @@
import json
-from zope.deprecation import deprecate
-from zope.deprecation.deprecation import deprecated
from zope.interface import implementer
from zope.interface.interface import InterfaceClass
@@ -15,9 +13,6 @@ from pyramid.interfaces import (
)
from pyramid.compat import (
- iterkeys_,
- itervalues_,
- iteritems_,
text_,
bytes_,
native_,
@@ -32,167 +27,6 @@ from pyramid.util import InstancePropertyMixin
class TemplateContext(object):
pass
-class DeprecatedRequestMethodsMixin(object):
-
- # b/c dict interface for "root factory" code that expects a bare
- # environ. Explicitly omitted dict methods: clear (unnecessary),
- # copy (implemented by WebOb), fromkeys (unnecessary); deprecated
- # as of Pyramid 1.1.
-
- dictlike = ('Use of the request as a dict-like object is deprecated as '
- 'of Pyramid 1.1. Use dict-like methods of "request.environ" '
- 'instead.')
-
- @deprecate(dictlike)
- def __contains__(self, k):
- return self.environ.__contains__(k)
-
- @deprecate(dictlike)
- def __delitem__(self, k):
- return self.environ.__delitem__(k)
-
- @deprecate(dictlike)
- def __getitem__(self, k):
- return self.environ.__getitem__(k)
-
- @deprecate(dictlike)
- def __iter__(self):
- return iter(self.environ)
-
- @deprecate(dictlike)
- def __setitem__(self, k, v):
- self.environ[k] = v
-
- @deprecate(dictlike)
- def get(self, k, default=None):
- return self.environ.get(k, default)
-
- @deprecate(dictlike)
- def has_key(self, k):
- return k in self.environ
-
- @deprecate(dictlike)
- def items(self):
- return self.environ.items()
-
- @deprecate(dictlike)
- def iteritems(self):
- return iteritems_(self.environ)
-
- @deprecate(dictlike)
- def iterkeys(self):
- return iterkeys_(self.environ)
-
- @deprecate(dictlike)
- def itervalues(self):
- return itervalues_(self.environ)
-
- @deprecate(dictlike)
- def keys(self):
- return self.environ.keys()
-
- @deprecate(dictlike)
- def pop(self, k):
- return self.environ.pop(k)
-
- @deprecate(dictlike)
- def popitem(self):
- return self.environ.popitem()
-
- @deprecate(dictlike)
- def setdefault(self, v, default):
- return self.environ.setdefault(v, default)
-
- @deprecate(dictlike)
- def update(self, v, **kw):
- return self.environ.update(v, **kw)
-
- @deprecate(dictlike)
- def values(self):
- return self.environ.values()
-
- # 1.0 deprecated bw compat code for using response_* values
-
- rr_dep = ('Accessing and setting "request.response_%s" is '
- 'deprecated as of Pyramid 1.1; access or set '
- '"request.response.%s" instead.')
-
- # response_content_type
- def _response_content_type_get(self):
- return self._response_content_type
- def _response_content_type_set(self, value):
- self._response_content_type = value
- def _response_content_type_del(self):
- del self._response_content_type
- response_content_type = property(_response_content_type_get,
- _response_content_type_set,
- _response_content_type_del)
- response_content_type = deprecated(
- response_content_type,
- rr_dep % ('content_type', 'content_type'))
-
- # response_headerlist
- def _response_headerlist_get(self):
- return self._response_headerlist
- def _response_headerlist_set(self, value):
- self._response_headerlist = value
- def _response_headerlist_del(self):
- del self._response_headerlist
- response_headerlist = property(_response_headerlist_get,
- _response_headerlist_set,
- _response_headerlist_del)
-
- hl_dep = ('Accessing and setting "request.response_headerlist" is '
- 'deprecated as of Pyramid 1.1; access the headerlist via '
- '"request.response.headerlist" and extend headers via '
- '"request.response.headerlist.extend(alist)" instead of '
- '"request.response_headerlist = alist"')
-
- response_headerlist = deprecated(response_headerlist, hl_dep)
-
- # response_status
- def _response_status_get(self):
- return self._response_status
- def _response_status_set(self, value):
- self._response_status = value
- def _response_status_del(self):
- del self._response_status
- response_status = property(_response_status_get,
- _response_status_set,
- _response_status_del)
-
- response_status = deprecated(
- response_status,
- rr_dep % ('status', 'status'))
-
- # response_charset
- def _response_charset_get(self):
- return self._response_charset
- def _response_charset_set(self, value):
- self._response_charset = value
- def _response_charset_del(self):
- del self._response_charset
- response_charset = property(_response_charset_get,
- _response_charset_set,
- _response_charset_del)
- response_charset = deprecated(
- response_charset,
- rr_dep % ('charset', 'charset'))
-
- # response_cache_for
- def _response_cache_for_get(self):
- return self._response_cache_for
- def _response_cache_for_set(self, value):
- self._response_cache_for = value
- def _response_cache_for_del(self):
- del self._response_cache_for
- response_cache_for = property(_response_cache_for_get,
- _response_cache_for_set,
- _response_cache_for_del)
- response_cache_for = deprecated(
- response_cache_for,
- rr_dep % ('cache_for', 'cache_expires'))
-
class CallbackMethodsMixin(object):
response_callbacks = ()
finished_callbacks = ()
@@ -302,9 +136,8 @@ class CallbackMethodsMixin(object):
callback(self)
@implementer(IRequest)
-class Request(BaseRequest, DeprecatedRequestMethodsMixin, URLMethodsMixin,
- CallbackMethodsMixin, InstancePropertyMixin,
- LocalizerRequestMixin):
+class Request(BaseRequest, URLMethodsMixin, CallbackMethodsMixin,
+ InstancePropertyMixin, LocalizerRequestMixin):
"""
A subclass of the :term:`WebOb` Request class. An instance of
this class is created by the :term:`router` and is provided to a
diff --git a/pyramid/router.py b/pyramid/router.py
index 1a991648b..6239f3980 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -212,13 +212,13 @@ class Router(object):
- causes a :class:`~pyramid.event.ContextFound` event to be sent
when a context resource is found.
- - causes a :class:`~pyramid.event.NewResponse` event to be sent when
- the Pyramid application returns a response.
-
- Calls any :term:`response callback` functions defined within the
request's lifetime if a response is obtained from the Pyramid
application.
+ - causes a :class:`~pyramid.event.NewResponse` event to be sent if a
+ response is obtained.
+
- Calls any :term:`finished callback` functions defined within the
request's lifetime.
@@ -245,11 +245,12 @@ class Router(object):
if extensions is not None:
request._set_extensions(extensions)
response = handle_request(request)
- has_listeners and notify(NewResponse(request, response))
if request.response_callbacks:
request._process_response_callbacks(response)
+ has_listeners and notify(NewResponse(request, response))
+
return response
finally:
diff --git a/pyramid/scaffolds/alchemy/+package+/__init__.py b/pyramid/scaffolds/alchemy/+package+/__init__.py
index aac7c5e69..867049e4f 100644
--- a/pyramid/scaffolds/alchemy/+package+/__init__.py
+++ b/pyramid/scaffolds/alchemy/+package+/__init__.py
@@ -14,6 +14,7 @@ def main(global_config, **settings):
DBSession.configure(bind=engine)
Base.metadata.bind = engine
config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.scan()
diff --git a/pyramid/scaffolds/alchemy/+package+/models.py b/pyramid/scaffolds/alchemy/+package+/models.py
index db1fee832..a0d3e7b71 100644
--- a/pyramid/scaffolds/alchemy/+package+/models.py
+++ b/pyramid/scaffolds/alchemy/+package+/models.py
@@ -24,8 +24,4 @@ class MyModel(Base):
name = Column(Text)
value = Column(Integer)
- def __init__(self, name, value):
- self.name = name
- self.value = value
-
Index('my_index', MyModel.name, unique=True, mysql_length=255)
diff --git a/pyramid/scaffolds/alchemy/setup.py_tmpl b/pyramid/scaffolds/alchemy/setup.py_tmpl
index 69b5faea9..9496b9948 100644
--- a/pyramid/scaffolds/alchemy/setup.py_tmpl
+++ b/pyramid/scaffolds/alchemy/setup.py_tmpl
@@ -10,10 +10,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
'SQLAlchemy',
'transaction',
- 'pyramid_tm',
- 'pyramid_debugtoolbar',
'zope.sqlalchemy',
'waitress',
]
diff --git a/pyramid/scaffolds/starter/+package+/__init__.py b/pyramid/scaffolds/starter/+package+/__init__.py
index 6c512f52f..ad5ecbc6f 100644
--- a/pyramid/scaffolds/starter/+package+/__init__.py
+++ b/pyramid/scaffolds/starter/+package+/__init__.py
@@ -5,6 +5,7 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(settings=settings)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.scan()
diff --git a/pyramid/scaffolds/starter/+package+/static/pyramid-small.png b/pyramid/scaffolds/starter/+package+/static/pyramid-small.png
deleted file mode 100644
index a5bc0ade7..000000000
--- a/pyramid/scaffolds/starter/+package+/static/pyramid-small.png
+++ /dev/null
Binary files differ
diff --git a/pyramid/scaffolds/starter/setup.py_tmpl b/pyramid/scaffolds/starter/setup.py_tmpl
index c0908d96f..3802c3e23 100644
--- a/pyramid/scaffolds/starter/setup.py_tmpl
+++ b/pyramid/scaffolds/starter/setup.py_tmpl
@@ -10,6 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
'pyramid_debugtoolbar',
'waitress',
]
diff --git a/pyramid/scaffolds/tests.py b/pyramid/scaffolds/tests.py
index b90e494e9..d913d022a 100644
--- a/pyramid/scaffolds/tests.py
+++ b/pyramid/scaffolds/tests.py
@@ -9,22 +9,18 @@ import time
try:
import httplib
except ImportError: # pragma: no cover
- import http.client as httplib
-
-from pyramid.compat import PY3
+ import http.client as httplib #py3
class TemplateTest(object):
def make_venv(self, directory): # pragma: no cover
import virtualenv
- import sys
from virtualenv import Logger
logger = Logger([(Logger.level_for_integer(2), sys.stdout)])
virtualenv.logger = logger
virtualenv.create_environment(directory,
site_packages=False,
clear=False,
- unzip_setuptools=True,
- use_distribute=PY3)
+ unzip_setuptools=True)
def install(self, tmpl_name): # pragma: no cover
try:
self.old_cwd = os.getcwd()
@@ -70,10 +66,7 @@ class TemplateTest(object):
os.chdir(self.old_cwd)
if __name__ == '__main__': # pragma: no cover
- templates = ['starter', 'alchemy',]
-
- if sys.version_info >= (2, 6) and sys.version_info < (3, 0):
- templates.append('zodb')
+ templates = ['starter', 'alchemy', 'zodb']
for name in templates:
test = TemplateTest()
diff --git a/pyramid/scaffolds/zodb/+package+/__init__.py b/pyramid/scaffolds/zodb/+package+/__init__.py
index c3bb87a62..f2a86df47 100644
--- a/pyramid/scaffolds/zodb/+package+/__init__.py
+++ b/pyramid/scaffolds/zodb/+package+/__init__.py
@@ -12,6 +12,7 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(root_factory=root_factory, settings=settings)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.scan()
return config.make_wsgi_app()
diff --git a/pyramid/scaffolds/zodb/+package+/static/pyramid-small.png b/pyramid/scaffolds/zodb/+package+/static/pyramid-small.png
deleted file mode 100644
index a5bc0ade7..000000000
--- a/pyramid/scaffolds/zodb/+package+/static/pyramid-small.png
+++ /dev/null
Binary files differ
diff --git a/pyramid/scaffolds/zodb/setup.py_tmpl b/pyramid/scaffolds/zodb/setup.py_tmpl
index 02789657d..3a6032429 100644
--- a/pyramid/scaffolds/zodb/setup.py_tmpl
+++ b/pyramid/scaffolds/zodb/setup.py_tmpl
@@ -10,10 +10,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
'pyramid_zodbconn',
'transaction',
- 'pyramid_tm',
- 'pyramid_debugtoolbar',
'ZODB3',
'waitress',
]
diff --git a/pyramid/scripts/pcreate.py b/pyramid/scripts/pcreate.py
index ba4eb0856..5e2240856 100644
--- a/pyramid/scripts/pcreate.py
+++ b/pyramid/scripts/pcreate.py
@@ -77,8 +77,8 @@ class PCreateCommand(object):
def render_scaffolds(self):
options = self.options
args = self.args
- project_name = os.path.basename(args[0])
output_dir = os.path.abspath(os.path.normpath(args[0]))
+ project_name = os.path.basename(os.path.split(output_dir)[1])
pkg_name = _bad_chars_re.sub('', project_name.lower())
safe_name = pkg_resources.safe_name(project_name)
egg_name = pkg_resources.to_filename(safe_name)
diff --git a/pyramid/scripts/pviews.py b/pyramid/scripts/pviews.py
index 081c13e9d..504f583b7 100644
--- a/pyramid/scripts/pviews.py
+++ b/pyramid/scripts/pviews.py
@@ -4,6 +4,7 @@ import textwrap
from pyramid.interfaces import IMultiView
from pyramid.paster import bootstrap
+from pyramid.request import Request
from pyramid.scripts.common import parse_vars
def main(argv=sys.argv, quiet=False):
@@ -52,7 +53,7 @@ class PViewsCommand(object):
infos.append(info)
return infos
- def _find_view(self, url, registry):
+ def _find_view(self, request):
"""
Accept ``url`` and ``registry``; create a :term:`request` and
find a :app:`Pyramid` view based on introspection of :term:`view
@@ -63,22 +64,19 @@ class PViewsCommand(object):
from pyramid.interfaces import IRequest
from pyramid.interfaces import IRootFactory
from pyramid.interfaces import IRouteRequest
- from pyramid.interfaces import IRequestFactory
from pyramid.interfaces import IRoutesMapper
from pyramid.interfaces import IView
from pyramid.interfaces import IViewClassifier
from pyramid.interfaces import ITraverser
- from pyramid.request import Request
from pyramid.traversal import DefaultRootFactory
from pyramid.traversal import ResourceTreeTraverser
+ registry = request.registry
q = registry.queryUtility
root_factory = q(IRootFactory, default=DefaultRootFactory)
routes_mapper = q(IRoutesMapper)
- request_factory = q(IRequestFactory, default=Request)
adapters = registry.adapters
- request = None
@implementer(IMultiView)
class RoutesMultiView(object):
@@ -111,20 +109,9 @@ class PViewsCommand(object):
view.__view_attr__ = ''
self.views.append((None, view, None))
-
- # create the request
- environ = {
- 'wsgi.url_scheme':'http',
- 'SERVER_NAME':'localhost',
- 'SERVER_PORT':'8080',
- 'REQUEST_METHOD':'GET',
- 'PATH_INFO':url,
- }
- request = request_factory(environ)
context = None
routes_multiview = None
attrs = request.__dict__
- attrs['registry'] = registry
request_iface = IRequest
# find the root object
@@ -236,9 +223,10 @@ class PViewsCommand(object):
if not url.startswith('/'):
url = '/%s' % url
- env = self.bootstrap[0](config_uri, options=parse_vars(self.args[2:]))
- registry = env['registry']
- view = self._find_view(url, registry)
+ request = Request.blank(url)
+ env = self.bootstrap[0](config_uri, options=parse_vars(self.args[2:]),
+ request=request)
+ view = self._find_view(request)
self.out('')
self.out("URL = %s" % url)
self.out('')
@@ -257,5 +245,6 @@ class PViewsCommand(object):
else:
self.out(" Not found.")
self.out('')
+ env['closer']()
return 0
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 36c690117..4590c55f8 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -34,12 +34,8 @@ from pyramid.threadlocal import (
manager,
)
-from pyramid.request import (
- DeprecatedRequestMethodsMixin,
- CallbackMethodsMixin,
- )
-
from pyramid.i18n import LocalizerRequestMixin
+from pyramid.request import CallbackMethodsMixin
from pyramid.url import URLMethodsMixin
from pyramid.util import InstancePropertyMixin
@@ -286,8 +282,7 @@ class DummySession(dict):
@implementer(IRequest)
-class DummyRequest(DeprecatedRequestMethodsMixin, URLMethodsMixin,
- CallbackMethodsMixin, InstancePropertyMixin,
+class DummyRequest(URLMethodsMixin, CallbackMethodsMixin, InstancePropertyMixin,
LocalizerRequestMixin):
""" A DummyRequest object (incompletely) imitates a :term:`request` object.
@@ -455,18 +450,7 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True,
# someone may be passing us an esoteric "dummy" registry, and
# the below won't succeed if it doesn't have a registerUtility
# method.
- from pyramid.config import DEFAULT_RENDERERS
- for name, renderer in DEFAULT_RENDERERS:
- # Cause the default renderers to be registered because
- # in-the-wild test code relies on being able to call
- # e.g. ``pyramid.chameleon_zpt.render_template``
- # without registering a .pt renderer, expecting the "real"
- # template to be rendered. This is a holdover from when
- # individual template system renderers weren't indirected
- # by the ``pyramid.renderers`` machinery, and
- # ``render_template`` and friends went behind the back of
- # any existing renderer factory lookup system.
- config.add_renderer(name, renderer)
+ config.add_default_renderers()
config.add_default_view_predicates()
config.add_default_route_predicates()
config.commit()
diff --git a/pyramid/tests/fixtures/components.mak b/pyramid/tests/fixtures/components.mak
deleted file mode 100644
index cc886805c..000000000
--- a/pyramid/tests/fixtures/components.mak
+++ /dev/null
@@ -1,3 +0,0 @@
-<%def name="comp()">
-World!
-</%def> \ No newline at end of file
diff --git a/pyramid/tests/fixtures/hello .world.mako b/pyramid/tests/fixtures/hello .world.mako
deleted file mode 100644
index 7a06eed97..000000000
--- a/pyramid/tests/fixtures/hello .world.mako
+++ /dev/null
@@ -1,3 +0,0 @@
-## -*- coding: utf-8 -*-
-<%!from pyramid.compat import text_%><% a, b = 'foo', text_('föö', 'utf-8') %>
-Hello ${text_('föö', 'utf-8')} \ No newline at end of file
diff --git a/pyramid/tests/fixtures/hello_inherit_pkg.mak b/pyramid/tests/fixtures/hello_inherit_pkg.mak
deleted file mode 100644
index 87d18d0f7..000000000
--- a/pyramid/tests/fixtures/hello_inherit_pkg.mak
+++ /dev/null
@@ -1,2 +0,0 @@
-Hello World!
-<%inherit file="pyramid.tests:fixtures/layout.mak"/> \ No newline at end of file
diff --git a/pyramid/tests/fixtures/hellocompo.mak b/pyramid/tests/fixtures/hellocompo.mak
deleted file mode 100644
index 142676a11..000000000
--- a/pyramid/tests/fixtures/hellocompo.mak
+++ /dev/null
@@ -1,3 +0,0 @@
-<%namespace name="comp" file="pyramid.tests:fixtures/components.mak"/>
-Namespace
-Hello ${comp.comp()} \ No newline at end of file
diff --git a/pyramid/tests/fixtures/helloinherit.mak b/pyramid/tests/fixtures/helloinherit.mak
deleted file mode 100644
index 53edd71ed..000000000
--- a/pyramid/tests/fixtures/helloinherit.mak
+++ /dev/null
@@ -1,2 +0,0 @@
-Hello World!
-<%inherit file="layout.mak"/> \ No newline at end of file
diff --git a/pyramid/tests/fixtures/helloworld.mak b/pyramid/tests/fixtures/helloworld.mak
deleted file mode 100644
index 25283a50d..000000000
--- a/pyramid/tests/fixtures/helloworld.mak
+++ /dev/null
@@ -1,3 +0,0 @@
-## -*- coding: utf-8 -*-
-<%!from pyramid.compat import text_%><% a, b = 'foo', text_('föö', 'utf-8') %>
-Hello ${text_('föö', 'utf-8')}
diff --git a/pyramid/tests/fixtures/helloworld.mako b/pyramid/tests/fixtures/helloworld.mako
deleted file mode 100644
index 25283a50d..000000000
--- a/pyramid/tests/fixtures/helloworld.mako
+++ /dev/null
@@ -1,3 +0,0 @@
-## -*- coding: utf-8 -*-
-<%!from pyramid.compat import text_%><% a, b = 'foo', text_('föö', 'utf-8') %>
-Hello ${text_('föö', 'utf-8')}
diff --git a/pyramid/tests/fixtures/layout.mak b/pyramid/tests/fixtures/layout.mak
deleted file mode 100644
index 3bef88bf8..000000000
--- a/pyramid/tests/fixtures/layout.mak
+++ /dev/null
@@ -1,2 +0,0 @@
-Layout
-${next.body()} \ No newline at end of file
diff --git a/pyramid/tests/fixtures/minimal.pt b/pyramid/tests/fixtures/minimal.pt
deleted file mode 100644
index 693d155ef..000000000
--- a/pyramid/tests/fixtures/minimal.pt
+++ /dev/null
@@ -1,3 +0,0 @@
-<div xmlns="http://www.w3.org/1999/xhtml"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
-</div>
diff --git a/pyramid/tests/fixtures/nonminimal.mak b/pyramid/tests/fixtures/nonminimal.mak
deleted file mode 100644
index 9de95ec92..000000000
--- a/pyramid/tests/fixtures/nonminimal.mak
+++ /dev/null
@@ -1 +0,0 @@
-Hello, ${name}!
diff --git a/pyramid/tests/fixtures/pp.pt b/pyramid/tests/fixtures/pp.pt
deleted file mode 100644
index 9df7d22da..000000000
--- a/pyramid/tests/fixtures/pp.pt
+++ /dev/null
@@ -1,3 +0,0 @@
-<p xmlns="http://www.w3.org/1999/xhtml"
- xmlns:tal="http://xml.zope.org/namespaces/tal"
- tal:content="wrapped">WRAPPED</p>
diff --git a/pyramid/tests/fixtures/withmacro.pt b/pyramid/tests/fixtures/withmacro.pt
deleted file mode 100644
index 6fa654645..000000000
--- a/pyramid/tests/fixtures/withmacro.pt
+++ /dev/null
@@ -1,7 +0,0 @@
-<html>
-Outside macro
-<metal:m define-macro="foo">
- Hello!
-</metal:m>
-</html>
-
diff --git a/pyramid/tests/pkgs/fixtureapp/subpackage/templates/bar.pt b/pyramid/tests/pkgs/fixtureapp/subpackage/templates/bar.pt
deleted file mode 100644
index 90531a4b3..000000000
--- a/pyramid/tests/pkgs/fixtureapp/subpackage/templates/bar.pt
+++ /dev/null
@@ -1,2 +0,0 @@
-<html>
-</html>
diff --git a/pyramid/tests/pkgs/fixtureapp/templates/fixture.pt b/pyramid/tests/pkgs/fixtureapp/templates/fixture.pt
deleted file mode 100644
index 06dd4e2b1..000000000
--- a/pyramid/tests/pkgs/fixtureapp/templates/fixture.pt
+++ /dev/null
@@ -1,6 +0,0 @@
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head></head>
-<body>
-</body>
-</html>
diff --git a/pyramid/tests/pkgs/rendererscanapp/__init__.py b/pyramid/tests/pkgs/rendererscanapp/__init__.py
index 1baec0940..f3276a063 100644
--- a/pyramid/tests/pkgs/rendererscanapp/__init__.py
+++ b/pyramid/tests/pkgs/rendererscanapp/__init__.py
@@ -1,6 +1,6 @@
from pyramid.view import view_config
-@view_config(name='one', renderer='one.pt')
+@view_config(name='one', renderer='json')
def one(request):
return {'name':'One!'}
diff --git a/pyramid/tests/pkgs/rendererscanapp/one.pt b/pyramid/tests/pkgs/rendererscanapp/one.pt
deleted file mode 100644
index 42114d94f..000000000
--- a/pyramid/tests/pkgs/rendererscanapp/one.pt
+++ /dev/null
@@ -1,4 +0,0 @@
-<div xmlns="http://www.w3.org/1999/xhtml"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
- ${name}
-</div>
diff --git a/pyramid/tests/pkgs/rendererscanapp/two/__init__.py b/pyramid/tests/pkgs/rendererscanapp/two/__init__.py
index be0077fcb..6f575dd83 100644
--- a/pyramid/tests/pkgs/rendererscanapp/two/__init__.py
+++ b/pyramid/tests/pkgs/rendererscanapp/two/__init__.py
@@ -1,6 +1,6 @@
from pyramid.view import view_config
-@view_config(name='two', renderer='two.pt')
+@view_config(name='two', renderer='json')
def two(request):
return {'nameagain':'Two!'}
diff --git a/pyramid/tests/pkgs/rendererscanapp/two/two.pt b/pyramid/tests/pkgs/rendererscanapp/two/two.pt
deleted file mode 100644
index 7eff97c22..000000000
--- a/pyramid/tests/pkgs/rendererscanapp/two/two.pt
+++ /dev/null
@@ -1,4 +0,0 @@
-<div xmlns="http://www.w3.org/1999/xhtml"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
- ${nameagain}
-</div>
diff --git a/pyramid/tests/pkgs/viewdecoratorapp/views/templates/foo.pt b/pyramid/tests/pkgs/viewdecoratorapp/views/templates/foo.pt
deleted file mode 100644
index 6a2f701b6..000000000
--- a/pyramid/tests/pkgs/viewdecoratorapp/views/templates/foo.pt
+++ /dev/null
@@ -1,3 +0,0 @@
-<html>
-${result}
-</html>
diff --git a/pyramid/tests/pkgs/viewdecoratorapp/views/views.py b/pyramid/tests/pkgs/viewdecoratorapp/views/views.py
index 2b7d7e928..18ec78847 100644
--- a/pyramid/tests/pkgs/viewdecoratorapp/views/views.py
+++ b/pyramid/tests/pkgs/viewdecoratorapp/views/views.py
@@ -1,11 +1,11 @@
from pyramid.view import view_config
-@view_config(renderer='templates/foo.pt', name='first')
+@view_config(renderer='json', name='first')
def first(request):
return {'result':'OK1'}
@view_config(
- renderer='pyramid.tests.pkgs.viewdecoratorapp.views:templates/foo.pt',
+ renderer='json',
name='second')
def second(request):
return {'result':'OK2'}
diff --git a/pyramid/tests/test_authorization.py b/pyramid/tests/test_authorization.py
index 60b1b0c8d..05cd3b4f8 100644
--- a/pyramid/tests/test_authorization.py
+++ b/pyramid/tests/test_authorization.py
@@ -146,6 +146,19 @@ class TestACLAuthorizationPolicy(unittest.TestCase):
policy.principals_allowed_by_permission(context, 'read'))
self.assertEqual(result, ['chrism'])
+ def test_principals_allowed_by_permission_callable_acl(self):
+ from pyramid.security import Allow
+ from pyramid.security import DENY_ALL
+ context = DummyContext()
+ acl = lambda: [ (Allow, 'chrism', ('read', 'write')),
+ DENY_ALL,
+ (Allow, 'other', 'read') ]
+ context.__acl__ = acl
+ policy = self._makeOne()
+ result = sorted(
+ policy.principals_allowed_by_permission(context, 'read'))
+ self.assertEqual(result, ['chrism'])
+
def test_principals_allowed_by_permission_string_permission(self):
from pyramid.security import Allow
context = DummyContext()
diff --git a/pyramid/tests/test_chameleon_text.py b/pyramid/tests/test_chameleon_text.py
deleted file mode 100644
index d9f20f241..000000000
--- a/pyramid/tests/test_chameleon_text.py
+++ /dev/null
@@ -1,145 +0,0 @@
-import sys
-import unittest
-
-from pyramid.compat import binary_type
-from pyramid import testing
-
-class Base(object):
- def setUp(self):
- self.config = testing.setUp()
-
- def tearDown(self):
- testing.tearDown()
-
- def _getTemplatePath(self, name):
- import os
- here = os.path.abspath(os.path.dirname(__file__))
- return os.path.join(here, 'fixtures', name)
-
-class Test_renderer_factory(Base, unittest.TestCase):
- def _callFUT(self, info):
- from pyramid.chameleon_text import renderer_factory
- return renderer_factory(info)
-
- def test_it(self):
- # this test is way too functional
- from pyramid.chameleon_text import TextTemplateRenderer
- info = DummyInfo()
- result = self._callFUT(info)
- self.assertEqual(result.__class__, TextTemplateRenderer)
-
-class TextTemplateRendererTests(Base, unittest.TestCase):
- def _getTargetClass(self):
- from pyramid.chameleon_text import TextTemplateRenderer
- return TextTemplateRenderer
-
- def _makeOne(self, *arg, **kw):
- klass = self._getTargetClass()
- return klass(*arg, **kw)
-
- def test_instance_implements_ITemplate(self):
- from zope.interface.verify import verifyObject
- from pyramid.interfaces import ITemplateRenderer
- path = self._getTemplatePath('minimal.txt')
- lookup = DummyLookup()
- verifyObject(ITemplateRenderer, self._makeOne(path, lookup))
-
- def test_class_implements_ITemplate(self):
- from zope.interface.verify import verifyClass
- from pyramid.interfaces import ITemplateRenderer
- verifyClass(ITemplateRenderer, self._getTargetClass())
-
- def test_template_reified(self):
- minimal = self._getTemplatePath('minimal.txt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup)
- self.assertFalse('template' in instance.__dict__)
- template = instance.template
- self.assertEqual(template, instance.__dict__['template'])
-
- def test_template_with_ichameleon_translate(self):
- minimal = self._getTemplatePath('minimal.txt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup)
- self.assertFalse('template' in instance.__dict__)
- template = instance.template
- self.assertEqual(template.translate, lookup.translate)
-
- def test_template_with_debug_templates(self):
- minimal = self._getTemplatePath('minimal.txt')
- lookup = DummyLookup()
- lookup.debug = True
- instance = self._makeOne(minimal, lookup)
- self.assertFalse('template' in instance.__dict__)
- template = instance.template
- self.assertEqual(template.debug, True)
-
- def test_template_with_reload_templates(self):
- minimal = self._getTemplatePath('minimal.txt')
- lookup = DummyLookup()
- lookup.auto_reload = True
- instance = self._makeOne(minimal, lookup)
- self.assertFalse('template' in instance.__dict__)
- template = instance.template
- self.assertEqual(template.auto_reload, True)
-
- def test_template_without_reload_templates(self):
- minimal = self._getTemplatePath('minimal.txt')
- lookup = DummyLookup()
- lookup.auto_reload = False
- instance = self._makeOne(minimal, lookup)
- self.assertFalse('template' in instance.__dict__)
- template = instance.template
- self.assertEqual(template.auto_reload, False)
-
- def test_call(self):
- minimal = self._getTemplatePath('minimal.txt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup)
- result = instance({}, {})
- self.assertTrue(isinstance(result, binary_type))
- self.assertEqual(result, b'Hello.\n')
-
- def test_call_with_nondict_value(self):
- minimal = self._getTemplatePath('minimal.txt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup)
- self.assertRaises(ValueError, instance, None, {})
-
- def test_call_nonminimal(self):
- nonminimal = self._getTemplatePath('nonminimal.txt')
- lookup = DummyLookup()
- instance = self._makeOne(nonminimal, lookup)
- result = instance({'name':'Chris'}, {})
- self.assertTrue(isinstance(result, binary_type))
- self.assertEqual(result, b'Hello, Chris!\n')
-
- def test_implementation(self):
- minimal = self._getTemplatePath('minimal.txt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup)
- result = instance.implementation()()
- self.assertTrue(isinstance(result, binary_type))
- self.assertEqual(result, b'Hello.\n')
-
-class DummyLookup(object):
- auto_reload=True
- debug = True
- def translate(self, msg): pass
-
-class DummyRegistry(object):
- def queryUtility(self, iface, name):
- self.queried = iface, name
- return None
-
- def registerUtility(self, impl, iface, name):
- self.registered = impl, iface, name
-
-class DummyInfo(object):
- def __init__(self):
- self.registry = DummyRegistry()
- self.type = '.pt'
- self.name = 'fixtures/minimal.pt'
- self.package = sys.modules[__name__]
- self.settings = {}
-
diff --git a/pyramid/tests/test_chameleon_zpt.py b/pyramid/tests/test_chameleon_zpt.py
deleted file mode 100644
index d7ca94298..000000000
--- a/pyramid/tests/test_chameleon_zpt.py
+++ /dev/null
@@ -1,175 +0,0 @@
-import sys
-import unittest
-
-from pyramid import testing
-from pyramid.compat import text_type
-
-class Base(object):
- def setUp(self):
- self.config = testing.setUp()
-
- def tearDown(self):
- testing.tearDown()
-
- def _getTemplatePath(self, name):
- import os
- here = os.path.abspath(os.path.dirname(__file__))
- return os.path.join(here, 'fixtures', name)
-
-class Test_renderer_factory(Base, unittest.TestCase):
- def _callFUT(self, info):
- from pyramid.chameleon_zpt import renderer_factory
- return renderer_factory(info)
-
- def test_it(self):
- # this test is way too functional
- from pyramid.chameleon_zpt import ZPTTemplateRenderer
- info = DummyInfo()
- result = self._callFUT(info)
- self.assertEqual(result.__class__, ZPTTemplateRenderer)
-
-class ZPTTemplateRendererTests(Base, unittest.TestCase):
- def _getTargetClass(self):
- from pyramid.chameleon_zpt import ZPTTemplateRenderer
- return ZPTTemplateRenderer
-
- def _makeOne(self, *arg, **kw):
- klass = self._getTargetClass()
- return klass(*arg, **kw)
-
- def test_instance_implements_ITemplate(self):
- from zope.interface.verify import verifyObject
- from pyramid.interfaces import ITemplateRenderer
- path = self._getTemplatePath('minimal.pt')
- lookup = DummyLookup()
- verifyObject(ITemplateRenderer, self._makeOne(path, lookup))
-
- def test_class_implements_ITemplate(self):
- from zope.interface.verify import verifyClass
- from pyramid.interfaces import ITemplateRenderer
- verifyClass(ITemplateRenderer, self._getTargetClass())
-
- def test_call(self):
- minimal = self._getTemplatePath('minimal.pt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup)
- result = instance({}, {})
- self.assertTrue(isinstance(result, text_type))
- self.assertEqual(result.rstrip('\n'),
- '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>')
-
- def test_template_reified(self):
- minimal = self._getTemplatePath('minimal.pt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup)
- self.assertFalse('template' in instance.__dict__)
- template = instance.template
- self.assertEqual(template, instance.__dict__['template'])
-
- def test_template_with_ichameleon_translate(self):
- minimal = self._getTemplatePath('minimal.pt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup)
- self.assertFalse('template' in instance.__dict__)
- template = instance.template
- self.assertEqual(template.translate, lookup.translate)
-
- def test_template_with_debug_templates(self):
- minimal = self._getTemplatePath('minimal.pt')
- lookup = DummyLookup()
- lookup.debug = True
- instance = self._makeOne(minimal, lookup)
- self.assertFalse('template' in instance.__dict__)
- template = instance.template
- self.assertEqual(template.debug, True)
-
- def test_template_without_debug_templates(self):
- minimal = self._getTemplatePath('minimal.pt')
- lookup = DummyLookup()
- lookup.debug = False
- instance = self._makeOne(minimal, lookup)
- self.assertFalse('template' in instance.__dict__)
- template = instance.template
- self.assertEqual(template.debug, False)
-
- def test_template_with_reload_templates(self):
- minimal = self._getTemplatePath('minimal.pt')
- lookup = DummyLookup()
- lookup.auto_reload = True
- instance = self._makeOne(minimal, lookup)
- self.assertFalse('template' in instance.__dict__)
- template = instance.template
- self.assertEqual(template.auto_reload, True)
-
- def test_template_without_reload_templates(self):
- minimal = self._getTemplatePath('minimal.pt')
- lookup = DummyLookup()
- lookup.auto_reload = False
- instance = self._makeOne(minimal, lookup)
- self.assertFalse('template' in instance.__dict__)
- template = instance.template
- self.assertEqual(template.auto_reload, False)
-
- def test_call_with_nondict_value(self):
- minimal = self._getTemplatePath('minimal.pt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup)
- self.assertRaises(ValueError, instance, None, {})
-
- def test_implementation(self):
- minimal = self._getTemplatePath('minimal.pt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup)
- result = instance.implementation()()
- self.assertTrue(isinstance(result, text_type))
- self.assertEqual(result.rstrip('\n'),
- '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>')
-
- def test_macro_supplied(self):
- minimal = self._getTemplatePath('withmacro.pt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup, macro='foo')
- result = instance.implementation()()
- self.assertEqual(result, '\n Hello!\n')
-
- def test_macro_notsupplied(self):
- minimal = self._getTemplatePath('withmacro.pt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup)
- result = instance.implementation()()
- self.assertEqual(result,
- '<html>\nOutside macro\n\n Hello!\n\n</html>\n\n')
-
- def test_macro_template_reload(self):
- minimal = self._getTemplatePath('withmacro.pt')
- lookup = DummyLookup()
- instance = self._makeOne(minimal, lookup, macro='foo')
- result = instance.implementation()()
- self.assertEqual(result, '\n Hello!\n')
- instance.template.cook(
- '<html>\nOutside macro\n\n Hello!\n\n</html>\n\n'
- )
- result = instance.implementation()()
- self.assertEqual(result, '\n Hello!\n')
-
-class DummyLookup(object):
- auto_reload=True
- debug = True
- def translate(self, msg): pass
-
-class DummyRegistry(object):
- def queryUtility(self, iface, name):
- self.queried = iface, name
- return None
-
- def registerUtility(self, impl, iface, name):
- self.registered = impl, iface, name
-
-class DummyInfo(object):
- def __init__(self):
- self.registry = DummyRegistry()
- self.type = '.pt'
- self.name = 'fixtures/minimal.pt'
- self.package = sys.modules[__name__]
- self.settings = {}
-
diff --git a/pyramid/tests/test_config/files/minimal.pt b/pyramid/tests/test_config/files/minimal.pt
deleted file mode 100644
index 693d155ef..000000000
--- a/pyramid/tests/test_config/files/minimal.pt
+++ /dev/null
@@ -1,3 +0,0 @@
-<div xmlns="http://www.w3.org/1999/xhtml"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
-</div>
diff --git a/pyramid/tests/test_config/files/minimal.txt b/pyramid/tests/test_config/files/minimal.txt
new file mode 100644
index 000000000..19fe66dfa
--- /dev/null
+++ b/pyramid/tests/test_config/files/minimal.txt
@@ -0,0 +1 @@
+<div clas="header"></div>
diff --git a/pyramid/tests/test_config/pkgs/asset/subpackage/templates/bar.pt b/pyramid/tests/test_config/pkgs/asset/subpackage/templates/bar.pt
deleted file mode 100644
index 90531a4b3..000000000
--- a/pyramid/tests/test_config/pkgs/asset/subpackage/templates/bar.pt
+++ /dev/null
@@ -1,2 +0,0 @@
-<html>
-</html>
diff --git a/pyramid/tests/test_config/pkgs/asset/templates/fixture.pt b/pyramid/tests/test_config/pkgs/asset/templates/fixture.pt
deleted file mode 100644
index 06dd4e2b1..000000000
--- a/pyramid/tests/test_config/pkgs/asset/templates/fixture.pt
+++ /dev/null
@@ -1,6 +0,0 @@
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head></head>
-<body>
-</body>
-</html>
diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py
index e89fc077e..6e679397f 100644
--- a/pyramid/tests/test_config/test_factories.py
+++ b/pyramid/tests/test_config/test_factories.py
@@ -67,51 +67,6 @@ class TestFactoriesMixin(unittest.TestCase):
self.assertEqual(config.registry.getUtility(ISessionFactory),
dummyfactory)
- def test_set_request_property_with_callable(self):
- from pyramid.interfaces import IRequestExtensions
- config = self._makeOne(autocommit=True)
- callable = lambda x: None
- config.set_request_property(callable, name='foo')
- exts = config.registry.getUtility(IRequestExtensions)
- self.assertTrue('foo' in exts.descriptors)
-
- def test_set_request_property_with_unnamed_callable(self):
- from pyramid.interfaces import IRequestExtensions
- config = self._makeOne(autocommit=True)
- def foo(self): pass
- config.set_request_property(foo, reify=True)
- exts = config.registry.getUtility(IRequestExtensions)
- self.assertTrue('foo' in exts.descriptors)
-
- def test_set_request_property_with_property(self):
- from pyramid.interfaces import IRequestExtensions
- config = self._makeOne(autocommit=True)
- callable = property(lambda x: None)
- config.set_request_property(callable, name='foo')
- exts = config.registry.getUtility(IRequestExtensions)
- self.assertTrue('foo' in exts.descriptors)
-
- def test_set_multiple_request_properties(self):
- from pyramid.interfaces import IRequestExtensions
- config = self._makeOne()
- def foo(self): pass
- bar = property(lambda x: None)
- config.set_request_property(foo, reify=True)
- config.set_request_property(bar, name='bar')
- config.commit()
- exts = config.registry.getUtility(IRequestExtensions)
- self.assertTrue('foo' in exts.descriptors)
- self.assertTrue('bar' in exts.descriptors)
-
- def test_set_multiple_request_properties_conflict(self):
- from pyramid.exceptions import ConfigurationConflictError
- config = self._makeOne()
- def foo(self): pass
- bar = property(lambda x: None)
- config.set_request_property(foo, name='bar', reify=True)
- config.set_request_property(bar, name='bar')
- self.assertRaises(ConfigurationConflictError, config.commit)
-
def test_add_request_method_with_callable(self):
from pyramid.interfaces import IRequestExtensions
config = self._makeOne(autocommit=True)
@@ -157,3 +112,63 @@ class TestFactoriesMixin(unittest.TestCase):
self.assertRaises(AttributeError, config.add_request_method)
+class TestDeprecatedFactoriesMixinMethods(unittest.TestCase):
+ def setUp(self):
+ from zope.deprecation import __show__
+ __show__.off()
+
+ def tearDown(self):
+ from zope.deprecation import __show__
+ __show__.on()
+
+ def _makeOne(self, *arg, **kw):
+ from pyramid.config import Configurator
+ config = Configurator(*arg, **kw)
+ return config
+
+ def test_set_request_property_with_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ callable = lambda x: None
+ config.set_request_property(callable, name='foo')
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
+
+ def test_set_request_property_with_unnamed_callable(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ def foo(self): pass
+ config.set_request_property(foo, reify=True)
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
+
+ def test_set_request_property_with_property(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne(autocommit=True)
+ callable = property(lambda x: None)
+ config.set_request_property(callable, name='foo')
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
+
+ def test_set_multiple_request_properties(self):
+ from pyramid.interfaces import IRequestExtensions
+ config = self._makeOne()
+ def foo(self): pass
+ bar = property(lambda x: None)
+ config.set_request_property(foo, reify=True)
+ config.set_request_property(bar, name='bar')
+ config.commit()
+ exts = config.registry.getUtility(IRequestExtensions)
+ self.assertTrue('foo' in exts.descriptors)
+ self.assertTrue('bar' in exts.descriptors)
+
+ def test_set_multiple_request_properties_conflict(self):
+ from pyramid.exceptions import ConfigurationConflictError
+ config = self._makeOne()
+ def foo(self): pass
+ bar = property(lambda x: None)
+ config.set_request_property(foo, name='bar', reify=True)
+ 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 fdee0416f..71c68af8a 100644
--- a/pyramid/tests/test_config/test_i18n.py
+++ b/pyramid/tests/test_config/test_i18n.py
@@ -42,13 +42,10 @@ class TestI18NConfiguratorMixin(unittest.TestCase):
def test_add_translation_dirs_no_specs(self):
from pyramid.interfaces import ITranslationDirectories
- from pyramid.interfaces import IChameleonTranslate
config = self._makeOne()
config.add_translation_dirs()
self.assertEqual(config.registry.queryUtility(ITranslationDirectories),
None)
- self.assertEqual(config.registry.queryUtility(IChameleonTranslate),
- None)
def test_add_translation_dirs_asset_spec(self):
from pyramid.interfaces import ITranslationDirectories
@@ -83,21 +80,6 @@ class TestI18NConfiguratorMixin(unittest.TestCase):
self.assertEqual(config.registry.getUtility(ITranslationDirectories),
[locale3, locale, locale2])
- def test_add_translation_dirs_registers_chameleon_translate(self):
- from pyramid.interfaces import IChameleonTranslate
- from pyramid.threadlocal import manager
- from pyramid.request import Request
- config = self._makeOne(autocommit=True)
- request = Request.blank('/')
- request.registry = config.registry
- manager.push({'request':request, 'registry':config.registry})
- try:
- config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale')
- translate = config.registry.getUtility(IChameleonTranslate)
- self.assertEqual(translate('Approve'), 'Approve')
- finally:
- manager.pop()
-
def test_add_translation_dirs_abspath(self):
from pyramid.interfaces import ITranslationDirectories
config = self._makeOne(autocommit=True)
diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py
index b8cbbd676..d6dba17f6 100644
--- a/pyramid/tests/test_config/test_init.py
+++ b/pyramid/tests/test_config/test_init.py
@@ -3,7 +3,6 @@ import warnings
import os
-from pyramid.compat import PYPY
from pyramid.compat import im_func
from pyramid.testing import skip_on
@@ -69,11 +68,6 @@ class ConfiguratorTests(unittest.TestCase):
config.commit()
self.assertTrue(config.registry.getUtility(IRendererFactory, 'json'))
self.assertTrue(config.registry.getUtility(IRendererFactory, 'string'))
- if not PYPY:
- self.assertTrue(config.registry.getUtility(IRendererFactory, '.pt'))
- self.assertTrue(config.registry.getUtility(IRendererFactory,'.txt'))
- self.assertTrue(config.registry.getUtility(IRendererFactory, '.mak'))
- self.assertTrue(config.registry.getUtility(IRendererFactory, '.mako'))
def test_begin(self):
from pyramid.config import Configurator
@@ -233,6 +227,14 @@ class ConfiguratorTests(unittest.TestCase):
config = self._makeOne(introspection=False)
self.assertEqual(config.introspection, False)
+ def test_ctor_default_webob_response_adapter_registered(self):
+ from webob import Response as WebobResponse
+ response = WebobResponse()
+ from pyramid.interfaces import IResponse
+ config = self._makeOne(autocommit=True)
+ result = config.registry.queryAdapter(response, IResponse)
+ self.assertEqual(result, response)
+
def test_with_package_module(self):
from pyramid.tests.test_config import test_init
import pyramid.tests
@@ -556,35 +558,6 @@ class ConfiguratorTests(unittest.TestCase):
utility = reg.getUtility(IRequestFactory)
self.assertEqual(utility, pyramid.tests.test_config)
- def test_setup_registry_renderer_globals_factory(self):
- from pyramid.registry import Registry
- from pyramid.interfaces import IRendererGlobalsFactory
- reg = Registry()
- config = self._makeOne(reg)
- factory = object()
- with warnings.catch_warnings():
- warnings.filterwarnings('ignore')
- config.setup_registry(renderer_globals_factory=factory)
- self.assertEqual(reg.queryUtility(IRendererGlobalsFactory), None)
- config.commit()
- utility = reg.getUtility(IRendererGlobalsFactory)
- self.assertEqual(utility, factory)
-
- def test_setup_registry_renderer_globals_factory_dottedname(self):
- from pyramid.registry import Registry
- from pyramid.interfaces import IRendererGlobalsFactory
- reg = Registry()
- config = self._makeOne(reg)
- import pyramid.tests.test_config
- with warnings.catch_warnings():
- warnings.filterwarnings('ignore')
- config.setup_registry(
- renderer_globals_factory='pyramid.tests.test_config')
- self.assertEqual(reg.queryUtility(IRendererGlobalsFactory), None)
- config.commit()
- utility = reg.getUtility(IRendererGlobalsFactory)
- self.assertEqual(utility, pyramid.tests.test_config)
-
def test_setup_registry_alternate_renderers(self):
from pyramid.registry import Registry
from pyramid.interfaces import IRendererFactory
@@ -1188,221 +1161,6 @@ pyramid.tests.test_config.dummy_include2""",
foo_meth = config.foo
self.assertTrue(getattr(foo_meth, im_func) is foo)
-class TestConfiguratorDeprecatedFeatures(unittest.TestCase):
-
- def setUp(self):
- self.warnings = warnings.catch_warnings()
- self.warnings.__enter__()
- warnings.filterwarnings('ignore')
-
- def tearDown(self):
- self.warnings.__exit__(None, None, None)
-
- def _makeOne(self, *arg, **kw):
- from pyramid.config import Configurator
- config = Configurator(*arg, **kw)
- config.registry._dont_resolve_responses = True
- return config
-
- def _getRouteRequestIface(self, config, name):
- from pyramid.interfaces import IRouteRequest
- iface = config.registry.getUtility(IRouteRequest, name)
- return iface
-
- def _getViewCallable(self, config, ctx_iface=None, request_iface=None,
- name='', exception_view=False):
- from zope.interface import Interface
- from pyramid.interfaces import IView
- from pyramid.interfaces import IViewClassifier
- from pyramid.interfaces import IExceptionViewClassifier
- if exception_view:
- classifier = IExceptionViewClassifier
- else:
- classifier = IViewClassifier
- if ctx_iface is None:
- ctx_iface = Interface
- return config.registry.adapters.lookup(
- (classifier, request_iface, ctx_iface), IView, name=name,
- default=None)
-
- def _registerRenderer(self, config, name='.txt'):
- from pyramid.interfaces import IRendererFactory
- from pyramid.interfaces import ITemplateRenderer
- from zope.interface import implementer
- @implementer(ITemplateRenderer)
- class Renderer:
- def __init__(self, info):
- self.__class__.info = info
- def __call__(self, *arg):
- return 'Hello!'
- config.registry.registerUtility(Renderer, IRendererFactory, name=name)
- return Renderer
-
- def _assertRoute(self, config, name, path, num_predicates=0):
- from pyramid.interfaces import IRoutesMapper
- mapper = config.registry.getUtility(IRoutesMapper)
- routes = mapper.get_routes()
- route = routes[0]
- self.assertEqual(len(routes), 1)
- self.assertEqual(route.name, name)
- self.assertEqual(route.path, path)
- self.assertEqual(len(routes[0].predicates), num_predicates)
- return route
-
- def _makeRequest(self, config):
- request = DummyRequest()
- request.registry = config.registry
- return request
-
- def test_add_route_with_view(self):
- from pyramid.renderers import null_renderer
- config = self._makeOne(autocommit=True)
- view = lambda *arg: 'OK'
- config.add_route('name', 'path', view=view, view_renderer=null_renderer)
- request_type = self._getRouteRequestIface(config, 'name')
- wrapper = self._getViewCallable(config, None, request_type)
- self.assertEqual(wrapper(None, None), 'OK')
- self._assertRoute(config, 'name', 'path')
-
- def test_add_route_with_view_context(self):
- from pyramid.renderers import null_renderer
- config = self._makeOne(autocommit=True)
- view = lambda *arg: 'OK'
- config.add_route('name', 'path', view=view, view_context=IDummy,
- view_renderer=null_renderer)
- request_type = self._getRouteRequestIface(config, 'name')
- wrapper = self._getViewCallable(config, IDummy, request_type)
- self.assertEqual(wrapper(None, None), 'OK')
- self._assertRoute(config, 'name', 'path')
- wrapper = self._getViewCallable(config, IOther, request_type)
- self.assertEqual(wrapper, None)
-
- def test_add_route_with_view_exception(self):
- from pyramid.renderers import null_renderer
- from zope.interface import implementedBy
- config = self._makeOne(autocommit=True)
- view = lambda *arg: 'OK'
- config.add_route('name', 'path', view=view, view_context=RuntimeError,
- view_renderer=null_renderer)
- request_type = self._getRouteRequestIface(config, 'name')
- wrapper = self._getViewCallable(
- config, ctx_iface=implementedBy(RuntimeError),
- request_iface=request_type, exception_view=True)
- self.assertEqual(wrapper(None, None), 'OK')
- self._assertRoute(config, 'name', 'path')
- wrapper = self._getViewCallable(
- config, ctx_iface=IOther,
- request_iface=request_type, exception_view=True)
- self.assertEqual(wrapper, None)
-
- def test_add_route_with_view_for(self):
- from pyramid.renderers import null_renderer
- config = self._makeOne(autocommit=True)
- view = lambda *arg: 'OK'
- config.add_route('name', 'path', view=view, view_for=IDummy,
- view_renderer=null_renderer)
- request_type = self._getRouteRequestIface(config, 'name')
- wrapper = self._getViewCallable(config, IDummy, request_type)
- self.assertEqual(wrapper(None, None), 'OK')
- self._assertRoute(config, 'name', 'path')
- wrapper = self._getViewCallable(config, IOther, request_type)
- self.assertEqual(wrapper, None)
-
- def test_add_route_with_for_(self):
- from pyramid.renderers import null_renderer
- config = self._makeOne(autocommit=True)
- view = lambda *arg: 'OK'
- config.add_route('name', 'path', view=view, for_=IDummy,
- view_renderer=null_renderer)
- request_type = self._getRouteRequestIface(config, 'name')
- wrapper = self._getViewCallable(config, IDummy, request_type)
- self.assertEqual(wrapper(None, None), 'OK')
- self._assertRoute(config, 'name', 'path')
- wrapper = self._getViewCallable(config, IOther, request_type)
- self.assertEqual(wrapper, None)
-
- def test_add_route_with_view_renderer(self):
- config = self._makeOne(autocommit=True)
- self._registerRenderer(config)
- view = lambda *arg: 'OK'
- config.add_route('name', 'path', view=view,
- view_renderer='files/minimal.txt')
- request_type = self._getRouteRequestIface(config, 'name')
- wrapper = self._getViewCallable(config, None, request_type)
- self._assertRoute(config, 'name', 'path')
- self.assertEqual(wrapper(None, None).body, b'Hello!')
-
- def test_add_route_with_view_attr(self):
- from pyramid.renderers import null_renderer
- config = self._makeOne(autocommit=True)
- self._registerRenderer(config)
- class View(object):
- def __init__(self, context, request):
- pass
- def alt(self):
- return 'OK'
- config.add_route('name', 'path', view=View, view_attr='alt',
- view_renderer=null_renderer)
- request_type = self._getRouteRequestIface(config, 'name')
- wrapper = self._getViewCallable(config, None, request_type)
- self._assertRoute(config, 'name', 'path')
- request = self._makeRequest(config)
- self.assertEqual(wrapper(None, request), 'OK')
-
- def test_add_route_with_view_renderer_alias(self):
- config = self._makeOne(autocommit=True)
- self._registerRenderer(config)
- view = lambda *arg: 'OK'
- config.add_route('name', 'path', view=view,
- renderer='files/minimal.txt')
- request_type = self._getRouteRequestIface(config, 'name')
- wrapper = self._getViewCallable(config, None, request_type)
- self._assertRoute(config, 'name', 'path')
- self.assertEqual(wrapper(None, None).body, b'Hello!')
-
- def test_add_route_with_view_permission(self):
- from pyramid.interfaces import IAuthenticationPolicy
- from pyramid.interfaces import IAuthorizationPolicy
- config = self._makeOne(autocommit=True)
- policy = lambda *arg: None
- config.registry.registerUtility(policy, IAuthenticationPolicy)
- config.registry.registerUtility(policy, IAuthorizationPolicy)
- view = lambda *arg: 'OK'
- config.add_route('name', 'path', view=view, view_permission='edit')
- request_type = self._getRouteRequestIface(config, 'name')
- wrapper = self._getViewCallable(config, None, request_type)
- self._assertRoute(config, 'name', 'path')
- self.assertTrue(hasattr(wrapper, '__call_permissive__'))
-
- def test_add_route_with_view_permission_alias(self):
- from pyramid.interfaces import IAuthenticationPolicy
- from pyramid.interfaces import IAuthorizationPolicy
- config = self._makeOne(autocommit=True)
- policy = lambda *arg: None
- config.registry.registerUtility(policy, IAuthenticationPolicy)
- config.registry.registerUtility(policy, IAuthorizationPolicy)
- view = lambda *arg: 'OK'
- config.add_route('name', 'path', view=view, permission='edit')
- request_type = self._getRouteRequestIface(config, 'name')
- wrapper = self._getViewCallable(config, None, request_type)
- self._assertRoute(config, 'name', 'path')
- self.assertTrue(hasattr(wrapper, '__call_permissive__'))
-
- def test_conflict_route_with_view(self):
- config = self._makeOne()
- def view1(request): pass
- def view2(request): pass
- config.add_route('a', '/a', view=view1)
- config.add_route('a', '/a', view=view2)
- try:
- config.commit()
- except ConfigurationConflictError as why:
- c1, c2 = _conflictFunctions(why)
- self.assertEqual(c1, 'test_conflict_route_with_view')
- self.assertEqual(c2, 'test_conflict_route_with_view')
- else: # pragma: no cover
- raise AssertionError
-
class TestConfigurator_add_directive(unittest.TestCase):
def setUp(self):
@@ -1496,6 +1254,39 @@ class TestConfigurator_add_directive(unittest.TestCase):
self.assertEqual(action['callable'], None)
self.assertEqual(action['args'], config2.package)
+class TestConfigurator__add_predicate(unittest.TestCase):
+ def _makeOne(self):
+ from pyramid.config import Configurator
+ return Configurator()
+
+ def test_factory_as_object(self):
+ config = self._makeOne()
+
+ def _fakeAction(discriminator, callable=None, args=(), kw=None,
+ order=0, introspectables=(), **extra):
+ self.assertEqual(len(introspectables), 1)
+ self.assertEqual(introspectables[0]['name'], 'testing')
+ self.assertEqual(introspectables[0]['factory'], DummyPredicate)
+
+ config.action = _fakeAction
+ config._add_predicate('route', 'testing', DummyPredicate)
+
+ def test_factory_as_dotted_name(self):
+ config = self._makeOne()
+
+ def _fakeAction(discriminator, callable=None, args=(),
+ kw=None, order=0, introspectables=(), **extra):
+ self.assertEqual(len(introspectables), 1)
+ self.assertEqual(introspectables[0]['name'], 'testing')
+ self.assertEqual(introspectables[0]['factory'], DummyPredicate)
+
+ config.action = _fakeAction
+ config._add_predicate(
+ 'route',
+ 'testing',
+ 'pyramid.tests.test_config.test_init.DummyPredicate'
+ )
+
class TestActionState(unittest.TestCase):
def _makeOne(self):
from pyramid.config import ActionState
@@ -2010,3 +1801,5 @@ class DummyIntrospectable(object):
def register(self, introspector, action_info):
self.registered.append((introspector, action_info))
+class DummyPredicate(object):
+ pass
diff --git a/pyramid/tests/test_config/test_rendering.py b/pyramid/tests/test_config/test_rendering.py
index e6ee9ad70..cede64d3a 100644
--- a/pyramid/tests/test_config/test_rendering.py
+++ b/pyramid/tests/test_config/test_rendering.py
@@ -1,7 +1,4 @@
import unittest
-import warnings
-
-from pyramid.tests.test_config import dummyfactory
class TestRenderingConfiguratorMixin(unittest.TestCase):
def _makeOne(self, *arg, **kw):
@@ -9,27 +6,15 @@ class TestRenderingConfiguratorMixin(unittest.TestCase):
config = Configurator(*arg, **kw)
return config
- def test_set_renderer_globals_factory(self):
- from pyramid.interfaces import IRendererGlobalsFactory
- config = self._makeOne(autocommit=True)
- factory = object()
- with warnings.catch_warnings():
- warnings.filterwarnings('ignore')
- config.set_renderer_globals_factory(factory)
- self.assertEqual(
- config.registry.getUtility(IRendererGlobalsFactory),
- factory)
-
- def test_set_renderer_globals_factory_dottedname(self):
- from pyramid.interfaces import IRendererGlobalsFactory
+ def test_add_default_renderers(self):
+ from pyramid.config.rendering import DEFAULT_RENDERERS
+ from pyramid.interfaces import IRendererFactory
config = self._makeOne(autocommit=True)
- with warnings.catch_warnings():
- warnings.filterwarnings('ignore')
- config.set_renderer_globals_factory(
- 'pyramid.tests.test_config.dummyfactory')
- self.assertEqual(
- config.registry.getUtility(IRendererGlobalsFactory),
- dummyfactory)
+ config.add_default_renderers()
+ for name, impl in DEFAULT_RENDERERS:
+ self.assertTrue(
+ config.registry.queryUtility(IRendererFactory, name) is not None
+ )
def test_add_renderer(self):
from pyramid.interfaces import IRendererFactory
diff --git a/pyramid/tests/test_config/test_routes.py b/pyramid/tests/test_config/test_routes.py
index 6fb5189f6..1d2530c02 100644
--- a/pyramid/tests/test_config/test_routes.py
+++ b/pyramid/tests/test_config/test_routes.py
@@ -153,10 +153,14 @@ class RoutesConfiguratorMixinTests(unittest.TestCase):
self.assertEqual(predicate(None, request), False)
def test_add_route_with_custom_predicates(self):
+ import warnings
config = self._makeOne(autocommit=True)
def pred1(context, request): pass
def pred2(context, request): pass
- config.add_route('name', 'path', custom_predicates=(pred1, pred2))
+ with warnings.catch_warnings(record=True) as w:
+ warnings.filterwarnings('always')
+ config.add_route('name', 'path', custom_predicates=(pred1, pred2))
+ self.assertEqual(len(w), 1)
route = self._assertRoute(config, 'name', 'path', 2)
self.assertEqual(len(route.predicates), 2)
diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py
index f6cd414fb..bb61714ae 100644
--- a/pyramid/tests/test_config/test_util.py
+++ b/pyramid/tests/test_config/test_util.py
@@ -380,8 +380,8 @@ class TestPredicateList(unittest.TestCase):
self.assertEqual(predicates[2].text(), '!header header')
self.assertEqual(predicates[1](None, request), True)
self.assertEqual(predicates[2](None, request), True)
-
-
+
+
class Test_takes_one_arg(unittest.TestCase):
def _callFUT(self, view, attr=None, argname=None):
from pyramid.config.util import takes_one_arg
@@ -560,7 +560,7 @@ class Test_takes_one_arg(unittest.TestCase):
class Foo: pass
foo = Foo()
self.assertFalse(self._callFUT(foo))
-
+
def test_method_onearg_named_request(self):
class Foo:
def method(self, request):
@@ -586,11 +586,11 @@ class TestNotted(unittest.TestCase):
self.assertEqual(inst.text(), '')
self.assertEqual(inst.phash(), '')
self.assertEqual(inst(None, None), True)
-
+
class DummyPredicate(object):
def __init__(self, result):
self.result = result
-
+
def text(self):
return self.result
@@ -598,7 +598,7 @@ class DummyPredicate(object):
def __call__(self, context, request):
return True
-
+
class DummyCustomPredicate(object):
def __init__(self):
self.__text__ = 'custom predicate'
@@ -626,4 +626,4 @@ class DummyRequest:
class DummyConfigurator(object):
def maybe_dotted(self, thing):
return thing
-
+
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 94bc497ba..be2865d30 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -47,7 +47,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
def __init__(self, info):
self.__class__.info = info
def __call__(self, *arg):
- return 'Hello!'
+ return b'Hello!'
config.registry.registerUtility(Renderer, IRendererFactory, name=name)
return Renderer
@@ -109,6 +109,37 @@ class TestViewsConfigurationMixin(unittest.TestCase):
view = self._getViewCallable(config)
self.assertTrue(b'Hello!' in view(None, None).body)
+ def test_add_view_with_tmpl_renderer_factory_introspector_missing(self):
+ config = self._makeOne(autocommit=True)
+ config.introspection = False
+ config.introspector = None
+ config.add_view(renderer='dummy.pt')
+ view = self._getViewCallable(config)
+ self.assertRaises(ValueError, view, None, None)
+
+ def test_add_view_with_tmpl_renderer_factory_no_renderer_factory(self):
+ config = self._makeOne(autocommit=True)
+ introspector = DummyIntrospector()
+ config.introspector = introspector
+ config.add_view(renderer='dummy.pt')
+ self.assertFalse(('renderer factories', '.pt') in
+ introspector.related[-1])
+ view = self._getViewCallable(config)
+ self.assertRaises(ValueError, view, None, None)
+
+ def test_add_view_with_tmpl_renderer_factory_with_renderer_factory(self):
+ config = self._makeOne(autocommit=True)
+ introspector = DummyIntrospector(True)
+ config.introspector = introspector
+ def dummy_factory(helper):
+ return lambda val, system_vals: 'Hello!'
+ config.add_renderer('.pt', dummy_factory)
+ config.add_view(renderer='dummy.pt')
+ self.assertTrue(
+ ('renderer factories', '.pt') in introspector.related[-1])
+ view = self._getViewCallable(config)
+ self.assertTrue(b'Hello!' in view(None, None).body)
+
def test_add_view_wrapped_view_is_decorated(self):
def view(request): # request-only wrapper
""" """
@@ -264,7 +295,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
wrapper = self._getViewCallable(config)
result = wrapper(None, None)
self.assertEqual(result, 'OK')
-
+
def test_add_view_as_instance_requestonly(self):
from pyramid.renderers import null_renderer
class AView:
@@ -994,7 +1025,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
pass
foo = Foo()
bar = Bar()
-
+
from pyramid.interfaces import IRequest
from pyramid.interfaces import IView
from pyramid.interfaces import IViewClassifier
@@ -1029,6 +1060,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
config = self._makeOne(autocommit=True)
renderer = self._registerRenderer(config)
fixture = 'pyramid.tests.test_config:files/minimal.txt'
+ config.introspection = False
config.add_view(view=view, renderer=fixture)
wrapper = self._getViewCallable(config)
request = self._makeRequest(config)
@@ -1055,7 +1087,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
def __init__(self, *arg, **kw):
pass
def __call__(self, *arg, **kw):
- return 'moo'
+ return b'moo'
config.add_renderer(None, moo)
config.add_view(view=view)
wrapper = self._getViewCallable(config)
@@ -1069,6 +1101,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
config = self._makeOne(autocommit=True)
renderer = self._registerRenderer(config)
fixture = 'pyramid.tests.test_config:files/minimal.txt'
+ config.introspection = False
config.add_view(view=None, renderer=fixture)
wrapper = self._getViewCallable(config)
request = self._makeRequest(config)
@@ -1198,7 +1231,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
request = self._makeRequest(config)
request.method = 'HEAD'
self.assertEqual(wrapper(None, request), 'OK')
-
+
def test_add_view_with_request_param_noval_true(self):
from pyramid.renderers import null_renderer
view = lambda *arg: 'OK'
@@ -1389,6 +1422,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
self._assertNotFound(wrapper, None, request)
def test_add_view_with_custom_predicates_match(self):
+ import warnings
from pyramid.renderers import null_renderer
view = lambda *arg: 'OK'
config = self._makeOne(autocommit=True)
@@ -1397,13 +1431,17 @@ class TestViewsConfigurationMixin(unittest.TestCase):
def pred2(context, request):
return True
predicates = (pred1, pred2)
- config.add_view(view=view, custom_predicates=predicates,
- renderer=null_renderer)
+ with warnings.catch_warnings(record=True) as w:
+ warnings.filterwarnings('always')
+ config.add_view(view=view, custom_predicates=predicates,
+ renderer=null_renderer)
+ self.assertEqual(len(w), 1)
wrapper = self._getViewCallable(config)
request = self._makeRequest(config)
self.assertEqual(wrapper(None, request), 'OK')
def test_add_view_with_custom_predicates_nomatch(self):
+ import warnings
view = lambda *arg: 'OK'
config = self._makeOne(autocommit=True)
def pred1(context, request):
@@ -1411,22 +1449,29 @@ class TestViewsConfigurationMixin(unittest.TestCase):
def pred2(context, request):
return False
predicates = (pred1, pred2)
- config.add_view(view=view, custom_predicates=predicates)
+ with warnings.catch_warnings(record=True) as w:
+ warnings.filterwarnings('always')
+ config.add_view(view=view, custom_predicates=predicates)
+ self.assertEqual(len(w), 1)
wrapper = self._getViewCallable(config)
request = self._makeRequest(config)
self._assertNotFound(wrapper, None, request)
def test_add_view_custom_predicate_bests_standard_predicate(self):
+ import warnings
from pyramid.renderers import null_renderer
view = lambda *arg: 'OK'
view2 = lambda *arg: 'NOT OK'
config = self._makeOne(autocommit=True)
def pred1(context, request):
return True
- config.add_view(view=view, custom_predicates=(pred1,),
- renderer=null_renderer)
- config.add_view(view=view2, request_method='GET',
+ with warnings.catch_warnings(record=True) as w:
+ warnings.filterwarnings('always')
+ config.add_view(view=view, custom_predicates=(pred1,),
renderer=null_renderer)
+ config.add_view(view=view2, request_method='GET',
+ renderer=null_renderer)
+ self.assertEqual(len(w), 1)
wrapper = self._getViewCallable(config)
request = self._makeRequest(config)
request.method = 'GET'
@@ -1585,7 +1630,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
context = DummyContext()
request = self._makeRequest(config)
self.assertRaises(PredicateMismatch, wrapper, context, request)
-
+
def test_add_view_with_view_config_and_view_defaults_doesnt_conflict(self):
from pyramid.renderers import null_renderer
class view(object):
@@ -1663,7 +1708,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
def __init__(self, view):
pass
def __call__(self, *arg, **kw):
- return 'foo'
+ return b'foo'
def view(request):
return 'OK'
config = self._makeOne()
@@ -1687,8 +1732,8 @@ class TestViewsConfigurationMixin(unittest.TestCase):
wrapped = config.registry.adapters.lookup(
(IViewClassifier, request_type, Interface), IView, name='')
from pyramid.request import Request
- request = Request.blank('/static/minimal.pt')
- request.subpath = ('minimal.pt', )
+ request = Request.blank('/static/minimal.txt')
+ request.subpath = ('minimal.txt', )
result = wrapped(None, request)
self.assertEqual(result.status, '200 OK')
self.assertTrue(result.body.startswith(b'<div'))
@@ -1725,7 +1770,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
config.add_static_view('static', static_path)
self.assertEqual(info.added,
[(config, 'static', static_path, {})])
-
+
def test_add_forbidden_view(self):
from pyramid.renderers import null_renderer
from zope.interface import implementedBy
@@ -1819,7 +1864,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
config = self._makeOne(autocommit=True)
self.assertRaises(ConfigurationError,
config.add_notfound_view, http_cache='foo')
-
+
def test_add_notfound_view_append_slash(self):
from pyramid.response import Response
from pyramid.renderers import null_renderer
@@ -1839,26 +1884,31 @@ class TestViewsConfigurationMixin(unittest.TestCase):
request_iface=IRequest)
result = view(None, request)
self.assertEqual(result.location, '/scriptname/foo/?a=1&b=2')
-
+
+ # 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
+ self.assertEqual(response.text, value)
+ else: # pragma: nocover
+ self.assertEqual(response.body, value)
+
def test_add_notfound_view_with_renderer(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
from pyramid.httpexceptions import HTTPNotFound
config = self._makeOne(autocommit=True)
view = lambda *arg: {}
+ config.introspection = False
config.add_notfound_view(
view,
- renderer='pyramid.tests.test_config:files/minimal.pt')
- config.begin()
- try: # chameleon depends on being able to find a threadlocal registry
- request = self._makeRequest(config)
- view = self._getViewCallable(config,
- ctx_iface=implementedBy(HTTPNotFound),
- request_iface=IRequest)
- result = view(None, request)
- finally:
- config.end()
- self.assertTrue(b'div' in result.body)
+ renderer='json')
+ request = self._makeRequest(config)
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPNotFound),
+ request_iface=IRequest)
+ result = view(None, request)
+ self._assertBody(result, '{}')
def test_add_forbidden_view_with_renderer(self):
from zope.interface import implementedBy
@@ -1866,19 +1916,16 @@ class TestViewsConfigurationMixin(unittest.TestCase):
from pyramid.httpexceptions import HTTPForbidden
config = self._makeOne(autocommit=True)
view = lambda *arg: {}
+ config.introspection = False
config.add_forbidden_view(
view,
- renderer='pyramid.tests.test_config:files/minimal.pt')
- config.begin()
- try: # chameleon requires a threadlocal registry
- request = self._makeRequest(config)
- view = self._getViewCallable(config,
- ctx_iface=implementedBy(HTTPForbidden),
- request_iface=IRequest)
- result = view(None, request)
- finally:
- config.end()
- self.assertTrue(b'div' in result.body)
+ renderer='json')
+ request = self._makeRequest(config)
+ view = self._getViewCallable(config,
+ ctx_iface=implementedBy(HTTPForbidden),
+ request_iface=IRequest)
+ result = view(None, request)
+ self._assertBody(result, '{}')
def test_set_view_mapper(self):
from pyramid.interfaces import IViewMapperFactory
@@ -2228,12 +2275,12 @@ class TestViewDeriver(unittest.TestCase):
def tearDown(self):
self.config = None
-
+
def _makeOne(self, **kw):
kw['registry'] = self.config.registry
from pyramid.config.views import ViewDeriver
return ViewDeriver(**kw)
-
+
def _makeRequest(self):
request = DummyRequest()
request.registry = self.config.registry
@@ -2262,7 +2309,7 @@ class TestViewDeriver(unittest.TestCase):
result(None, None)
except ValueError as e:
self.assertEqual(
- e.args[0],
+ e.args[0],
'Could not convert return value of the view callable function '
'pyramid.tests.test_config.test_views.view into a response '
'object. The value returned was None. You may have forgotten '
@@ -2281,7 +2328,7 @@ class TestViewDeriver(unittest.TestCase):
result(None, None)
except ValueError as e:
self.assertEqual(
- e.args[0],
+ e.args[0],
"Could not convert return value of the view callable function "
"pyramid.tests.test_config.test_views.view into a response "
"object. The value returned was {'a': 1}. You may have "
@@ -2289,7 +2336,7 @@ class TestViewDeriver(unittest.TestCase):
)
else: # pragma: no cover
raise AssertionError
-
+
def test_instance_returns_non_adaptable(self):
class AView(object):
def __call__(self, request):
@@ -2348,7 +2395,7 @@ class TestViewDeriver(unittest.TestCase):
result(None, request)
except ValueError as e:
self.assertEqual(
- e.args[0],
+ e.args[0],
'Could not convert return value of the view callable '
'method __call__ of '
'class pyramid.tests.test_config.test_views.AView into a '
@@ -2372,7 +2419,7 @@ class TestViewDeriver(unittest.TestCase):
result(None, request)
except ValueError as e:
self.assertEqual(
- e.args[0],
+ e.args[0],
'Could not convert return value of the view callable '
'method theviewmethod of '
'class pyramid.tests.test_config.test_views.AView into a '
@@ -2381,7 +2428,7 @@ class TestViewDeriver(unittest.TestCase):
)
else: # pragma: no cover
raise AssertionError
-
+
def test_requestonly_function(self):
response = DummyResponse()
def view(request):
@@ -2415,7 +2462,7 @@ class TestViewDeriver(unittest.TestCase):
self.assertEqual(value, 'OK')
self.assertEqual(system['request'], request)
self.assertEqual(system['context'], context)
- return 'moo'
+ return b'moo'
return inner
def view(request):
return 'OK'
@@ -2932,7 +2979,7 @@ class TestViewDeriver(unittest.TestCase):
'predicate mismatch for view myview (pred2)')
else: # pragma: no cover
raise AssertionError
-
+
def test_with_predicates_all(self):
response = DummyResponse()
view = lambda *arg: response
@@ -3243,7 +3290,7 @@ class TestViewDeriver(unittest.TestCase):
expires = parse_httpdate(headers['Expires'])
assert_similar_datetime(expires, when)
self.assertEqual(headers['Cache-Control'], 'max-age=3600')
-
+
def test_http_cached_view_timedelta(self):
import datetime
from pyramid.response import Response
@@ -3339,7 +3386,7 @@ class TestViewDeriver(unittest.TestCase):
class TestDefaultViewMapper(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
- self.registry = self.config.registry
+ self.registry = self.config.registry
def tearDown(self):
del self.registry
@@ -3601,7 +3648,7 @@ class TestStaticURLInfo(unittest.TestCase):
def _getTargetClass(self):
from pyramid.config.views import StaticURLInfo
return StaticURLInfo
-
+
def _makeOne(self):
return self._getTargetClass()()
@@ -3773,27 +3820,13 @@ class TestStaticURLInfo(unittest.TestCase):
permission='abc')
self.assertEqual(config.view_kw['permission'], 'abc')
- def test_add_viewname_with_view_permission(self):
- config = self._makeConfig()
- inst = self._makeOne()
- inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1,
- view_permission='abc')
- self.assertEqual(config.view_kw['permission'], 'abc')
-
- def test_add_viewname_with_view_context(self):
- config = self._makeConfig()
- inst = self._makeOne()
- inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1,
- view_context=DummyContext)
- self.assertEqual(config.view_kw['context'], DummyContext)
-
- def test_add_viewname_with_view_for(self):
+ def test_add_viewname_with_context(self):
config = self._makeConfig()
inst = self._makeOne()
inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1,
- view_for=DummyContext)
+ context=DummyContext)
self.assertEqual(config.view_kw['context'], DummyContext)
-
+
def test_add_viewname_with_for_(self):
config = self._makeConfig()
inst = self._makeOne()
@@ -3801,14 +3834,6 @@ class TestStaticURLInfo(unittest.TestCase):
for_=DummyContext)
self.assertEqual(config.view_kw['context'], DummyContext)
- def test_add_viewname_with_view_renderer(self):
- config = self._makeConfig()
- inst = self._makeOne()
- inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1,
- view_renderer='mypackage:templates/index.pt')
- self.assertEqual(config.view_kw['renderer'],
- 'mypackage:templates/index.pt')
-
def test_add_viewname_with_renderer(self):
config = self._makeConfig()
inst = self._makeOne()
@@ -3817,53 +3842,50 @@ class TestStaticURLInfo(unittest.TestCase):
self.assertEqual(config.view_kw['renderer'],
'mypackage:templates/index.pt')
- def test_add_viewname_with_view_attr(self):
- config = self._makeConfig()
- inst = self._makeOne()
- inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1,
- view_attr='attr')
- self.assertEqual(config.view_kw['attr'], 'attr')
-
class Test_view_description(unittest.TestCase):
def _callFUT(self, view):
from pyramid.config.views import view_description
return view_description(view)
-
+
def test_with_text(self):
def view(): pass
view.__text__ = 'some text'
result = self._callFUT(view)
self.assertEqual(result, 'some text')
-
+
def test_without_text(self):
def view(): pass
result = self._callFUT(view)
- self.assertEqual(result,
+ self.assertEqual(result,
'function pyramid.tests.test_config.test_views.view')
-
+
class DummyRegistry:
pass
+from zope.interface import implementer
+from pyramid.interfaces import IResponse
+@implementer(IResponse)
+class DummyResponse(object):
+ content_type = None
+ default_content_type = None
+ body = None
+
class DummyRequest:
subpath = ()
matchdict = None
+
def __init__(self, environ=None):
if environ is None:
environ = {}
self.environ = environ
self.params = {}
self.cookies = {}
+ self.response = DummyResponse()
class DummyContext:
pass
-from zope.interface import implementer
-from pyramid.interfaces import IResponse
-@implementer(IResponse)
-class DummyResponse(object):
- pass
-
class DummyAccept(object):
def __init__(self, *matches):
self.matches = list(matches)
@@ -3963,3 +3985,14 @@ class DummyPredicate(object):
phash = text
+class DummyIntrospector(object):
+ def __init__(self, getval=None):
+ self.related = []
+ self.introspectables = []
+ self.getval = getval
+ def add(self, introspectable):
+ self.introspectables.append(introspectable)
+ def get(self, name, discrim):
+ return self.getval
+ def relate(self, a, b):
+ self.related.append((a, b))
diff --git a/pyramid/tests/test_encode.py b/pyramid/tests/test_encode.py
index 736ecb5b3..908249877 100644
--- a/pyramid/tests/test_encode.py
+++ b/pyramid/tests/test_encode.py
@@ -41,6 +41,18 @@ class UrlEncodeTests(unittest.TestCase):
result = self._callFUT({'a':1})
self.assertEqual(result, 'a=1')
+ def test_None_value(self):
+ result = self._callFUT([('a', None)])
+ self.assertEqual(result, 'a=')
+
+ def test_None_value_with_prefix(self):
+ result = self._callFUT([('a', '1'), ('b', None)])
+ self.assertEqual(result, 'a=1&b=')
+
+ def test_None_value_with_prefix_values(self):
+ result = self._callFUT([('a', '1'), ('b', None), ('c', None)])
+ self.assertEqual(result, 'a=1&b=&c=')
+
class URLQuoteTests(unittest.TestCase):
def _callFUT(self, val, safe=''):
from pyramid.encode import url_quote
diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py
index 391310432..9d3a9e004 100644
--- a/pyramid/tests/test_integration.py
+++ b/pyramid/tests/test_integration.py
@@ -74,8 +74,8 @@ here = os.path.dirname(__file__)
class StaticAppBase(IntegrationBase):
def test_basic(self):
- res = self.testapp.get('/minimal.pt', status=200)
- _assertBody(res.body, os.path.join(here, 'fixtures/minimal.pt'))
+ res = self.testapp.get('/minimal.txt', status=200)
+ _assertBody(res.body, os.path.join(here, 'fixtures/minimal.txt'))
def test_hidden(self):
res = self.testapp.get('/static/.hiddenfile', status=200)
@@ -119,7 +119,7 @@ class StaticAppBase(IntegrationBase):
def test_not_modified(self):
self.testapp.extra_environ = {
'HTTP_IF_MODIFIED_SINCE':httpdate(fiveyrsfuture)}
- res = self.testapp.get('/minimal.pt', status=304)
+ res = self.testapp.get('/minimal.txt', status=304)
self.assertEqual(res.body, b'')
def test_file_in_subdir(self):
@@ -222,19 +222,19 @@ class TestStaticAppNoSubpath(unittest.TestCase):
return request
def test_basic(self):
- request = self._makeRequest({'PATH_INFO':'/minimal.pt'})
+ request = self._makeRequest({'PATH_INFO':'/minimal.txt'})
context = DummyContext()
result = self.staticapp(context, request)
self.assertEqual(result.status, '200 OK')
- _assertBody(result.body, os.path.join(here, 'fixtures/minimal.pt'))
+ _assertBody(result.body, os.path.join(here, 'fixtures/minimal.txt'))
class TestStaticAppWithRoutePrefix(IntegrationBase, unittest.TestCase):
package = 'pyramid.tests.pkgs.static_routeprefix'
def test_includelevel1(self):
- res = self.testapp.get('/static/minimal.pt', status=200)
+ res = self.testapp.get('/static/minimal.txt', status=200)
_assertBody(res.body,
- os.path.join(here, 'fixtures/minimal.pt'))
+ os.path.join(here, 'fixtures/minimal.txt'))
def test_includelevel2(self):
res = self.testapp.get('/prefix/static/index.html', status=200)
diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py
deleted file mode 100644
index 69485ca19..000000000
--- a/pyramid/tests/test_mako_templating.py
+++ /dev/null
@@ -1,655 +0,0 @@
-## come on python gimme some of that sweet, sweet -*- coding: utf-8 -*-
-
-import shutil
-import tempfile
-import unittest
-
-from pyramid import testing
-
-from pyramid.compat import (
- text_,
- text_type,
- )
-
-class Base(object):
- def setUp(self):
- self.config = testing.setUp()
- self.config.begin()
- import os
- here = os.path.abspath(os.path.dirname(__file__))
- self.templates_dir = os.path.join(here, 'fixtures')
-
- def tearDown(self):
- self.config.end()
-
-def maybe_unittest():
- # The latest release of MarkupSafe (0.17) which is used by Mako is
- # incompatible with Python 3.2, so we skip these tests if we cannot
- # import a Mako module which ends up importing MarkupSafe. Note that
- # this version of MarkupSafe *is* compatible with Python 2.6, 2.7, and 3.3,
- # so these tests should not be skipped on those platforms.
- try:
- import mako.lookup
- except (ImportError, SyntaxError, AttributeError): # pragma: no cover
- return object
- else:
- return unittest.TestCase
-
-class Test_renderer_factory(Base, maybe_unittest()):
- def _callFUT(self, info):
- from pyramid.mako_templating import renderer_factory
- return renderer_factory(info)
-
- def _getLookup(self, name='mako.'):
- from pyramid.mako_templating import IMakoLookup
- return self.config.registry.getUtility(IMakoLookup, name=name)
-
- def test_hyphen_filenames(self):
- from pyramid.mako_templating import renderer_factory
-
- info = DummyRendererInfo({
- 'name':'app:moon-and-world.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- 'type': ''
- })
-
- result = renderer_factory(info)
- self.assertEqual(result.path, 'app:moon-and-world.mak')
-
- def test_no_directories(self):
- info = DummyRendererInfo({
- 'name':'pyramid.tests:fixtures/helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- })
- renderer = self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.directories, [])
- self.assertEqual(lookup.filesystem_checks, False)
- self.assertEqual(renderer.path,
- 'pyramid.tests:fixtures/helloworld.mak')
- self.assertEqual(renderer.lookup, lookup)
-
- def test_no_lookup(self):
- settings = {'mako.directories':self.templates_dir}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- renderer = self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.directories, [self.templates_dir])
- self.assertEqual(lookup.filesystem_checks, False)
- self.assertEqual(renderer.path, 'helloworld.mak')
- self.assertEqual(renderer.lookup, lookup)
-
- def test_composite_directories_path(self):
- twice = '\n' + self.templates_dir + '\n' + self.templates_dir + '\n'
- settings = {'mako.directories':twice}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.directories, [self.templates_dir]*2)
-
- def test_directories_list(self):
- import sys
- import os.path
- settings = {'mako.directories':['a', 'b']}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- module_path = os.path.dirname(
- sys.modules['__main__'].__file__).rstrip('.') # ./setup.py
- self.assertEqual(lookup.directories, [
- os.path.join(module_path, 'a'),
- os.path.join(module_path, 'b')])
-
- def test_with_module_directory_asset_spec(self):
- import os
- module_directory = 'pyramid.tests:fixtures'
- settings = {'mako.directories':self.templates_dir,
- 'mako.module_directory':module_directory}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- fixtures = os.path.join(os.path.dirname(__file__), 'fixtures')
- self.assertEqual(lookup.module_directory, fixtures)
-
- def test_with_module_directory_asset_abspath(self):
- import os
- fixtures = os.path.join(os.path.dirname(__file__), 'fixtures')
- settings = {'mako.directories':self.templates_dir,
- 'mako.module_directory':fixtures}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.module_directory, fixtures)
-
- def test_with_input_encoding(self):
- settings = {'mako.directories':self.templates_dir,
- 'mako.input_encoding':'utf-16'}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.template_args['input_encoding'], 'utf-16')
-
- def test_with_error_handler(self):
- settings = {'mako.directories':self.templates_dir,
- 'mako.error_handler':'pyramid.tests'}
- import pyramid.tests
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.template_args['error_handler'], pyramid.tests)
-
- def test_with_preprocessor(self):
- settings = {'mako.directories':self.templates_dir,
- 'mako.preprocessor':'pyramid.tests'}
- import pyramid.tests
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.template_args['preprocessor'], pyramid.tests)
-
- def test_with_default_filters(self):
- settings = {'mako.directories':self.templates_dir,
- 'mako.default_filters':'\nh\ng\n\n'}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.template_args['default_filters'], ['h', 'g'])
-
- def test_with_default_filters_list(self):
- settings = {'mako.directories':self.templates_dir,
- 'mako.default_filters':['h', 'g']}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.template_args['default_filters'], ['h', 'g'])
-
- def test_with_imports(self):
- settings = {'mako.directories':self.templates_dir,
- 'mako.imports':'\none\ntwo\n\n'}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.template_args['imports'], ['one', 'two'])
-
- def test_with_imports_list(self):
- settings = {'mako.directories':self.templates_dir,
- 'mako.imports':['one', 'two']}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.template_args['imports'], ['one', 'two'])
-
- def test_with_strict_undefined_true(self):
- settings = {'mako.directories':self.templates_dir,
- 'mako.strict_undefined':'true'}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.template_args['strict_undefined'], True)
-
- def test_with_strict_undefined_false(self):
- settings = {'mako.directories':self.templates_dir,
- 'mako.strict_undefined':'false'}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- })
- self._callFUT(info)
- lookup = self._getLookup()
- self.assertEqual(lookup.template_args['strict_undefined'], False)
-
- def test_with_lookup(self):
- from pyramid.mako_templating import IMakoLookup
- lookup = dict()
- self.config.registry.registerUtility(lookup, IMakoLookup, name='mako.')
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- })
- renderer = self._callFUT(info)
- self.assertEqual(renderer.lookup, lookup)
- self.assertEqual(renderer.path, 'helloworld.mak')
-
- def test_space_dot_name(self):
- from pyramid.mako_templating import renderer_factory
-
- info = DummyRendererInfo({
- 'name':'hello .world.mako',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- })
-
- result = renderer_factory(info)
- self.assertEqual(result.path, 'hello .world.mako')
- self.assertTrue(result.defname is None)
-
- def test_space_dot_name_def(self):
- from pyramid.mako_templating import renderer_factory
-
- info = DummyRendererInfo({
- 'name':'hello .world#comp.mako',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- })
-
- result = renderer_factory(info)
- self.assertEqual(result.path, 'hello .world.mako')
- self.assertEqual(result.defname, 'comp')
-
-class MakoRendererFactoryHelperTests(Base, maybe_unittest()):
- def _getTargetClass(self):
- from pyramid.mako_templating import MakoRendererFactoryHelper
- return MakoRendererFactoryHelper
-
- def _makeOne(self, *arg, **kw):
- klass = self._getTargetClass()
- return klass(*arg, **kw)
-
- def _getLookup(self, name='mako.'):
- from pyramid.mako_templating import IMakoLookup
- return self.config.registry.getUtility(IMakoLookup, name=name)
-
- def test_no_settings_prefix(self):
- settings = {'foo.directories':self.templates_dir}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- 'type':'foo',
- })
- helper = self._makeOne()
- renderer = helper(info)
- lookup = self._getLookup('foo.')
- self.assertEqual(lookup.directories, [self.templates_dir])
- self.assertEqual(lookup.filesystem_checks, False)
- self.assertEqual(renderer.path, 'helloworld.mak')
- self.assertEqual(renderer.lookup, lookup)
-
- def test_custom_settings_prefix(self):
- settings = {'bar.directories':self.templates_dir}
- info = DummyRendererInfo({
- 'name':'helloworld.mak',
- 'package':None,
- 'registry':self.config.registry,
- 'settings':settings,
- 'type':'foo',
- })
- helper = self._makeOne('bar.')
- renderer = helper(info)
- lookup = self._getLookup('bar.')
- self.assertEqual(lookup.directories, [self.templates_dir])
- self.assertEqual(lookup.filesystem_checks, False)
- self.assertEqual(renderer.path, 'helloworld.mak')
- self.assertEqual(renderer.lookup, lookup)
-
-class MakoLookupTemplateRendererTests(Base, maybe_unittest()):
- def _getTargetClass(self):
- from pyramid.mako_templating import MakoLookupTemplateRenderer
- return MakoLookupTemplateRenderer
-
- def _makeOne(self, *arg, **kw):
- klass = self._getTargetClass()
- return klass(*arg, **kw)
-
- def test_instance_implements_ITemplate(self):
- from zope.interface.verify import verifyObject
- from pyramid.interfaces import ITemplateRenderer
- verifyObject(ITemplateRenderer, self._makeOne(None, None, None))
-
- def test_class_implements_ITemplate(self):
- from zope.interface.verify import verifyClass
- from pyramid.interfaces import ITemplateRenderer
- verifyClass(ITemplateRenderer, self._getTargetClass())
-
- def test_call(self):
- lookup = DummyLookup()
- instance = self._makeOne('path', None, lookup)
- result = instance({}, {'system':1})
- self.assertTrue(isinstance(result, text_type))
- self.assertEqual(result, text_('result'))
-
- def test_call_with_system_context(self):
- # lame
- lookup = DummyLookup()
- instance = self._makeOne('path', None, lookup)
- result = instance({}, {'context':1})
- self.assertTrue(isinstance(result, text_type))
- self.assertEqual(result, text_('result'))
- self.assertEqual(lookup.values, {'_context':1})
-
- def test_call_with_tuple_value(self):
- lookup = DummyLookup()
- instance = self._makeOne('path', None, lookup)
- warnings = DummyWarnings()
- instance.warnings = warnings
- result = instance(('fub', {}), {'context':1})
- self.assertEqual(lookup.deffed, 'fub')
- self.assertEqual(result, text_('result'))
- self.assertEqual(lookup.values, {'_context':1})
- self.assertEqual(len(warnings.msgs), 1)
-
- def test_call_with_defname(self):
- lookup = DummyLookup()
- instance = self._makeOne('path', 'defname', lookup)
- result = instance({}, {'system':1})
- self.assertTrue(isinstance(result, text_type))
- self.assertEqual(result, text_('result'))
-
- def test_call_with_defname_with_tuple_value(self):
- lookup = DummyLookup()
- instance = self._makeOne('path', 'defname', lookup)
- warnings = DummyWarnings()
- instance.warnings = warnings
- result = instance(('defname', {}), {'context':1})
- self.assertEqual(lookup.deffed, 'defname')
- self.assertEqual(result, text_('result'))
- self.assertEqual(lookup.values, {'_context':1})
- self.assertEqual(len(warnings.msgs), 1)
-
- def test_call_with_defname_with_tuple_value_twice(self):
- lookup = DummyLookup()
- instance1 = self._makeOne('path', 'defname', lookup)
- warnings = DummyWarnings()
- instance1.warnings = warnings
- result1 = instance1(('defname1', {}), {'context':1})
- self.assertEqual(lookup.deffed, 'defname1')
- self.assertEqual(result1, text_('result'))
- self.assertEqual(lookup.values, {'_context':1})
- instance2 = self._makeOne('path', 'defname', lookup)
- warnings = DummyWarnings()
- instance2.warnings = warnings
- result2 = instance2(('defname2', {}), {'context':2})
- self.assertNotEqual(lookup.deffed, 'defname1')
- self.assertEqual(lookup.deffed, 'defname2')
- self.assertEqual(result2, text_('result'))
- self.assertEqual(lookup.values, {'_context':2})
-
- def test_call_with_nondict_value(self):
- lookup = DummyLookup()
- instance = self._makeOne('path', None, lookup)
- self.assertRaises(ValueError, instance, None, {})
-
- def test_call_render_raises(self):
- from pyramid.mako_templating import MakoRenderingException
- lookup = DummyLookup(exc=NotImplementedError)
- instance = self._makeOne('path', None, lookup)
- try:
- instance({}, {})
- except MakoRenderingException as e:
- self.assertTrue('NotImplementedError' in e.text)
- else: # pragma: no cover
- raise AssertionError
-
- def test_implementation(self):
- lookup = DummyLookup()
- instance = self._makeOne('path', None, lookup)
- result = instance.implementation().render_unicode()
- self.assertTrue(isinstance(result, text_type))
- self.assertEqual(result, text_('result'))
-
-class TestIntegration(maybe_unittest()):
- def setUp(self):
- import pyramid.mako_templating
- self.config = testing.setUp()
- self.config.add_settings({'mako.directories':
- 'pyramid.tests:fixtures'})
- self.config.add_renderer('.mak',
- pyramid.mako_templating.renderer_factory)
-
- def tearDown(self):
- self.config.end()
-
- def test_render(self):
- from pyramid.renderers import render
- result = render('helloworld.mak', {'a':1}).replace('\r','')
- self.assertEqual(result, text_('\nHello föö\n', 'utf-8'))
-
- def test_render_from_fs(self):
- from pyramid.renderers import render
- self.config.add_settings({'reload_templates': True})
- result = render('helloworld.mak', {'a':1}).replace('\r','')
- self.assertEqual(result, text_('\nHello föö\n', 'utf-8'))
-
- def test_render_inheritance(self):
- from pyramid.renderers import render
- result = render('helloinherit.mak', {}).replace('\r','')
- self.assertEqual(result, text_('Layout\nHello World!\n'))
-
- def test_render_inheritance_pkg_spec(self):
- from pyramid.renderers import render
- result = render('hello_inherit_pkg.mak', {}).replace('\r','')
- self.assertEqual(result, text_('Layout\nHello World!\n'))
-
- def test_render_namespace(self):
- from pyramid.renderers import render
- result = render('hellocompo.mak', {}).replace('\r','')
- self.assertEqual(result, text_('\nNamespace\nHello \nWorld!\n'))
-
- def test_render_to_response(self):
- from pyramid.renderers import render_to_response
- result = render_to_response('helloworld.mak', {'a':1})
- self.assertEqual(result.ubody.replace('\r',''),
- text_('\nHello föö\n', 'utf-8'))
-
- def test_render_to_response_pkg_spec(self):
- from pyramid.renderers import render_to_response
- result = render_to_response('pyramid.tests:fixtures/helloworld.mak',
- {'a':1})
- self.assertEqual(result.ubody.replace('\r', ''),
- text_('\nHello föö\n', 'utf-8'))
-
- def test_render_with_abs_path(self):
- from pyramid.renderers import render
- result = render('/helloworld.mak', {'a':1}).replace('\r','')
- self.assertEqual(result, text_('\nHello föö\n', 'utf-8'))
-
- def test_get_renderer(self):
- from pyramid.renderers import get_renderer
- result = get_renderer('helloworld.mak')
- self.assertEqual(
- result.implementation().render_unicode().replace('\r',''),
- text_('\nHello föö\n', 'utf-8'))
-
- def test_template_not_found(self):
- from pyramid.renderers import render
- from mako.exceptions import TemplateLookupException
- self.assertRaises(TemplateLookupException, render,
- 'helloworld_not_here.mak', {})
-
- def test_template_default_escaping(self):
- from pyramid.renderers import render
- result = render('nonminimal.mak',
- {'name':'<b>fred</b>'}).replace('\r','')
- self.assertEqual(result, text_('Hello, &lt;b&gt;fred&lt;/b&gt;!\n'))
-
-class TestPkgResourceTemplateLookup(maybe_unittest()):
- def _makeOne(self, **kw):
- from pyramid.mako_templating import PkgResourceTemplateLookup
- return PkgResourceTemplateLookup(**kw)
-
- def get_fixturedir(self):
- import os
- import pyramid.tests
- return os.path.join(os.path.dirname(pyramid.tests.__file__), 'fixtures')
-
- def test_adjust_uri_not_asset_spec(self):
- inst = self._makeOne()
- result = inst.adjust_uri('a', None)
- self.assertEqual(result, '/a')
-
- def test_adjust_uri_asset_spec(self):
- inst = self._makeOne()
- result = inst.adjust_uri('a:b', None)
- self.assertEqual(result, 'a:b')
-
- def test_adjust_uri_asset_spec_with_modified_asset_spec(self):
- inst = self._makeOne()
- result = inst.adjust_uri('a$b', None)
- self.assertEqual(result, 'a:b')
-
- def test_adjust_uri_not_asset_spec_with_relativeto_asset_spec(self):
- inst = self._makeOne()
- result = inst.adjust_uri('c', 'a:b')
- self.assertEqual(result, 'a:c')
-
- def test_adjust_uri_not_asset_spec_with_relativeto_modified_asset_spec(self):
- inst = self._makeOne()
- result = inst.adjust_uri('c', 'a$b')
- self.assertEqual(result, 'a:c')
-
- def test_adjust_uri_not_asset_spec_with_relativeto_not_asset_spec(self):
- inst = self._makeOne()
- result = inst.adjust_uri('b', '../a')
- self.assertEqual(result, '../b')
-
- def test_adjust_uri_not_asset_spec_abs_with_relativeto_asset_spec(self):
- inst = self._makeOne()
- result = inst.adjust_uri('/c', 'a:b')
- self.assertEqual(result, '/c')
-
- def test_adjust_uri_asset_spec_with_relativeto_not_asset_spec_abs(self):
- inst = self._makeOne()
- result = inst.adjust_uri('a:b', '/c')
- self.assertEqual(result, 'a:b')
-
- def test_get_template_not_asset_spec(self):
- fixturedir = self.get_fixturedir()
- inst = self._makeOne(directories=[fixturedir])
- result = inst.get_template('helloworld.mak')
- self.assertFalse(result is None)
-
- def test_get_template_asset_spec_with_filesystem_checks(self):
- inst = self._makeOne(filesystem_checks=True)
- result = inst.get_template('pyramid.tests:fixtures/helloworld.mak')
- self.assertFalse(result is None)
-
- def test_get_template_asset_spec_with_module_dir(self):
- tmpdir = tempfile.mkdtemp()
- try:
- inst = self._makeOne(module_directory=tmpdir)
- result = inst.get_template('pyramid.tests:fixtures/helloworld.mak')
- self.assertFalse(result is None)
- finally:
- shutil.rmtree(tmpdir, ignore_errors=True)
-
- def test_get_template_asset_spec_missing(self):
- from mako.exceptions import TopLevelLookupException
- fixturedir = self.get_fixturedir()
- inst = self._makeOne(filesystem_checks=True, directories=[fixturedir])
- self.assertRaises(TopLevelLookupException, inst.get_template,
- 'pyramid.tests:fixtures/notthere.mak')
-
-class TestMakoRenderingException(unittest.TestCase):
- def _makeOne(self, text):
- from pyramid.mako_templating import MakoRenderingException
- return MakoRenderingException(text)
-
- def test_repr_and_str(self):
- exc = self._makeOne('text')
- self.assertEqual(str(exc), 'text')
- self.assertEqual(repr(exc), 'text')
-
-class DummyLookup(object):
- def __init__(self, exc=None):
- self.exc = exc
-
- def get_template(self, path):
- self.path = path
- return self
-
- def get_def(self, path):
- self.deffed = path
- return self
-
- def render_unicode(self, **values):
- if self.exc:
- raise self.exc
- self.values = values
- return text_('result')
-
-class DummyRendererInfo(object):
- def __init__(self, kw):
- self.__dict__.update(kw)
-
-
-class DummyWarnings(object):
- def __init__(self):
- self.msgs = []
- def warn(self, msg, typ, level):
- self.msgs.append(msg)
diff --git a/pyramid/tests/test_path.py b/pyramid/tests/test_path.py
index a07ebeffa..fd927996a 100644
--- a/pyramid/tests/test_path.py
+++ b/pyramid/tests/test_path.py
@@ -154,6 +154,12 @@ class TestPackageName(unittest.TestCase):
package = DummyPackageOrModule(tests)
result = self._callFUT(package)
self.assertEqual(result, 'pyramid.tests')
+
+ def test_it_namespace_package(self):
+ from pyramid import tests
+ package = DummyNamespacePackage(tests)
+ result = self._callFUT(package)
+ self.assertEqual(result, 'pyramid.tests')
def test_it_module(self):
from pyramid.tests import test_path
@@ -558,3 +564,13 @@ class DummyPackageOrModule:
if self.raise_exc is not None:
raise self.raise_exc
self.__dict__[key] = val
+
+class DummyNamespacePackage:
+ """Has no __file__ attribute.
+ """
+
+ def __init__(self, real_package_or_module):
+ self.__name__ = real_package_or_module.__name__
+ import os
+ self.package_path = os.path.dirname(
+ os.path.abspath(real_package_or_module.__file__))
diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py
index befb714bd..f6b9d2b0d 100644
--- a/pyramid/tests/test_renderers.py
+++ b/pyramid/tests/test_renderers.py
@@ -4,372 +4,6 @@ from pyramid.testing import cleanUp
from pyramid import testing
from pyramid.compat import text_
-class TestTemplateRendererFactory(unittest.TestCase):
- def setUp(self):
- self.config = cleanUp()
-
- def tearDown(self):
- cleanUp()
-
- def _callFUT(self, info, impl):
- from pyramid.renderers import template_renderer_factory
- return template_renderer_factory(info, impl)
-
- def test_lookup_found(self):
- from pyramid.interfaces import IChameleonLookup
- L = []
- def dummy(info):
- L.append(info)
- return True
- self.config.registry.registerUtility(dummy, IChameleonLookup,
- name='abc')
- class DummyInfo(object):
- pass
- info = DummyInfo()
- info.registry = self.config.registry
- info.type = 'abc'
- result = self._callFUT(info, None)
- self.assertEqual(result, True)
- self.assertEqual(L, [info])
-
- def test_lookup_miss(self):
- from pyramid.interfaces import ITemplateRenderer
- import os
- abspath = os.path.abspath(__file__)
- renderer = {}
- self.config.registry.registerUtility(
- renderer, ITemplateRenderer, name=abspath)
- info = DummyRendererInfo({
- 'name':abspath,
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- 'type':'type',
- })
- result = self._callFUT(info, None)
- self.assertTrue(result is renderer)
-
-class TestChameleonRendererLookup(unittest.TestCase):
- def setUp(self):
- self.config = testing.setUp()
-
- def tearDown(self):
- testing.tearDown()
-
- def _makeOne(self, impl):
- from pyramid.renderers import ChameleonRendererLookup
- return ChameleonRendererLookup(impl, self.config.registry)
-
- def _registerTemplateRenderer(self, renderer, name):
- from pyramid.interfaces import ITemplateRenderer
- self.config.registry.registerUtility(
- renderer, ITemplateRenderer, name=name)
-
- def test_get_spec_not_abspath_no_colon_no_package(self):
- lookup = self._makeOne(None)
- result = lookup.get_spec('foo', None)
- self.assertEqual(result, 'foo')
-
- def test_get_spec_not_abspath_no_colon_with_package(self):
- from pyramid import tests
- lookup = self._makeOne(None)
- result = lookup.get_spec('foo', tests)
- self.assertEqual(result, 'pyramid.tests:foo')
-
- def test_get_spec_not_abspath_with_colon_no_package(self):
- lookup = self._makeOne(None)
- result = lookup.get_spec('fudge:foo', None)
- self.assertEqual(result, 'fudge:foo')
-
- def test_get_spec_not_abspath_with_colon_with_package(self):
- from pyramid import tests
- lookup = self._makeOne(None)
- result = lookup.get_spec('fudge:foo', tests)
- self.assertEqual(result, 'fudge:foo')
-
- def test_get_spec_is_abspath_no_colon_no_package(self):
- import os
- lookup = self._makeOne(None)
- spec = os.path.abspath(__file__)
- result = lookup.get_spec(spec, None)
- self.assertEqual(result, spec)
-
- def test_get_spec_is_abspath_no_colon_with_path_in_package(self):
- from pyramid import tests
- import os
- lookup = self._makeOne(None)
- f = __file__
- spec = os.path.abspath(f)
- result = lookup.get_spec(spec, tests)
- self.assertEqual(result, 'pyramid.tests:%s' % os.path.split(f)[-1])
-
- def test_get_spec_is_abspath_no_colon_with_path_outside_package(self):
- import venusian # used only because it's outside of pyramid.tests
- import os
- lookup = self._makeOne(None)
- f = __file__
- spec = os.path.abspath(f)
- result = lookup.get_spec(spec, venusian)
- self.assertEqual(result, spec)
-
- def test_get_spec_is_abspath_with_colon_no_package(self):
- import os
- lookup = self._makeOne(None)
- spec = os.path.join(os.path.abspath(__file__), ':foo')
- result = lookup.get_spec(spec, None)
- self.assertEqual(result, spec)
-
- def test_get_spec_is_abspath_with_colon_with_path_in_package(self):
- from pyramid import tests
- import os
- lookup = self._makeOne(None)
- f = os.path.abspath(__file__)
- spec = os.path.join(f, ':foo')
- result = lookup.get_spec(spec, tests)
- tail = spec.split(os.sep)[-2:]
- self.assertEqual(result, 'pyramid.tests:%s/%s' % tuple(tail))
-
- def test_get_spec_is_abspath_with_colon_with_path_outside_package(self):
- import venusian # used only because it's outside of pyramid.tests
- import os
- lookup = self._makeOne(None)
- spec = os.path.join(os.path.abspath(__file__), ':foo')
- result = lookup.get_spec(spec, venusian)
- self.assertEqual(result, spec)
-
- def test_translate(self):
- from pyramid.interfaces import IChameleonTranslate
- def t(): pass
- self.config.registry.registerUtility(t, IChameleonTranslate)
- lookup = self._makeOne(None)
- self.assertEqual(lookup.translate, t)
-
- def test_debug_settings_None(self):
- self.config.registry.settings = None
- lookup = self._makeOne(None)
- self.assertEqual(lookup.debug, False)
-
- def test_debug_settings_not_None(self):
- self.config.registry.settings = {'debug_templates':True}
- lookup = self._makeOne(None)
- self.assertEqual(lookup.debug, True)
-
- def test_auto_reload_settings_None(self):
- self.config.registry.settings = None
- lookup = self._makeOne(None)
- self.assertEqual(lookup.auto_reload, False)
-
- def test_auto_reload_settings_not_None(self):
- self.config.registry.settings = {'reload_templates':True}
- lookup = self._makeOne(None)
- self.assertEqual(lookup.auto_reload, True)
-
- def test___call__abspath_path_notexists(self):
- abspath = '/wont/exist'
- self._registerTemplateRenderer({}, abspath)
- info = DummyRendererInfo({
- 'name':abspath,
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- 'type':'type',
- })
- lookup = self._makeOne(None)
- self.assertRaises(ValueError, lookup.__call__, info)
-
- def test___call__abspath_alreadyregistered(self):
- import os
- abspath = os.path.abspath(__file__)
- renderer = {}
- self._registerTemplateRenderer(renderer, abspath)
- info = DummyRendererInfo({
- 'name':abspath,
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- 'type':'type',
- })
- lookup = self._makeOne(None)
- result = lookup(info)
- self.assertTrue(result is renderer)
-
- def test___call__abspath_notyetregistered(self):
- import os
- abspath = os.path.abspath(__file__)
- renderer = {}
- factory = DummyFactory(renderer)
- info = DummyRendererInfo({
- 'name':abspath,
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- 'type':'type',
- })
- lookup = self._makeOne(factory)
- result = lookup(info)
- self.assertEqual(result, renderer)
-
- def test___call__relpath_path_registered(self):
- renderer = {}
- spec = 'foo/bar'
- self._registerTemplateRenderer(renderer, spec)
- info = DummyRendererInfo({
- 'name':spec,
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- 'type':'type',
- })
- lookup = self._makeOne(None)
- result = lookup(info)
- self.assertTrue(renderer is result)
-
- def test___call__relpath_has_package_registered(self):
- renderer = {}
- import pyramid.tests
- spec = 'bar/baz'
- self._registerTemplateRenderer(renderer, 'pyramid.tests:bar/baz')
- info = DummyRendererInfo({
- 'name':spec,
- 'package':pyramid.tests,
- 'registry':self.config.registry,
- 'settings':{},
- 'type':'type',
- })
- lookup = self._makeOne(None)
- result = lookup(info)
- self.assertTrue(renderer is result)
-
- def test___call__spec_notfound(self):
- spec = 'pyramid.tests:wont/exist'
- info = DummyRendererInfo({
- 'name':spec,
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- 'type':'type',
- })
- lookup = self._makeOne(None)
- self.assertRaises(ValueError, lookup.__call__, info)
-
- def test___call__spec_alreadyregistered(self):
- from pyramid import tests
- module_name = tests.__name__
- relpath = 'test_renderers.py'
- spec = '%s:%s' % (module_name, relpath)
- info = DummyRendererInfo({
- 'name':spec,
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- 'type':'type',
- })
- renderer = {}
- self._registerTemplateRenderer(renderer, spec)
- lookup = self._makeOne(None)
- result = lookup(info)
- self.assertTrue(result is renderer)
-
- def test___call__spec_notyetregistered(self):
- import os
- from pyramid import tests
- module_name = tests.__name__
- relpath = 'test_renderers.py'
- renderer = {}
- factory = DummyFactory(renderer)
- spec = '%s:%s' % (module_name, relpath)
- info = DummyRendererInfo({
- 'name':spec,
- 'package':None,
- 'registry':self.config.registry,
- 'settings':{},
- 'type':'type',
- })
- lookup = self._makeOne(factory)
- result = lookup(info)
- self.assertTrue(result is renderer)
- path = os.path.abspath(__file__).split('$')[0] # jython
- if path.endswith('.pyc'): # pragma: no cover
- path = path[:-1]
- self.assertTrue(factory.path.startswith(path))
- self.assertEqual(factory.kw, {'macro':None})
-
- def test___call__spec_withmacro(self):
- from pyramid.interfaces import ITemplateRenderer
- import os
- from pyramid import tests
- module_name = tests.__name__
- relpath = 'fixtures/withmacro#foo.pt'
- renderer = {}
- factory = DummyFactory(renderer)
- spec = '%s:%s' % (module_name, relpath)
- reg = self.config.registry
- info = DummyRendererInfo({
- 'name':spec,
- 'package':None,
- 'registry':reg,
- 'settings':{},
- 'type':'type',
- })
- lookup = self._makeOne(factory)
- result = lookup(info)
- self.assertTrue(result is renderer)
- path = os.path.join(
- os.path.dirname(os.path.abspath(__file__)),
- 'fixtures',
- 'withmacro.pt')
- self.assertTrue(factory.path.startswith(path))
- self.assertEqual(factory.kw, {'macro':'foo'})
- self.assertTrue(
- reg.getUtility(ITemplateRenderer, name=spec) is renderer
- )
-
- def test___call__reload_assets_true(self):
- import pyramid.tests
- from pyramid.interfaces import ISettings
- from pyramid.interfaces import ITemplateRenderer
- settings = {'reload_assets':True}
- self.config.registry.registerUtility(settings, ISettings)
- renderer = {}
- factory = DummyFactory(renderer)
- spec = 'test_renderers.py'
- reg = self.config.registry
- info = DummyRendererInfo({
- 'name':spec,
- 'package':pyramid.tests,
- 'registry':reg,
- 'settings':settings,
- 'type':'type',
- })
- lookup = self._makeOne(factory)
- result = lookup(info)
- self.assertTrue(result is renderer)
- spec = '%s:%s' % ('pyramid.tests', 'test_renderers.py')
- self.assertEqual(reg.queryUtility(ITemplateRenderer, name=spec),
- None)
-
- def test___call__reload_assets_false(self):
- import pyramid.tests
- from pyramid.interfaces import ITemplateRenderer
- settings = {'reload_assets':False}
- renderer = {}
- factory = DummyFactory(renderer)
- spec = 'test_renderers.py'
- reg = self.config.registry
- info = DummyRendererInfo({
- 'name':spec,
- 'package':pyramid.tests,
- 'registry':reg,
- 'settings':settings,
- 'type':'type',
- })
- lookup = self._makeOne(factory)
- result = lookup(info)
- self.assertTrue(result is renderer)
- spec = '%s:%s' % ('pyramid.tests', 'test_renderers.py')
- self.assertNotEqual(reg.queryUtility(ITemplateRenderer, name=spec),
- None)
-
class TestJSON(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
@@ -470,7 +104,7 @@ class Test_string_renderer_factory(unittest.TestCase):
value = text_('La Pe\xc3\xb1a', 'utf-8')
result = renderer(value, {})
self.assertEqual(result, value)
-
+
def test_it_str(self):
renderer = self._callFUT(None)
value = 'La Pe\xc3\xb1a'
@@ -621,16 +255,6 @@ class TestRendererHelper(unittest.TestCase):
self.assertEqual(result[0], 'values')
self.assertEqual(result[1], system)
- def test_render_renderer_globals_factory_active(self):
- self._registerRendererFactory()
- from pyramid.interfaces import IRendererGlobalsFactory
- def rg(system):
- return {'a':1}
- self.config.registry.registerUtility(rg, IRendererGlobalsFactory)
- helper = self._makeOne('loo.foo')
- result = helper.render('values', None)
- self.assertEqual(result[1]['a'], 1)
-
def test__make_response_request_is_None(self):
request = None
helper = self._makeOne('loo.foo')
@@ -680,63 +304,6 @@ class TestRendererHelper(unittest.TestCase):
helper = self._makeOne('loo.foo')
response = helper._make_response(None, request)
self.assertEqual(response.body, b'abc')
-
- def test__make_response_with_content_type(self):
- from pyramid.response import Response
- request = testing.DummyRequest()
- request.response = Response()
- attrs = {'_response_content_type':'text/nonsense'}
- request.__dict__.update(attrs)
- helper = self._makeOne('loo.foo')
- response = helper._make_response('abc', request)
- self.assertEqual(response.content_type, 'text/nonsense')
- self.assertEqual(response.body, b'abc')
-
- def test__make_response_with_headerlist(self):
- from pyramid.response import Response
- request = testing.DummyRequest()
- request.response = Response()
- attrs = {'_response_headerlist':[('a', '1'), ('b', '2')]}
- request.__dict__.update(attrs)
- helper = self._makeOne('loo.foo')
- response = helper._make_response('abc', request)
- self.assertEqual(response.headerlist,
- [('Content-Type', 'text/html; charset=UTF-8'),
- ('Content-Length', '3'),
- ('a', '1'),
- ('b', '2')])
- self.assertEqual(response.body, b'abc')
-
- def test__make_response_with_status(self):
- from pyramid.response import Response
- request = testing.DummyRequest()
- request.response = Response()
- attrs = {'_response_status':'406 You Lose'}
- request.__dict__.update(attrs)
- helper = self._makeOne('loo.foo')
- response = helper._make_response('abc', request)
- self.assertEqual(response.status, '406 You Lose')
- self.assertEqual(response.body, b'abc')
-
- def test__make_response_with_charset(self):
- from pyramid.response import Response
- request = testing.DummyRequest()
- request.response = Response()
- attrs = {'_response_charset':'UTF-16'}
- request.__dict__.update(attrs)
- helper = self._makeOne('loo.foo')
- response = helper._make_response('abc', request)
- self.assertEqual(response.charset, 'UTF-16')
-
- def test__make_response_with_cache_for(self):
- from pyramid.response import Response
- request = testing.DummyRequest()
- request.response = Response()
- attrs = {'_response_cache_for':100}
- request.__dict__.update(attrs)
- helper = self._makeOne('loo.foo')
- response = helper._make_response('abc', request)
- self.assertEqual(response.cache_control.max_age, 100)
def test_with_alternate_response_factory(self):
from pyramid.interfaces import IResponseFactory
@@ -865,19 +432,21 @@ class Test_render(unittest.TestCase):
from pyramid.renderers import render
return render(renderer_name, value, request=request, package=package)
- def test_it_no_request(self):
+ def _registerRenderer(self):
renderer = self.config.testing_add_renderer(
'pyramid.tests:abc/def.pt')
renderer.string_response = 'abc'
+ return renderer
+
+ def test_it_no_request(self):
+ renderer = self._registerRenderer()
result = self._callFUT('abc/def.pt', dict(a=1))
self.assertEqual(result, 'abc')
renderer.assert_(a=1)
renderer.assert_(request=None)
-
+
def test_it_with_request(self):
- renderer = self.config.testing_add_renderer(
- 'pyramid.tests:abc/def.pt')
- renderer.string_response = 'abc'
+ renderer = self._registerRenderer()
request = testing.DummyRequest()
result = self._callFUT('abc/def.pt',
dict(a=1), request=request)
@@ -887,9 +456,7 @@ class Test_render(unittest.TestCase):
def test_it_with_package(self):
import pyramid.tests
- renderer = self.config.testing_add_renderer(
- 'pyramid.tests:abc/def.pt')
- renderer.string_response = 'abc'
+ renderer = self._registerRenderer()
request = testing.DummyRequest()
result = self._callFUT('abc/def.pt', dict(a=1), request=request,
package=pyramid.tests)
@@ -897,6 +464,30 @@ class Test_render(unittest.TestCase):
renderer.assert_(a=1)
renderer.assert_(request=request)
+ def test_response_preserved(self):
+ request = testing.DummyRequest()
+ response = object() # should error if mutated
+ request.response = response
+ # use a json renderer, which will mutate the response
+ result = self._callFUT('json', dict(a=1), request=request)
+ self.assertEqual(result, '{"a": 1}')
+ self.assertEqual(request.response, response)
+
+ def test_no_response_to_preserve(self):
+ from pyramid.decorator import reify
+ class DummyRequestWithClassResponse(object):
+ _response = DummyResponse()
+ _response.content_type = None
+ _response.default_content_type = None
+ @reify
+ def response(self):
+ return self._response
+ request = DummyRequestWithClassResponse()
+ # use a json renderer, which will mutate the response
+ result = self._callFUT('json', dict(a=1), request=request)
+ self.assertEqual(result, '{"a": 1}')
+ self.assertFalse('response' in request.__dict__)
+
class Test_render_to_response(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
@@ -917,7 +508,7 @@ class Test_render_to_response(unittest.TestCase):
self.assertEqual(response.body, b'abc')
renderer.assert_(a=1)
renderer.assert_(request=None)
-
+
def test_it_with_request(self):
renderer = self.config.testing_add_renderer(
'pyramid.tests:abc/def.pt')
@@ -999,17 +590,3 @@ class DummyResponse:
app_iter = ()
body = ''
-class DummyFactory:
- def __init__(self, renderer):
- self.renderer = renderer
-
- def __call__(self, path, lookup, **kw):
- self.path = path
- self.kw = kw
- return self.renderer
-
-
-class DummyRendererInfo(object):
- def __init__(self, kw):
- self.__dict__.update(kw)
-
diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py
index 565c6377e..6cd72fc59 100644
--- a/pyramid/tests/test_request.py
+++ b/pyramid/tests/test_request.py
@@ -308,163 +308,6 @@ class TestRequest(unittest.TestCase):
self.assertEqual(1, request.db)
self.assertEqual(1, request.db)
-class TestRequestDeprecatedMethods(unittest.TestCase):
- def setUp(self):
- self.config = testing.setUp()
- from zope.deprecation import __show__
- __show__.off()
-
- def tearDown(self):
- testing.tearDown()
- from zope.deprecation import __show__
- __show__.on()
-
- def _getTargetClass(self):
- from pyramid.request import Request
- return Request
-
- def _makeOne(self, environ=None):
- if environ is None:
- environ = {}
- return self._getTargetClass()(environ)
-
- def test___contains__(self):
- environ ={'zooma':1}
- inst = self._makeOne(environ)
- self.assertTrue('zooma' in inst)
-
- def test___delitem__(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- del inst['zooma']
- self.assertFalse('zooma' in environ)
-
- def test___getitem__(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- self.assertEqual(inst['zooma'], 1)
-
- def test___iter__(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- iterator = iter(inst)
- self.assertEqual(list(iterator), list(iter(environ)))
-
- def test___setitem__(self):
- environ = {}
- inst = self._makeOne(environ)
- inst['zooma'] = 1
- self.assertEqual(environ, {'zooma':1})
-
- def test_get(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- self.assertEqual(inst.get('zooma'), 1)
-
- def test_has_key(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- self.assertEqual(inst.has_key('zooma'), True)
-
- def test_items(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- self.assertEqual(inst.items(), environ.items())
-
- def test_iteritems(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- self.assertEqual(list(inst.iteritems()), list(iteritems_(environ)))
-
- def test_iterkeys(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- self.assertEqual(list(inst.iterkeys()), list(iterkeys_(environ)))
-
- def test_itervalues(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- self.assertEqual(list(inst.itervalues()), list(itervalues_(environ)))
-
- def test_keys(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- self.assertEqual(inst.keys(), environ.keys())
-
- def test_pop(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- popped = inst.pop('zooma')
- self.assertEqual(environ, {})
- self.assertEqual(popped, 1)
-
- def test_popitem(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- popped = inst.popitem()
- self.assertEqual(environ, {})
- self.assertEqual(popped, ('zooma', 1))
-
- def test_setdefault(self):
- environ = {}
- inst = self._makeOne(environ)
- marker = []
- result = inst.setdefault('a', marker)
- self.assertEqual(environ, {'a':marker})
- self.assertEqual(result, marker)
-
- def test_update(self):
- environ = {}
- inst = self._makeOne(environ)
- inst.update({'a':1}, b=2)
- self.assertEqual(environ, {'a':1, 'b':2})
-
- def test_values(self):
- environ = {'zooma':1}
- inst = self._makeOne(environ)
- result = list(inst.values())
- self.assertEqual(result, list(environ.values()))
-
- def test_response_content_type(self):
- inst = self._makeOne()
- self.assertFalse(hasattr(inst, 'response_content_type'))
- inst.response_content_type = 'abc'
- self.assertEqual(inst.response_content_type, 'abc')
- del inst.response_content_type
- self.assertFalse(hasattr(inst, 'response_content_type'))
-
- def test_response_headerlist(self):
- inst = self._makeOne()
- self.assertFalse(hasattr(inst, 'response_headerlist'))
- inst.response_headerlist = 'abc'
- self.assertEqual(inst.response_headerlist, 'abc')
- del inst.response_headerlist
- self.assertFalse(hasattr(inst, 'response_headerlist'))
-
- def test_response_status(self):
- inst = self._makeOne()
- self.assertFalse(hasattr(inst, 'response_status'))
- inst.response_status = 'abc'
- self.assertEqual(inst.response_status, 'abc')
- del inst.response_status
- self.assertFalse(hasattr(inst, 'response_status'))
-
- def test_response_charset(self):
- inst = self._makeOne()
- self.assertFalse(hasattr(inst, 'response_charset'))
- inst.response_charset = 'abc'
- self.assertEqual(inst.response_charset, 'abc')
- del inst.response_charset
- self.assertFalse(hasattr(inst, 'response_charset'))
-
- def test_response_cache_for(self):
- inst = self._makeOne()
- self.assertFalse(hasattr(inst, 'response_cache_for'))
- inst.response_cache_for = 'abc'
- self.assertEqual(inst.response_cache_for, 'abc')
- del inst.response_cache_for
- self.assertFalse(hasattr(inst, 'response_cache_for'))
-
class Test_route_request_iface(unittest.TestCase):
def _callFUT(self, name):
from pyramid.request import route_request_iface
diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py
index d580203af..366aa00b5 100644
--- a/pyramid/tests/test_scripts/dummy.py
+++ b/pyramid/tests/test_scripts/dummy.py
@@ -146,10 +146,13 @@ class DummyBootstrap(object):
def __call__(self, *a, **kw):
self.a = a
self.kw = kw
+ registry = kw.get('registry', self.registry)
+ request = kw.get('request', self.request)
+ request.registry = registry
return {
'app': self.app,
- 'registry': self.registry,
- 'request': self.request,
+ 'registry': registry,
+ 'request': request,
'root': self.root,
'root_factory': self.root_factory,
'closer': self.closer,
diff --git a/pyramid/tests/test_scripts/test_pcreate.py b/pyramid/tests/test_scripts/test_pcreate.py
index 1406d3911..6516ac229 100644
--- a/pyramid/tests/test_scripts/test_pcreate.py
+++ b/pyramid/tests/test_scripts/test_pcreate.py
@@ -110,6 +110,21 @@ class TestPCreateCommand(unittest.TestCase):
scaffold2.vars,
{'project': 'Distro', 'egg': 'Distro', 'package': 'distro'})
+ def test_known_scaffold_with_path_as_project_target_rendered(self):
+ import os
+ cmd = self._makeOne('-s', 'dummy', '/tmp/foo/Distro/')
+ scaffold = DummyScaffold('dummy')
+ cmd.scaffolds = [scaffold]
+ result = cmd.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(
+ scaffold.output_dir,
+ os.path.normpath(os.path.join(os.getcwd(), '/tmp/foo/Distro'))
+ )
+ self.assertEqual(
+ scaffold.vars,
+ {'project': 'Distro', 'egg': 'Distro', 'package': 'distro'})
+
class Test_main(unittest.TestCase):
def _callFUT(self, argv):
from pyramid.scripts.pcreate import main
diff --git a/pyramid/tests/test_scripts/test_pviews.py b/pyramid/tests/test_scripts/test_pviews.py
index 266d1ec90..b162144a7 100644
--- a/pyramid/tests/test_scripts/test_pviews.py
+++ b/pyramid/tests/test_scripts/test_pviews.py
@@ -12,6 +12,12 @@ class TestPViewsCommand(unittest.TestCase):
cmd.args = ('/foo/bar/myapp.ini#myapp',)
return cmd
+ def _makeRequest(self, url, registry):
+ from pyramid.request import Request
+ request = Request.blank('/a')
+ request.registry = registry
+ return request
+
def _register_mapper(self, registry, routes):
from pyramid.interfaces import IRoutesMapper
mapper = dummy.DummyMapper(*routes)
@@ -22,7 +28,8 @@ class TestPViewsCommand(unittest.TestCase):
registry = Registry()
self._register_mapper(registry, [])
command = self._makeOne(registry)
- result = command._find_view('/a', registry)
+ request = self._makeRequest('/a', registry)
+ result = command._find_view(request)
self.assertEqual(result, None)
def test__find_view_no_match_multiview_registered(self):
@@ -45,7 +52,8 @@ class TestPViewsCommand(unittest.TestCase):
IMultiView)
self._register_mapper(registry, [])
command = self._makeOne(registry=registry)
- result = command._find_view('/x', registry)
+ request = self._makeRequest('/x', registry)
+ result = command._find_view(request)
self.assertEqual(result, None)
def test__find_view_traversal(self):
@@ -65,7 +73,8 @@ class TestPViewsCommand(unittest.TestCase):
IView, name='a')
self._register_mapper(registry, [])
command = self._makeOne(registry=registry)
- result = command._find_view('/a', registry)
+ request = self._makeRequest('/a', registry)
+ result = command._find_view(request)
self.assertEqual(result, view1)
def test__find_view_traversal_multiview(self):
@@ -89,7 +98,8 @@ class TestPViewsCommand(unittest.TestCase):
IMultiView, name='a')
self._register_mapper(registry, [])
command = self._makeOne(registry=registry)
- result = command._find_view('/a', registry)
+ request = self._makeRequest('/a', registry)
+ result = command._find_view(request)
self.assertEqual(result, view)
def test__find_view_route_no_multiview(self):
@@ -117,7 +127,8 @@ class TestPViewsCommand(unittest.TestCase):
dummy.DummyRoute('b', '/b', factory=Factory)]
self._register_mapper(registry, routes)
command = self._makeOne(registry=registry)
- result = command._find_view('/a', registry)
+ request = self._makeRequest('/a', registry)
+ result = command._find_view(request)
self.assertEqual(result, view)
def test__find_view_route_multiview_no_view_registered(self):
@@ -147,7 +158,8 @@ class TestPViewsCommand(unittest.TestCase):
dummy.DummyRoute('b', '/a', matchdict={})]
self._register_mapper(registry, routes)
command = self._makeOne(registry=registry)
- result = command._find_view('/a', registry)
+ request = self._makeRequest('/a', registry)
+ result = command._find_view(request)
self.assertTrue(IMultiView.providedBy(result))
def test__find_view_route_multiview(self):
@@ -185,7 +197,8 @@ class TestPViewsCommand(unittest.TestCase):
dummy.DummyRoute('b', '/a', matchdict={})]
self._register_mapper(registry, routes)
command = self._makeOne(registry=registry)
- result = command._find_view('/a', registry)
+ request = self._makeRequest('/a', registry)
+ result = command._find_view(request)
self.assertTrue(IMultiView.providedBy(result))
self.assertEqual(len(result.views), 2)
self.assertTrue((None, view1, None) in result.views)
@@ -228,7 +241,7 @@ class TestPViewsCommand(unittest.TestCase):
command = self._makeOne(registry=registry)
L = []
command.out = L.append
- command._find_view = lambda arg1, arg2: None
+ command._find_view = lambda arg1: None
command.args = ('/foo/bar/myapp.ini#myapp', '/a')
result = command.run()
self.assertEqual(result, 0)
@@ -241,7 +254,7 @@ class TestPViewsCommand(unittest.TestCase):
command = self._makeOne(registry=registry)
L = []
command.out = L.append
- command._find_view = lambda arg1, arg2: None
+ command._find_view = lambda arg1: None
command.args = ('/foo/bar/myapp.ini#myapp', 'a')
result = command.run()
self.assertEqual(result, 0)
@@ -255,7 +268,7 @@ class TestPViewsCommand(unittest.TestCase):
L = []
command.out = L.append
view = dummy.DummyView(context='context', view_name='a')
- command._find_view = lambda arg1, arg2: view
+ command._find_view = lambda arg1: view
command.args = ('/foo/bar/myapp.ini#myapp', '/a')
result = command.run()
self.assertEqual(result, 0)
@@ -273,7 +286,7 @@ class TestPViewsCommand(unittest.TestCase):
command.out = L.append
def view(): pass
view.__request_attrs__ = {'context': 'context', 'view_name': 'a'}
- command._find_view = lambda arg1, arg2: view
+ command._find_view = lambda arg1: view
command.args = ('/foo/bar/myapp.ini#myapp', '/a')
result = command.run()
self.assertEqual(result, 0)
@@ -291,7 +304,7 @@ class TestPViewsCommand(unittest.TestCase):
command.out = L.append
view = dummy.DummyView(context='context', view_name='a')
view.__permission__ = 'test'
- command._find_view = lambda arg1, arg2: view
+ command._find_view = lambda arg1: view
command.args = ('/foo/bar/myapp.ini#myapp', '/a')
result = command.run()
self.assertEqual(result, 0)
@@ -312,7 +325,7 @@ class TestPViewsCommand(unittest.TestCase):
predicate.text = lambda *arg: "predicate = x"
view = dummy.DummyView(context='context', view_name='a')
view.__predicates__ = [predicate]
- command._find_view = lambda arg1, arg2: view
+ command._find_view = lambda arg1: view
command.args = ('/foo/bar/myapp.ini#myapp', '/a')
result = command.run()
self.assertEqual(result, 0)
@@ -332,7 +345,7 @@ class TestPViewsCommand(unittest.TestCase):
route = dummy.DummyRoute('a', '/a', matchdict={})
view = dummy.DummyView(context='context', view_name='a',
matched_route=route, subpath='')
- command._find_view = lambda arg1, arg2: view
+ command._find_view = lambda arg1: view
command.args = ('/foo/bar/myapp.ini#myapp', '/a')
result = command.run()
self.assertEqual(result, 0)
@@ -360,7 +373,7 @@ class TestPViewsCommand(unittest.TestCase):
view_name='a1')
multiview2 = dummy.DummyMultiView(multiview1, context='context',
view_name='a')
- command._find_view = lambda arg1, arg2: multiview2
+ command._find_view = lambda arg1: multiview2
command.args = ('/foo/bar/myapp.ini#myapp', '/a')
result = command.run()
self.assertEqual(result, 0)
@@ -383,7 +396,7 @@ class TestPViewsCommand(unittest.TestCase):
route = dummy.DummyRoute('a', '/a', matchdict={}, predicate=predicate)
view = dummy.DummyView(context='context', view_name='a',
matched_route=route, subpath='')
- command._find_view = lambda arg1, arg2: view
+ command._find_view = lambda arg1: view
command.args = ('/foo/bar/myapp.ini#myapp', '/a')
result = command.run()
self.assertEqual(result, 0)
@@ -409,7 +422,7 @@ class TestPViewsCommand(unittest.TestCase):
view.__name__ = 'view'
view.__view_attr__ = 'call'
multiview = dummy.DummyMultiView(view, context='context', view_name='a')
- command._find_view = lambda arg1, arg2: multiview
+ command._find_view = lambda arg1: multiview
command.args = ('/foo/bar/myapp.ini#myapp', '/a')
result = command.run()
self.assertEqual(result, 0)
@@ -430,7 +443,7 @@ class TestPViewsCommand(unittest.TestCase):
view.__view_attr__ = 'call'
view.__permission__ = 'test'
multiview = dummy.DummyMultiView(view, context='context', view_name='a')
- command._find_view = lambda arg1, arg2: multiview
+ command._find_view = lambda arg1: multiview
command.args = ('/foo/bar/myapp.ini#myapp', '/a')
result = command.run()
self.assertEqual(result, 0)
@@ -454,7 +467,7 @@ class TestPViewsCommand(unittest.TestCase):
view.__view_attr__ = 'call'
view.__predicates__ = [predicate]
multiview = dummy.DummyMultiView(view, context='context', view_name='a')
- command._find_view = lambda arg1, arg2: multiview
+ command._find_view = lambda arg1: multiview
command.args = ('/foo/bar/myapp.ini#myapp', '/a')
result = command.run()
self.assertEqual(result, 0)
diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py
index ff5937811..0dcc4a027 100644
--- a/pyramid/tests/test_traversal.py
+++ b/pyramid/tests/test_traversal.py
@@ -1278,22 +1278,13 @@ class TestDefaultRootFactory(unittest.TestCase):
def _makeOne(self, environ):
return self._getTargetClass()(environ)
- def test_no_matchdict(self):
- class DummyRequest:
- matchdict = None
+ def test_it(self):
+ class DummyRequest(object):
+ pass
root = self._makeOne(DummyRequest())
self.assertEqual(root.__parent__, None)
self.assertEqual(root.__name__, None)
- def test_matchdict(self):
- class DummyRequest:
- pass
- request = DummyRequest()
- request.matchdict = {'a':1, 'b':2}
- root = self._makeOne(request)
- self.assertEqual(root.a, 1)
- self.assertEqual(root.b, 2)
-
class Test__join_path_tuple(unittest.TestCase):
def _callFUT(self, tup):
from pyramid.traversal import _join_path_tuple
diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py
index a0d476662..309fd47e2 100644
--- a/pyramid/tests/test_view.py
+++ b/pyramid/tests/test_view.py
@@ -301,51 +301,6 @@ class RenderViewTests(BaseTest, unittest.TestCase):
s = self._callFUT(context, request, name='registered', secure=False)
self.assertEqual(s, b'anotherview')
-class TestIsResponse(unittest.TestCase):
- def setUp(self):
- from zope.deprecation import __show__
- __show__.off()
-
- def tearDown(self):
- from zope.deprecation import __show__
- __show__.on()
-
- def _callFUT(self, *arg, **kw):
- from pyramid.view import is_response
- return is_response(*arg, **kw)
-
- def test_is(self):
- response = DummyResponse()
- self.assertEqual(self._callFUT(response), True)
-
- def test_isnt(self):
- response = None
- self.assertEqual(self._callFUT(response), False)
-
- def test_isnt_no_headerlist(self):
- class Response(object):
- pass
- resp = Response
- resp.status = '200 OK'
- resp.app_iter = []
- self.assertEqual(self._callFUT(resp), False)
-
- def test_isnt_no_status(self):
- class Response(object):
- pass
- resp = Response
- resp.app_iter = []
- resp.headerlist = ()
- self.assertEqual(self._callFUT(resp), False)
-
- def test_isnt_no_app_iter(self):
- class Response(object):
- pass
- resp = Response
- resp.status = '200 OK'
- resp.headerlist = ()
- self.assertEqual(self._callFUT(resp), False)
-
class TestViewConfigDecorator(unittest.TestCase):
def setUp(self):
testing.setUp()
@@ -673,24 +628,6 @@ class Test_default_exceptionresponse_view(unittest.TestCase):
result = self._callFUT(context, request)
self.assertEqual(result, 'abc')
-class Test_static(unittest.TestCase):
- def setUp(self):
- from zope.deprecation import __show__
- __show__.off()
-
- def tearDown(self):
- from zope.deprecation import __show__
- __show__.on()
-
- def _makeOne(self, path, package_name):
- from pyramid.view import static
- return static(path, package_name)
-
- def test_it(self):
- path = 'fixtures'
- view = self._makeOne(path, None)
- self.assertEqual(view.docroot, 'fixtures')
-
class Test_view_defaults(unittest.TestCase):
def test_it(self):
from pyramid.view import view_defaults
diff --git a/pyramid/traversal.py b/pyramid/traversal.py
index 341ed2d75..4c275c4c1 100644
--- a/pyramid/traversal.py
+++ b/pyramid/traversal.py
@@ -822,9 +822,4 @@ class DefaultRootFactory:
__parent__ = None
__name__ = None
def __init__(self, request):
- matchdict = request.matchdict
- # provide backwards compatibility for applications which
- # used routes (at least apps without any custom "context
- # factory") in BFG 0.9.X and before
- if matchdict is not None:
- self.__dict__.update(matchdict)
+ pass
diff --git a/pyramid/view.py b/pyramid/view.py
index b64db69d2..55ab38871 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -1,8 +1,6 @@
import venusian
from zope.interface import providedBy
-from zope.deprecation import deprecated
-
from pyramid.interfaces import (
IRoutesMapper,
@@ -20,33 +18,10 @@ from pyramid.httpexceptions import (
default_exceptionresponse_view,
)
-from pyramid.path import caller_package
-from pyramid.static import static_view
from pyramid.threadlocal import get_current_registry
_marker = object()
-class static(static_view):
- """ Backwards compatibility alias for
- :class:`pyramid.static.static_view`; it overrides that class' constructor
- to pass ``use_subpath=True`` by default.
-
- .. deprecated:: 1.1
- use :class:`pyramid.static.static_view` instead
- (probably with a ``use_subpath=True`` argument)
- """
- def __init__(self, root_dir, cache_max_age=3600, package_name=None):
- if package_name is None:
- package_name = caller_package().__name__
- static_view.__init__(self, root_dir, cache_max_age=cache_max_age,
- package_name=package_name, use_subpath=True)
-
-deprecated(
- 'static',
- 'The "pyramid.view.static" class is deprecated as of Pyramid 1.1; '
- 'use the "pyramid.static.static_view" class instead with the '
- '"use_subpath" argument set to True.')
-
def render_view_to_response(context, request, name='', secure=True):
""" Call the :term:`view callable` configured with a :term:`view
configuration` that matches the :term:`view name` ``name``
@@ -191,6 +166,9 @@ class view_config(object):
See :ref:`mapping_views_using_a_decorator_section` for details about
using :class:`pyramid.view.view_config`.
+ ATTENTION: ``view_config`` will work ONLY on module top level members
+ because of the limitation of ``venusian.Scanner.scan``.
+
"""
venusian = venusian # for testing injection
def __init__(self, **settings):
@@ -230,7 +208,7 @@ class view_defaults(view_config):
See :ref:`view_defaults` for more information.
"""
-
+
def __call__(self, wrapped):
wrapped.__view_defaults__ = self.__dict__.copy()
return wrapped
@@ -330,7 +308,7 @@ class notfound_view_config(object):
from pyramid.view import notfound_view_config
from pyramid.response import Response
-
+
@notfound_view_config()
def notfound(request):
return Response('Not found, dude!', status='404 Not Found')
@@ -393,7 +371,7 @@ class forbidden_view_config(object):
from pyramid.view import forbidden_view_config
from pyramid.response import Response
-
+
@forbidden_view_config()
def forbidden(request):
return Response('You are not allowed', status='401 Unauthorized')
@@ -429,19 +407,4 @@ class forbidden_view_config(object):
settings['_info'] = info.codeinfo # fbo "action_method"
return wrapped
-
-def is_response(ob):
- """ Return ``True`` if ``ob`` implements the interface implied by
- :ref:`the_response`. ``False`` if not.
-
- .. deprecated:: 1.1
- use :func:`pyramid.request.Request.is_response` instead"""
- if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and
- hasattr(ob, 'status') ):
- return True
- return False
-
-deprecated(
- 'is_response',
- 'pyramid.view.is_response is deprecated as of Pyramid 1.1. Use '
- 'pyramid.request.Request.is_response instead.')
+
diff --git a/rtd.txt b/rtd.txt
index 9de7ff3bb..b449ac73c 100644
--- a/rtd.txt
+++ b/rtd.txt
@@ -1,3 +1,4 @@
repoze.sphinx.autointerface
repoze.lru
+pylons_sphinx_latesturl
diff --git a/setup.py b/setup.py
index 6c6f1723f..2d49717b7 100644
--- a/setup.py
+++ b/setup.py
@@ -39,8 +39,6 @@ except IOError:
install_requires=[
'setuptools',
- 'Chameleon >= 1.2.3',
- 'Mako >= 0.3.6', # strict_undefined
'WebOb >= 1.2b3', # request.path_info is unicode
'repoze.lru >= 0.4', # py3 compat
'zope.interface >= 3.8.0', # has zope.interface.registry
@@ -71,9 +69,8 @@ testing_extras = tests_require + [
]
setup(name='pyramid',
- version='1.5a1',
- description=('The Pyramid Web Framework, a '
- 'Pylons project'),
+ version='1.5a2',
+ description='The Pyramid Web Framework, a Pylons project',
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Intended Audience :: Developers",
@@ -111,7 +108,6 @@ setup(name='pyramid',
zodb=pyramid.scaffolds:ZODBProjectTemplate
alchemy=pyramid.scaffolds:AlchemyProjectTemplate
[console_scripts]
- bfg2pyramid = pyramid.fixers.fix_bfg_imports:main
pcreate = pyramid.scripts.pcreate:main
pserve = pyramid.scripts.pserve:main
pshell = pyramid.scripts.pshell:main