summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml6
-rw-r--r--HACKING.txt13
-rw-r--r--RELEASING.txt5
-rw-r--r--appveyor.yml2
-rw-r--r--docs/api/compat.rst156
-rw-r--r--docs/conf.py20
-rw-r--r--docs/glossary.rst24
-rw-r--r--docs/narr/commandline.rst8
-rw-r--r--docs/narr/hooks.rst8
-rw-r--r--docs/narr/i18n.rst17
-rw-r--r--docs/narr/install.rst3
-rw-r--r--docs/narr/myproject/setup.py4
-rw-r--r--docs/narr/renderers.rst5
-rw-r--r--docs/narr/traversal.rst8
-rw-r--r--docs/narr/upgrading.rst1
-rw-r--r--docs/narr/urldispatch.rst38
-rw-r--r--docs/narr/views.rst39
-rw-r--r--docs/narr/webob.rst21
-rw-r--r--docs/quick_tour.rst15
-rw-r--r--docs/quick_tour/logging/setup.py4
-rw-r--r--docs/quick_tour/package/setup.py4
-rw-r--r--docs/quick_tour/sessions/setup.py4
-rw-r--r--docs/quick_tour/sqla_demo/setup.py4
-rw-r--r--docs/quick_tour/views/views.py4
-rw-r--r--docs/quick_tutorial/cookiecutters/setup.py4
-rw-r--r--docs/quick_tutorial/index.rst3
-rw-r--r--docs/quick_tutorial/requirements.rst12
-rw-r--r--docs/tutorials/wiki/authorization.rst460
-rw-r--r--docs/tutorials/wiki/background.rst5
-rw-r--r--docs/tutorials/wiki/basiclayout.rst355
-rw-r--r--docs/tutorials/wiki/definingmodels.rst151
-rw-r--r--docs/tutorials/wiki/definingviews.rst477
-rw-r--r--docs/tutorials/wiki/design.rst96
-rw-r--r--docs/tutorials/wiki/distributing.rst18
-rw-r--r--docs/tutorials/wiki/installation.rst181
-rw-r--r--docs/tutorials/wiki/src/authorization/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/authorization/pytest.ini2
-rw-r--r--docs/tutorials/wiki/src/authorization/setup.py8
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/models/__init__.py (renamed from docs/tutorials/wiki/src/authorization/tutorial/models.py)0
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/pshell.py1
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/routes.py2
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/theme.css3
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt10
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt83
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt59
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt57
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt81
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/tests.py3
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views/default.py (renamed from docs/tutorials/wiki/src/tests/tutorial/views.py)49
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py12
-rw-r--r--docs/tutorials/wiki/src/basiclayout/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/pytest.ini2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/setup.py8
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/models/__init__.py (renamed from docs/tutorials/wiki/src/basiclayout/tutorial/models.py)0
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py1
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/routes.py2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css3
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt10
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt62
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt70
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/tests.py3
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/views.py7
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py8
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki/src/installation/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/installation/pytest.ini2
-rw-r--r--docs/tutorials/wiki/src/installation/setup.py8
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/models/__init__.py (renamed from docs/tutorials/wiki/src/installation/tutorial/models.py)0
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/pshell.py1
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/routes.py2
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/static/theme.css3
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/templates/404.pt10
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt62
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt70
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/tests.py3
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/views.py7
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/views/default.py8
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki/src/models/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/models/pytest.ini2
-rw-r--r--docs/tutorials/wiki/src/models/setup.py8
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/models/__init__.py (renamed from docs/tutorials/wiki/src/models/tutorial/models.py)0
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/pshell.py1
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/routes.py2
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/theme.css3
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/templates/404.pt10
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/templates/layout.pt62
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt70
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/tests.py3
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/views.py7
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/views/default.py8
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki/src/tests/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/tests/pytest.ini2
-rw-r--r--docs/tutorials/wiki/src/tests/setup.py8
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/models/__init__.py (renamed from docs/tutorials/wiki/src/tests/tutorial/models.py)0
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/pshell.py1
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/routes.py2
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/static/theme.css3
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/404.pt10
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt83
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt59
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/login.pt57
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/view.pt81
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/tests.py14
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views/default.py (renamed from docs/tutorials/wiki/src/authorization/tutorial/views.py)49
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views/notfound.py12
-rw-r--r--docs/tutorials/wiki/src/views/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/views/pytest.ini2
-rw-r--r--docs/tutorials/wiki/src/views/setup.py8
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/models/__init__.py (renamed from docs/tutorials/wiki/src/views/tutorial/models.py)0
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/pshell.py1
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/routes.py2
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/theme.css3
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/404.pt10
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/edit.pt79
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/layout.pt59
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/view.pt78
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/tests.py3
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views/default.py (renamed from docs/tutorials/wiki/src/views/tutorial/views.py)26
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views/notfound.py12
-rw-r--r--docs/tutorials/wiki/tests.rst55
-rw-r--r--docs/tutorials/wiki2/background.rst2
-rw-r--r--docs/tutorials/wiki2/installation.rst10
-rw-r--r--docs/tutorials/wiki2/src/authentication/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/views/default.py2
-rw-r--r--docs/tutorials/wiki2/src/authorization/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views/default.py2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/installation/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/models/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/tests/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/default.py2
-rw-r--r--docs/tutorials/wiki2/src/views/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views/default.py2
-rw-r--r--pyproject.toml3
-rw-r--r--setup.py13
-rw-r--r--src/pyramid/asset.py4
-rw-r--r--src/pyramid/authentication.py40
-rw-r--r--src/pyramid/authorization.py4
-rw-r--r--src/pyramid/compat.py326
-rw-r--r--src/pyramid/config/__init__.py16
-rw-r--r--src/pyramid/config/actions.py2
-rw-r--r--src/pyramid/config/predicates.py2
-rw-r--r--src/pyramid/config/routes.py4
-rw-r--r--src/pyramid/config/testing.py4
-rw-r--r--src/pyramid/config/tweens.py10
-rw-r--r--src/pyramid/config/views.py34
-rw-r--r--src/pyramid/csrf.py15
-rw-r--r--src/pyramid/encode.py23
-rw-r--r--src/pyramid/httpexceptions.py13
-rw-r--r--src/pyramid/i18n.py30
-rw-r--r--src/pyramid/interfaces.py15
-rw-r--r--src/pyramid/path.py8
-rw-r--r--src/pyramid/predicates.py4
-rw-r--r--src/pyramid/registry.py5
-rw-r--r--src/pyramid/renderers.py6
-rw-r--r--src/pyramid/request.py13
-rw-r--r--src/pyramid/response.py8
-rw-r--r--src/pyramid/scripts/prequest.py4
-rw-r--r--src/pyramid/scripts/proutes.py3
-rw-r--r--src/pyramid/scripts/pserve.py23
-rw-r--r--src/pyramid/scripts/pshell.py3
-rw-r--r--src/pyramid/scripts/pviews.py2
-rw-r--r--src/pyramid/security.py7
-rw-r--r--src/pyramid/session.py12
-rw-r--r--src/pyramid/settings.py4
-rw-r--r--src/pyramid/static.py9
-rw-r--r--src/pyramid/testing.py8
-rw-r--r--src/pyramid/traversal.py295
-rw-r--r--src/pyramid/tweens.py2
-rw-r--r--src/pyramid/url.py5
-rw-r--r--src/pyramid/urldispatch.py54
-rw-r--r--src/pyramid/util.py180
-rw-r--r--src/pyramid/view.py6
-rw-r--r--src/pyramid/viewderivers.py9
-rw-r--r--tests/pkgs/forbiddenapp/__init__.py2
-rw-r--r--tests/pkgs/permbugapp/__init__.py2
-rw-r--r--tests/test_authentication.py17
-rw-r--r--tests/test_compat.py32
-rw-r--r--tests/test_config/test_adapters.py6
-rw-r--r--tests/test_config/test_factories.py9
-rw-r--r--tests/test_config/test_init.py9
-rw-r--r--tests/test_config/test_predicates.py2
-rw-r--r--tests/test_config/test_routes.py2
-rw-r--r--tests/test_config/test_testing.py16
-rw-r--r--tests/test_config/test_views.py33
-rw-r--r--tests/test_encode.py4
-rw-r--r--tests/test_httpexceptions.py10
-rw-r--r--tests/test_integration.py15
-rw-r--r--tests/test_path.py6
-rw-r--r--tests/test_predicates.py2
-rw-r--r--tests/test_renderers.py6
-rw-r--r--tests/test_request.py9
-rw-r--r--tests/test_response.py2
-rw-r--r--tests/test_scripts/dummy.py5
-rw-r--r--tests/test_scripts/test_prequest.py21
-rw-r--r--tests/test_scripts/test_pserve.py5
-rw-r--r--tests/test_scripts/test_pviews.py18
-rw-r--r--tests/test_session.py10
-rw-r--r--tests/test_traversal.py42
-rw-r--r--tests/test_url.py3
-rw-r--r--tests/test_urldispatch.py66
-rw-r--r--tests/test_util.py149
-rw-r--r--tests/test_view.py3
-rw-r--r--tox.ini49
218 files changed, 2506 insertions, 3192 deletions
diff --git a/.travis.yml b/.travis.yml
index 6efbee21b..c4860d2de 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,20 +4,16 @@ sudo: false
matrix:
include:
- - python: 2.7
- env: TOXENV=py27
- python: 3.4
env: TOXENV=py34
- python: 3.5
env: TOXENV=py35
- python: 3.6
env: TOXENV=py36
- - python: pypy
- env: TOXENV=pypy
- python: pypy3
env: TOXENV=pypy3
- python: 3.6
- env: TOXENV=py2-cover,py3-cover,coverage
+ env: TOXENV=py36-cover,coverage
- python: 3.5
env: TOXENV=docs
- python: 3.6
diff --git a/HACKING.txt b/HACKING.txt
index acd65e4fd..901eb8518 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -34,10 +34,10 @@ In order to add a feature to Pyramid:
- The feature must be documented in both the API and narrative documentation
(in `docs/`).
-- The feature must work fully on the following CPython versions: 2.7, 3.4, 3.5,
- 3.6, and 3.7 on both UNIX and Windows.
+- The feature must work fully on the following CPython versions: 3.4, 3.5, 3.6,
+ and 3.7 on both UNIX and Windows.
-- The feature must work on the latest version of PyPy.
+- The feature must work on the latest version of PyPy3.
- The feature must not depend on any particular persistence layer (filesystem,
SQL, etc).
@@ -65,10 +65,9 @@ Running Tests
$ tox -e py37
- This command will run tests on the latest versions of Python 2 and 3 with
- coverage totaled for both versions.
+ This command will run tests on the latest version of Python 3 with coverage.
- $ tox -e py2-cover,py3-cover,coverage
+ $ tox -e py3-cover,coverage
- To run individual tests (i.e., during development), you can use `nosetests`
syntax as follows, where `$VENV` is an environment variable set to the path
@@ -105,7 +104,7 @@ Test Coverage
-------------
- The codebase *must* have 100% test statement coverage after each commit. You
- can test coverage via `tox -epy2-cover,py3-cover,coverage`.
+ can test coverage via `tox -epy3-cover,coverage`.
Documentation Coverage and Building HTML Documentation
diff --git a/RELEASING.txt b/RELEASING.txt
index 94726f9f3..73e79d057 100644
--- a/RELEASING.txt
+++ b/RELEASING.txt
@@ -33,11 +33,6 @@ Prepare new release branch
- Run tests on Windows if feasible.
-- Make sure all scaffold tests pass (CPython 2.7, 3.4, 3.5, 3.6, and 3.7, and
- PyPy on UNIX; this doesn't work on Windows):
-
- $ tox -e{py27,py34,py35,py36,py37,pypy}-scaffolds
-
- For each ``pyramid-cookiecutter-*``, make a new branch off "master" with the
same name to align with the new Pyramid release branch name.
diff --git a/appveyor.yml b/appveyor.yml
index 8c9d158e1..a9bcd40f1 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -8,8 +8,6 @@ environment:
TOXENV: "py35"
- PYTHON: "C:\\Python34"
TOXENV: "py34"
- - PYTHON: "C:\\Python27"
- TOXENV: "py27"
cache:
- '%LOCALAPPDATA%\pip\Cache'
diff --git a/docs/api/compat.rst b/docs/api/compat.rst
deleted file mode 100644
index bb34f38e4..000000000
--- a/docs/api/compat.rst
+++ /dev/null
@@ -1,156 +0,0 @@
-.. _compat_module:
-
-:mod:`pyramid.compat`
-----------------------
-
-The ``pyramid.compat`` module provides platform and version compatibility for
-Pyramid and its add-ons across Python platform and version differences. APIs
-will be removed from this module over time as Pyramid ceases to support
-systems which require compatibility imports.
-
-.. automodule:: pyramid.compat
-
- .. autofunction:: ascii_native_
-
- .. attribute:: binary_type
-
- Binary type for this platform. For Python 3, it's ``bytes``. For
- Python 2, it's ``str``.
-
- .. autofunction:: bytes_
-
- .. attribute:: class_types
-
- Sequence of class types for this platform. For Python 3, it's
- ``(type,)``. For Python 2, it's ``(type, types.ClassType)``.
-
- .. attribute:: configparser
-
- On Python 2, the ``ConfigParser`` module, on Python 3, the
- ``configparser`` module.
-
- .. function:: escape(v)
-
- On Python 2, the ``cgi.escape`` function, on Python 3, the
- ``html.escape`` function.
-
- .. function:: exec_(code, globs=None, locs=None)
-
- Exec code in a compatible way on both Python 2 and 3.
-
- .. attribute:: im_func
-
- On Python 2, the string value ``im_func``, on Python 3, the string
- value ``__func__``.
-
- .. function:: input_(v)
-
- On Python 2, the ``raw_input`` function, on Python 3, the
- ``input`` function.
-
- .. attribute:: integer_types
-
- Sequence of integer types for this platform. For Python 3, it's
- ``(int,)``. For Python 2, it's ``(int, long)``.
-
- .. function:: is_nonstr_iter(v)
-
- Return ``True`` if ``v`` is a non-``str`` iterable on both Python 2 and
- Python 3.
-
- .. function:: iteritems_(d)
-
- Return ``d.items()`` on Python 3, ``d.iteritems()`` on Python 2.
-
- .. function:: itervalues_(d)
-
- Return ``d.values()`` on Python 3, ``d.itervalues()`` on Python 2.
-
- .. function:: iterkeys_(d)
-
- Return ``d.keys()`` on Python 3, ``d.iterkeys()`` on Python 2.
-
- .. attribute:: long
-
- Long type for this platform. For Python 3, it's ``int``. For
- Python 2, it's ``long``.
-
- .. function:: map_(v)
-
- Return ``list(map(v))`` on Python 3, ``map(v)`` on Python 2.
-
- .. attribute:: pickle
-
- ``cPickle`` module if it exists, ``pickle`` module otherwise.
-
- .. attribute:: PY3
-
- ``True`` if running on Python 3, ``False`` otherwise.
-
- .. attribute:: PYPY
-
- ``True`` if running on PyPy, ``False`` otherwise.
-
- .. function:: reraise(tp, value, tb=None)
-
- Reraise an exception in a compatible way on both Python 2 and Python 3,
- e.g. ``reraise(*sys.exc_info())``.
-
- .. attribute:: string_types
-
- Sequence of string types for this platform. For Python 3, it's
- ``(str,)``. For Python 2, it's ``(basestring,)``.
-
- .. attribute:: SimpleCookie
-
- On Python 2, the ``Cookie.SimpleCookie`` class, on Python 3, the
- ``http.cookies.SimpleCookie`` module.
-
- .. autofunction:: text_
-
- .. attribute:: text_type
-
- Text type for this platform. For Python 3, it's ``str``. For Python
- 2, it's ``unicode``.
-
- .. autofunction:: native_
-
- .. attribute:: urlparse
-
- ``urlparse`` module on Python 2, ``urllib.parse`` module on Python 3.
-
- .. attribute:: url_quote
-
- ``urllib.quote`` function on Python 2, ``urllib.parse.quote`` function
- on Python 3.
-
- .. attribute:: url_quote_plus
-
- ``urllib.quote_plus`` function on Python 2, ``urllib.parse.quote_plus``
- function on Python 3.
-
- .. attribute:: url_unquote
-
- ``urllib.unquote`` function on Python 2, ``urllib.parse.unquote``
- function on Python 3.
-
- .. attribute:: url_encode
-
- ``urllib.urlencode`` function on Python 2, ``urllib.parse.urlencode``
- function on Python 3.
-
- .. attribute:: url_open
-
- ``urllib2.urlopen`` function on Python 2, ``urllib.request.urlopen``
- function on Python 3.
-
- .. function:: url_unquote_text(v, encoding='utf-8', errors='replace')
-
- On Python 2, return ``url_unquote(v).decode(encoding(encoding, errors))``;
- on Python 3, return the result of ``urllib.parse.unquote``.
-
- .. function:: url_unquote_native(v, encoding='utf-8', errors='replace')
-
- On Python 2, return ``native_(url_unquote_text_v, encoding, errors))``;
- on Python 3, return the result of ``urllib.parse.unquote``.
-
diff --git a/docs/conf.py b/docs/conf.py
index 15cffb1eb..3b7eb261a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -61,26 +61,26 @@ extensions = [
# Looks for objects in external projects
intersphinx_mapping = {
- 'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest', None),
+ 'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest/', None),
'cookbook': ('https://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/', None),
'cookiecutter': ('https://cookiecutter.readthedocs.io/en/latest/', None),
- 'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest', None),
+ 'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest/', None),
'jinja2': ('https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None),
'plaster': ('https://docs.pylonsproject.org/projects/plaster/en/latest/', None),
'pylonswebframework': ('https://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None),
- 'python': ('https://docs.python.org/3', None),
+ 'python': ('https://docs.python.org/3/', None),
'pytest': ('https://docs.pytest.org/en/latest/', None),
- 'sphinx': ('http://www.sphinx-doc.org/en/latest', None),
- 'sqla': ('https://docs.sqlalchemy.org/en/latest', None),
+ 'sphinx': ('http://www.sphinx-doc.org/en/latest/', None),
+ 'sqla': ('https://docs.sqlalchemy.org/en/latest/', None),
'tm': ('https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None),
- 'toolbar': ('https://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest', None),
- 'tstring': ('https://docs.pylonsproject.org/projects/translationstring/en/latest', None),
+ 'toolbar': ('https://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest/', None),
+ 'tstring': ('https://docs.pylonsproject.org/projects/translationstring/en/latest/', None),
'tutorials': ('https://docs.pylonsproject.org/projects/pyramid-tutorials/en/latest/', None),
- 'venusian': ('https://docs.pylonsproject.org/projects/venusian/en/latest', None),
+ 'venusian': ('https://docs.pylonsproject.org/projects/venusian/en/latest/', None),
'webob': ('https://docs.pylonsproject.org/projects/webob/en/latest/', None),
'webtest': ('https://docs.pylonsproject.org/projects/webtest/en/latest/', None),
- 'who': ('https://repozewho.readthedocs.io/en/latest', None),
- 'zcml': ('https://docs.pylonsproject.org/projects/pyramid-zcml/en/latest', None),
+ 'who': ('https://repozewho.readthedocs.io/en/latest/', None),
+ 'zcml': ('https://docs.pylonsproject.org/projects/pyramid-zcml/en/latest/', None),
'zcomponent': ('https://zopecomponent.readthedocs.io/en/latest/', None),
'zinterface': ('https://zopeinterface.readthedocs.io/en/latest/', None),
}
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 89c03860b..9c6027209 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -165,7 +165,7 @@ Glossary
An object representing a node in the :term:`resource tree` of an
application. If :term:`traversal` is used, a resource is an element in
the resource tree traversed by the system. When traversal is used, a
- resource becomes the :term:`context` of a :term:`view`. If :term:`url
+ resource becomes the :term:`context` of a :term:`view`. If :term:`URL
dispatch` is used, a single resource is generated for each request and
is used as the context resource of a view.
@@ -232,7 +232,7 @@ Glossary
object *location-aware*.
permission
- A string or Unicode object that represents an action being taken against
+ A string that represents an action being taken against
a :term:`context` resource. A permission is associated with a view name
and a resource type by the developer. Resources are decorated with
security declarations (e.g. an :term:`ACL`), which reference these
@@ -289,7 +289,7 @@ Glossary
:term:`authorization policy`.
principal
- A *principal* is a string or Unicode object representing an entity,
+ A *principal* is a string representing an entity,
typically a user or group. Principals are provided by an
:term:`authentication policy`. For example, if a user has the
:term:`userid` `bob`, and is a member of two groups named `group foo` and
@@ -298,7 +298,7 @@ Glossary
foo` and `group bar`.
userid
- A *userid* is a string or Unicode object used to identify and authenticate
+ A *userid* is a string used to identify and authenticate
a real-world user or client. A userid is supplied to an
:term:`authentication policy` in order to discover the user's
:term:`principals <principal>`. In the authentication policies which
@@ -405,13 +405,13 @@ Glossary
the Routes syntax (which was inspired by Ruby On Rails pattern syntax).
route
- A single pattern matched by the :term:`url dispatch` subsystem,
+ A single pattern matched by the :term:`URL dispatch` subsystem,
which generally resolves to a :term:`root factory` (and then
ultimately a :term:`view`).
.. seealso::
- See also :term:`url dispatch`.
+ See also :term:`URL dispatch`.
route configuration
Route configuration is the act of associating request parameters with a
@@ -523,8 +523,8 @@ Glossary
from the :term:`physical root`. For example, the physical path of the
``abc`` subobject of the physical root object is ``/abc``. Physical paths
can also be specified as tuples where the first element is the empty
- string (representing the root), and every other element is a Unicode
- object, e.g. ``('', 'abc')``. Physical paths are also sometimes called
+ string (representing the root), and every other element is a Unicode string,
+ e.g. ``('', 'abc')``. Physical paths are also sometimes called
"traversal paths".
lineage
@@ -755,7 +755,7 @@ Glossary
Translation String
An instance of :class:`pyramid.i18n.TranslationString`, which
- is a class that behaves like a Unicode string, but has several
+ is a class that behaves like a string, but has several
extra attributes such as ``domain``, ``msgid``, and ``mapping``
for use during translation. Translation strings are usually
created by hand within software, but are sometimes created on the
@@ -779,7 +779,7 @@ Glossary
Translator
A callable which receives a :term:`translation string` and returns a
- translated Unicode object for the purposes of internationalization. A
+ translated string for the purposes of internationalization. A
:term:`localizer` supplies a translator to a :app:`Pyramid` application
accessible via its :class:`~pyramid.i18n.Localizer.translate` method.
@@ -912,7 +912,7 @@ Glossary
:meth:`pyramid.config.Configurator.add_route` and
:meth:`pyramid.config.Configurator.add_view` to make it more convenient
to register a collection of views as a single class when using
- :term:`url dispatch`. View handlers ship as part of the
+ :term:`URL dispatch`. View handlers ship as part of the
:term:`pyramid_handlers` add-on package.
Deployment settings
@@ -1071,7 +1071,7 @@ Glossary
Green Unicorn
Aka ``gunicorn``, a fast :term:`WSGI` server that runs on Unix under
- Python 2.6+ or Python 3.1+. See https://gunicorn.org/ for detailed
+ Python 2.6+ or Python 3.4+. See https://gunicorn.org/ for detailed
information.
predicate factory
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index b571a7d7b..21b2a0839 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -1006,8 +1006,8 @@ top-level directory, your ``setup.py`` file will look something like this:
requires = ['pyramid', 'pyramid_debugtoolbar']
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
@@ -1073,8 +1073,8 @@ The result will be something like:
requires = ['pyramid', 'pyramid_debugtoolbar']
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 9f405c336..0dac8d426 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -659,15 +659,15 @@ that implements the following interface:
``virtual_root``, and ``virtual_root_path``. These values are
typically the result of a resource tree traversal. ``root``
is the physical root object, ``context`` will be a resource
- object, ``view_name`` will be the view name used (a Unicode
- name), ``subpath`` will be a sequence of Unicode names that
+ object, ``view_name`` will be the view name used (a string),
+ ``subpath`` will be a sequence of strings that
followed the view name but were not traversed, ``traversed``
- will be a sequence of Unicode names that were traversed
+ will be a sequence of strings that were traversed
(including the virtual root path, if any) ``virtual_root``
will be a resource object representing the virtual root (or the
physical root if traversal was not performed), and
``virtual_root_path`` will be a sequence representing the
- virtual root path (a sequence of Unicode names) or None if
+ virtual root path (a sequence of strings) or ``None`` if
traversal was not performed.
Extra keys for special purpose functionality can be added as
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
index 9b838c7f4..b8cd396c0 100644
--- a/docs/narr/i18n.rst
+++ b/docs/narr/i18n.rst
@@ -33,7 +33,7 @@ While you write your software, you can insert specialized markup into your
Python code that makes it possible for the system to translate text values into
the languages used by your application's users. This markup creates a
:term:`translation string`. A translation string is an object that behaves
-mostly like a normal Unicode object, except that it also carries around extra
+mostly like a normal Unicode string, except that it also carries around extra
information related to its job as part of the :app:`Pyramid` translation
machinery.
@@ -49,7 +49,7 @@ The most primitive way to create a translation string is to use the
from pyramid.i18n import TranslationString
ts = TranslationString('Add')
-This creates a Unicode-like object that is a TranslationString.
+This creates a ``str``-like object that is a TranslationString.
.. note::
@@ -61,9 +61,8 @@ This creates a Unicode-like object that is a TranslationString.
The first argument to :class:`~pyramid.i18n.TranslationString` is the
``msgid``; it is required. It represents the key into the translation mappings
-provided by a particular localization. The ``msgid`` argument must be a Unicode
-object or an ASCII string. The msgid may optionally contain *replacement
-markers*. For instance:
+provided by a particular localization. The ``msgid`` argument must be a string.
+The ``msgid`` may optionally contain *replacement markers*. For instance:
.. code-block:: python
:linenos:
@@ -81,14 +80,14 @@ may be supplied at the same time as the replacement marker itself:
from pyramid.i18n import TranslationString
ts = TranslationString('Add ${number}', mapping={'number':1})
-Any number of replacement markers can be present in the msgid value, any number
+Any number of replacement markers can be present in the ``msgid`` value, any number
of times. Only markers which can be replaced by the values in the *mapping*
will be replaced at translation time. The others will not be interpolated and
will be output literally.
A translation string should also usually carry a *domain*. The domain
represents a translation category to disambiguate it from other translations of
-the same msgid, in case they conflict.
+the same ``msgid``, in case they conflict.
.. code-block:: python
:linenos:
@@ -100,7 +99,7 @@ the same msgid, in case they conflict.
The above translation string named a domain of ``form``. A :term:`translator`
function will often use the domain to locate the right translator file on the
filesystem which contains translations for a given domain. In this case, if it
-were trying to translate our msgid to German, it might try to find a
+were trying to translate our ``msgid`` to German, it might try to find a
translation from a :term:`gettext` file within a :term:`translation directory`
like this one:
@@ -429,7 +428,7 @@ Performing a Translation
A :term:`localizer` has a ``translate`` method which accepts either a
:term:`translation string` or a Unicode string and which returns a Unicode
-object representing the translation. Generating a translation in a view
+string representing the translation. Generating a translation in a view
component of an application might look like so:
.. code-block:: python
diff --git a/docs/narr/install.rst b/docs/narr/install.rst
index 248b432d3..268ae5f8d 100644
--- a/docs/narr/install.rst
+++ b/docs/narr/install.rst
@@ -21,8 +21,7 @@ the following sections.
.. sidebar:: Python Versions
- As of this writing, :app:`Pyramid` is tested against Python 2.7,
- Python 3.4, Python 3.5, Python 3.6, Python 3.7, and PyPy.
+ As of this writing, :app:`Pyramid` is tested against Python 3.4, Python 3.5, Python 3.6, Python 3.7, Python 3.8 (with allowed failures), and PyPy3.
:app:`Pyramid` is known to run on all popular Unix-like systems such as Linux,
macOS, and FreeBSD, as well as on Windows platforms. It is also known to
diff --git a/docs/narr/myproject/setup.py b/docs/narr/myproject/setup.py
index cf626880f..1ee272270 100644
--- a/docs/narr/myproject/setup.py
+++ b/docs/narr/myproject/setup.py
@@ -17,8 +17,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst
index 493f808d5..6b4982e4b 100644
--- a/docs/narr/renderers.rst
+++ b/docs/narr/renderers.rst
@@ -145,8 +145,7 @@ used in the ``renderer`` attribute of view configurations.
The ``string`` renderer renders a view callable result to a string. If a view
callable returns a non-Response object, and the ``string`` renderer is
associated in that view's configuration, the result will be to run the object
-through the Python ``str`` function to generate a string. Note that if a
-Unicode object is returned by the view callable, it is not ``str()``-ified.
+through the Python ``str`` function to generate a string.
Here's an example of a view that returns a dictionary. If the ``string``
renderer is specified in the configuration for this view, the view will render
@@ -496,7 +495,7 @@ interface. A typical class that follows this setup is as follows:
def __call__(self, value, system):
""" Call the renderer implementation with the value
and the system value passed in as arguments and return
- the result (a string or unicode object). The value is
+ the result (a bytes or string object). The value is
the return value of a view. The system value is a
dictionary containing available system values
(e.g., view, context, and request). """
diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst
index 9b91e21ba..0282c6096 100644
--- a/docs/narr/traversal.rst
+++ b/docs/narr/traversal.rst
@@ -237,19 +237,19 @@ uses this algorithm to find a :term:`context` resource and a :term:`view name`.
The traversal algorithm by default attempts to first URL-unquote and then
Unicode-decode each path segment derived from ``PATH_INFO`` from its
- natural byte string (``str`` type) representation. URL unquoting is
+ natural string representation. URL unquoting is
performed using the Python standard library ``urllib.unquote`` function.
Conversion from a URL-decoded string into Unicode is attempted using the
UTF-8 encoding. If any URL-unquoted path segment in ``PATH_INFO`` is not
decodeable using the UTF-8 decoding, a :exc:`TypeError` is raised. A
- segment will be fully URL-unquoted and UTF8-decoded before it is passed in
+ segment will be fully URL-unquoted and UTF-8-decoded before it is passed in
to the ``__getitem__`` of any resource during traversal.
Thus a request with a ``PATH_INFO`` variable of ``/a/b/c`` maps to the
- traversal sequence ``[u'a', u'b', u'c']``.
+ traversal sequence ``['a', 'b', 'c']``.
#. :term:`Traversal` begins at the root resource returned by the root factory.
- For the traversal sequence ``[u'a', u'b', u'c']``, the root resource's
+ For the traversal sequence ``['a', 'b', 'c']``, the root resource's
``__getitem__`` is called with the name ``'a'``. Traversal continues
through the sequence. In our example, if the root resource's
``__getitem__`` called with the name ``a`` returns a resource (a.k.a.
diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst
index 12e146cf1..87e4647c3 100644
--- a/docs/narr/upgrading.rst
+++ b/docs/narr/upgrading.rst
@@ -86,7 +86,6 @@ At the time of a Pyramid version release, each supports all versions of Python
through the end of their lifespans. The end-of-life for a given version of
Python is when security updates are no longer released.
-- `Python 2.7 Lifespan <https://devguide.python.org/#status-of-python-branches>`_ 2020-01-01.
- `Python 3.4 Lifespan <https://devguide.python.org/#status-of-python-branches>`_ 2019-03-16 .
- `Python 3.5 Lifespan <https://devguide.python.org/#status-of-python-branches>`_ 2020-09-13 .
- `Python 3.6 Lifespan <https://devguide.python.org/#status-of-python-branches>`_ 2021-12-23.
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index 3b737b46d..9372163e8 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -165,8 +165,8 @@ The above pattern will match these URLs, generating the following matchdicts:
.. code-block:: text
- foo/1/2 -> {'baz':u'1', 'bar':u'2'}
- foo/abc/def -> {'baz':u'abc', 'bar':u'def'}
+ foo/1/2 -> {'baz': '1', 'bar': '2'}
+ foo/abc/def -> {'baz': 'abc', 'bar': 'def'}
It will not match the following patterns however:
@@ -184,7 +184,7 @@ instance, if this route pattern was used:
foo/{name}.html
The literal path ``/foo/biz.html`` will match the above route pattern, and the
-match result will be ``{'name':u'biz'}``. However, the literal path
+match result will be ``{'name': 'biz'}``. However, the literal path
``/foo/biz`` will not match, because it does not contain a literal ``.html`` at
the end of the segment represented by ``{name}.html`` (it only contains
``biz``, not ``biz.html``).
@@ -242,7 +242,7 @@ The matchdict will look like so (the value is URL-decoded / UTF-8 decoded):
.. code-block:: text
- {'bar':u'La Pe\xf1a'}
+ {'bar': 'La Pe\xf1a'}
Literal strings in the path segment should represent the *decoded* value of the
``PATH_INFO`` provided to Pyramid. You don't want to use a URL-encoded value
@@ -303,10 +303,10 @@ The above pattern will match these URLs, generating the following matchdicts:
.. code-block:: text
foo/1/2/ ->
- {'baz':u'1', 'bar':u'2', 'fizzle':()}
+ {'baz': '1', 'bar': '2', 'fizzle': ()}
foo/abc/def/a/b/c ->
- {'baz':u'abc', 'bar':u'def', 'fizzle':(u'a', u'b', u'c')}
+ {'baz': 'abc', 'bar': 'def', 'fizzle': ('a', 'b', 'c')}
Note that when a ``*stararg`` remainder match is matched, the value put into
the matchdict is turned into a tuple of path segments representing the
@@ -327,7 +327,7 @@ Will generate the following matchdict:
.. code-block:: text
- {'fizzle':(u'La Pe\xf1a', u'a', u'b', u'c')}
+ {'fizzle': ('La Pe\xf1a', 'a', 'b', 'c')}
By default, the ``*stararg`` will parse the remainder sections into a tuple
split by segment. Changing the regular expression used to match a marker can
@@ -341,8 +341,8 @@ The above pattern will match these URLs, generating the following matchdicts:
.. code-block:: text
- foo/1/2/ -> {'baz':u'1', 'bar':u'2', 'fizzle':u''}
- foo/abc/def/a/b/c -> {'baz':u'abc', 'bar':u'def', 'fizzle': u'a/b/c'}
+ foo/1/2/ -> {'baz': '1', 'bar': '2', 'fizzle': ''}
+ foo/abc/def/a/b/c -> {'baz': 'abc', 'bar': 'def', 'fizzle': 'a/b/c'}
This occurs because the default regular expression for a marker is ``[^/]+``
which will match everything up to the first ``/``, while ``{fizzle:.*}`` will
@@ -513,7 +513,7 @@ When the ``/site/{id}`` route pattern matches during a request, the
When this route matches, a ``matchdict`` will be generated and attached to the
request as ``request.matchdict``. If the specific URL matched is ``/site/1``,
the ``matchdict`` will be a dictionary with a single key, ``id``; the value
-will be the string ``'1'``, ex.: ``{'id':'1'}``.
+will be the string ``'1'``, ex.: ``{'id': '1'}``.
The ``mypackage.views`` module referred to above might look like so:
@@ -562,12 +562,12 @@ Here is an example of a corresponding ``mypackage.views`` module:
@view_config(route_name='user')
def user_view(request):
user = request.matchdict['user']
- return Response(u'The user is {}.'.format(user))
+ return Response('The user is {}.'.format(user))
@view_config(route_name='tag')
def tag_view(request):
tag = request.matchdict['tag']
- return Response(u'The tag is {}.'.format(tag))
+ return Response('The tag is {}.'.format(tag))
The above configuration will allow :app:`Pyramid` to service URLs in these
forms:
@@ -581,17 +581,17 @@ forms:
- When a URL matches the pattern ``/ideas/{idea}``, the view callable
available at the dotted Python pathname ``mypackage.views.idea_view`` will
be called. For the specific URL ``/ideas/1``, the ``matchdict`` generated
- and attached to the :term:`request` will consist of ``{'idea':'1'}``.
+ and attached to the :term:`request` will consist of ``{'idea': '1'}``.
- When a URL matches the pattern ``/users/{user}``, the view callable
available at the dotted Python pathname ``mypackage.views.user_view`` will be
called. For the specific URL ``/users/1``, the ``matchdict`` generated and
- attached to the :term:`request` will consist of ``{'user':'1'}``.
+ attached to the :term:`request` will consist of ``{'user': '1'}``.
- When a URL matches the pattern ``/tags/{tag}``, the view callable available
at the dotted Python pathname ``mypackage.views.tag_view`` will be called.
For the specific URL ``/tags/1``, the ``matchdict`` generated and attached to
- the :term:`request` will consist of ``{'tag':'1'}``.
+ the :term:`request` will consist of ``{'tag': '1'}``.
In this example we've again associated each of our routes with a :term:`view
callable` directly. In all cases, the request, which will have a ``matchdict``
@@ -714,13 +714,13 @@ Therefore, if you've added a route like so:
.. code-block:: python
- config.add_route('la', u'/La Peña/{city}')
+ config.add_route('la', '/La Peña/{city}')
And you later generate a URL using ``route_path`` or ``route_url`` like so:
.. code-block:: python
- url = request.route_path('la', city=u'Québec')
+ url = request.route_path('la', city='Québec')
You will wind up with the path encoded to UTF-8 and URL-quoted like so:
@@ -739,7 +739,7 @@ And you later generate a URL using ``route_path`` or ``route_url`` using a
.. code-block:: python
- url = request.route_path('abc', foo=u'Québec/biz')
+ url = request.route_path('abc', foo='Québec/biz')
The value you pass will be URL-quoted except for embedded slashes in the
result:
@@ -752,7 +752,7 @@ You can get a similar result by passing a tuple composed of path elements:
.. code-block:: python
- url = request.route_path('abc', foo=(u'Québec', u'biz'))
+ url = request.route_path('abc', foo=('Québec', 'biz'))
Each value in the tuple will be URL-quoted and joined by slashes in this case:
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index a53063f78..1b4118b85 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -442,10 +442,7 @@ browser client, and its ``action`` points at some :app:`Pyramid` view code:
:linenos:
<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- </head>
- <form method="POST" action="myview">
+ <form method="POST" action="myview" accept-charset="UTF-8">
<div>
<input type="text" name="firstname"/>
</div>
@@ -457,8 +454,8 @@ browser client, and its ``action`` points at some :app:`Pyramid` view code:
</html>
The ``myview`` view code in the :app:`Pyramid` application *must* expect that
-the values returned by ``request.params`` will be of type ``unicode``, as
-opposed to type ``str``. The following will work to accept a form post from the
+the values returned by ``request.params`` will be of type ``str``, as opposed
+to type ``bytes``. The following will work to accept a form post from the
above form:
.. code-block:: python
@@ -468,24 +465,12 @@ above form:
firstname = request.params['firstname']
lastname = request.params['lastname']
-But the following ``myview`` view code *may not* work, as it tries to decode
-already-decoded (``unicode``) values obtained from ``request.params``:
-
-.. code-block:: python
- :linenos:
-
- def myview(request):
- # the .decode('utf-8') will break below if there are any high-order
- # characters in the firstname or lastname
- firstname = request.params['firstname'].decode('utf-8')
- lastname = request.params['lastname'].decode('utf-8')
-
For implicit decoding to work reliably, you should ensure that every form you
render that posts to a :app:`Pyramid` view explicitly defines a charset
encoding of UTF-8. This can be done via a response that has a
``;charset=UTF-8`` in its ``Content-Type`` header; or, as in the form above,
-with a ``meta http-equiv`` tag that implies that the charset is UTF-8 within
-the HTML ``head`` of the page containing the form. This must be done
+with an ``accept-charset`` attribute, informing the browser that the
+server expects the form content to be encoded using UTF-8. This must be done
explicitly because all known browser clients assume that they should encode
form data in the same character set implied by the ``Content-Type`` value of
the response containing the form when subsequently submitting that form. There
@@ -499,21 +484,15 @@ when it can't decode some high-order character encoded in another character set
within form data, e.g., when ``request.params['somename']`` is accessed.
If you are using the :class:`~pyramid.response.Response` class to generate a
-response, or if you use the ``render_template_*`` templating APIs, the UTF-8
-``charset`` is set automatically as the default via the ``Content-Type``
-header. If you return a ``Content-Type`` header without an explicit
-``charset``, a request will add a ``;charset=utf-8`` trailer to the
+response, or if you use the ``pyramid.renderers.render_*`` templating APIs,
+the UTF-8 ``charset`` is set automatically as the default via the
+``Content-Type`` header. If you return a ``Content-Type`` header without an
+explicit ``charset``, a request will add a ``;charset=utf-8`` trailer to the
``Content-Type`` header value for you for response content types that are
textual (e.g., ``text/html`` or ``application/xml``) as it is rendered. If you
are using your own response object, you will need to ensure you do this
yourself.
-.. note:: Only the *values* of request params obtained via ``request.params``,
- ``request.GET`` or ``request.POST`` are decoded to Unicode objects
- implicitly in the :app:`Pyramid` default configuration. The keys are still
- (byte) strings.
-
-
.. index::
single: view calling convention
diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst
index 89dc6e0f1..665bbddc9 100644
--- a/docs/narr/webob.rst
+++ b/docs/narr/webob.rst
@@ -188,15 +188,10 @@ of them. Here are a couple that might be useful:
Text (Unicode)
++++++++++++++
-Many of the properties of the request object will be text values (``unicode``
-under Python 2 or ``str`` under Python 3) if the request encoding/charset is
-provided. If it is provided, the values in ``req.POST``, ``req.GET``,
-``req.params``, and ``req.cookies`` will contain text. The client *can*
-indicate the charset with something like ``Content-Type:
-application/x-www-form-urlencoded; charset=utf8``, but browsers seldom set
-this. You can reset the charset of an existing request with ``newreq =
-req.decode('utf-8')``, or during instantiation with ``Request(environ,
-charset='utf8')``.
+Most of the properties of the request object will be text values.
+The values in ``req.POST``, ``req.GET``, ``req.params``, and ``req.cookies`` will contain text and are generated assuming a UTF-8 charset.
+The client *can* indicate the charset with something like ``Content-Type: application/x-www-form-urlencoded; charset=utf8``, but browsers seldom set this.
+You can reset the charset of an existing request with ``newreq = req.decode('utf-8')``, or during instantiation with ``Request(environ, charset='utf8')``.
.. index::
single: multidict (WebOb)
@@ -264,7 +259,7 @@ to a :app:`Pyramid` application:
jQuery.ajax({type:'POST',
url: 'http://localhost:6543/', // the pyramid server
data: JSON.stringify({'a':1}),
- contentType: 'application/json; charset=utf-8'});
+ contentType: 'application/json'});
When such a request reaches a view in your application, the
``request.json_body`` attribute will be available in the view callable body.
@@ -280,7 +275,7 @@ For the above view, printed to the console will be:
.. code-block:: python
- {u'a': 1}
+ {'a': 1}
For bonus points, here's a bit of client-side code that will produce a request
that has a body suitable for reading via ``request.json_body`` using Python's
@@ -386,8 +381,8 @@ A response object has three fundamental parts:
``response.app_iter``
An iterable (such as a list or generator) that will produce the content of
- the response. This is also accessible as ``response.body`` (a string),
- ``response.text`` (a unicode object, informed by ``response.charset``), and
+ the response. This is also accessible as ``response.body`` (bytes),
+ ``response.text`` (a Unicode string, informed by ``response.charset``), and
``response.body_file`` (a file-like object; writing to it appends to
``app_iter``).
diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst
index 1e2c71cf0..fbafead66 100644
--- a/docs/quick_tour.rst
+++ b/docs/quick_tour.rst
@@ -15,12 +15,9 @@ If you would prefer to cut and paste the example code in this tour you may brows
Installation
============
-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 <https://www.python.org/downloads/>`_, :mod:`python:venv` (or `virtualenv for
-Python 2.7 <https://virtualenv.pypa.io/en/stable/>`_),
-`pip <https://pypi.org/project/pip/>`_, and `Setuptools
-<https://pypi.org/project/setuptools/>`_.
+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 <https://www.python.org/downloads/>`_, :mod:`python:venv`, `pip <https://pypi.org/project/pip/>`_, and `Setuptools <https://pypi.org/project/setuptools/>`_.
To save a little bit of typing and to be certain that we use the modules,
scripts, and packages installed in our virtual environment, we'll set an
@@ -52,10 +49,8 @@ For Windows:
# or for a specific released version
c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ "
-Of course Pyramid runs fine on Python 2.7+, as do the examples in this *Quick
-Tour*. We're showing Python 3 for simplicity. (Pyramid had production support
-for Python 3 in October 2011.) Also for simplicity, the remaining examples will
-show only Unix commands.
+As of version 2.0, Pyramid runs on Python 3 only.
+For simplicity, the remaining examples will show only Unix commands.
.. seealso:: See also:
:ref:`Quick Tutorial section on Requirements <qtut_requirements>`,
diff --git a/docs/quick_tour/logging/setup.py b/docs/quick_tour/logging/setup.py
index 27b025384..e9c15db04 100644
--- a/docs/quick_tour/logging/setup.py
+++ b/docs/quick_tour/logging/setup.py
@@ -17,8 +17,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/quick_tour/package/setup.py b/docs/quick_tour/package/setup.py
index 27b025384..e9c15db04 100644
--- a/docs/quick_tour/package/setup.py
+++ b/docs/quick_tour/package/setup.py
@@ -17,8 +17,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/quick_tour/sessions/setup.py b/docs/quick_tour/sessions/setup.py
index 27b025384..e9c15db04 100644
--- a/docs/quick_tour/sessions/setup.py
+++ b/docs/quick_tour/sessions/setup.py
@@ -17,8 +17,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/quick_tour/sqla_demo/setup.py b/docs/quick_tour/sqla_demo/setup.py
index 76cd518ca..28a8e0815 100644
--- a/docs/quick_tour/sqla_demo/setup.py
+++ b/docs/quick_tour/sqla_demo/setup.py
@@ -23,8 +23,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/quick_tour/views/views.py b/docs/quick_tour/views/views.py
index 95a2b60ca..ffbe1d893 100644
--- a/docs/quick_tour/views/views.py
+++ b/docs/quick_tour/views/views.py
@@ -1,4 +1,4 @@
-from pyramid.compat import escape
+from html import escape
from pyramid.httpexceptions import HTTPFound
from pyramid.response import Response
@@ -16,7 +16,7 @@ def home_view(request):
def hello_view(request):
name = request.params.get('name', 'No Name')
body = '<p>Hi %s, this <a href="/goto">redirects</a></p>'
- # pyramid.compat.escape to prevent Cross-Site Scripting (XSS) [CWE 79]
+ # Python html.escape to prevent Cross-Site Scripting (XSS) [CWE 79]
return Response(body % escape(name))
diff --git a/docs/quick_tutorial/cookiecutters/setup.py b/docs/quick_tutorial/cookiecutters/setup.py
index 9482e7c32..d5d3d018b 100644
--- a/docs/quick_tutorial/cookiecutters/setup.py
+++ b/docs/quick_tutorial/cookiecutters/setup.py
@@ -17,8 +17,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/quick_tutorial/index.rst b/docs/quick_tutorial/index.rst
index b5b7b3313..d727502e9 100644
--- a/docs/quick_tutorial/index.rst
+++ b/docs/quick_tutorial/index.rst
@@ -4,8 +4,7 @@
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 tutorial gives a 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
diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst
index a74c07673..2ed9b8b55 100644
--- a/docs/quick_tutorial/requirements.rst
+++ b/docs/quick_tutorial/requirements.rst
@@ -19,12 +19,11 @@ virtual environment.)
This *Quick Tutorial* is based on:
-* **Python 3.7**. Pyramid fully supports Python 3.4+ and Python 2.7+. This
- tutorial uses **Python 3.7** but runs fine under Python 2.7.
+* **Python 3.7**. Pyramid fully supports Python 3.4+.
+ This tutorial uses **Python 3.7**.
-* **venv**. We believe in virtual environments. For this tutorial, we use
- Python 3's built-in solution :term:`venv`. For Python 2.7, you can install
- :term:`virtualenv`.
+* **venv**. We believe in virtual environments.
+ For this tutorial, we use Python 3's built-in solution :term:`venv`.
* **pip**. We use :term:`pip` for package management.
@@ -158,8 +157,7 @@ environment variable.
# Windows
python -m venv %VENV%
-.. seealso:: See also Python 3's :mod:`venv module <python:venv>` and Python
- 2's `virtualenv <https://virtualenv.pypa.io/en/latest/>`_ package.
+.. seealso:: See also :mod:`venv module <python:venv>`.
Update packaging tools in the virtual environment
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index b7eeb19ae..ef914cab5 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -1,39 +1,33 @@
.. _wiki_adding_authorization:
-====================
-Adding authorization
-====================
-
-:app:`Pyramid` provides facilities for :term:`authentication` and
-:term:`authorization`. We'll make use of both features to provide security to
-our application. Our application currently allows anyone with access to the
-server to view, edit, and add pages to our wiki. We'll change that to allow
-only people who are members of a *group* named ``group:editors`` to add and
-edit wiki pages, but we'll continue allowing anyone with access to the server
-to view pages.
-
-We will also add a login page and a logout link on all the pages. The login
-page will be shown when a user is denied access to any of the views that
+=======================================
+Adding authorization and authentication
+=======================================
+
+:app:`Pyramid` provides facilities for :term:`authentication` and :term:`authorization`.
+We will make use of both features to provide security to our application.
+Our application currently allows anyone with access to the server to view, edit, and add pages to our wiki.
+We will change that to allow only people who are members of a *group* named ``group:editors`` to add and edit wiki pages.
+We will continue to allow anyone with access to the server to view pages.
+
+We will also add a login page and a logout link on all the pages.
+The login page will be shown when a user is denied access to any of the views that
require permission, instead of a default "403 Forbidden" page.
We will implement the access control with the following steps:
-* Add password hashing dependencies.
-* Add users and groups (``security.py``, a new module).
-* Add an :term:`ACL` (``models.py``).
-* Add an :term:`authentication policy` and an :term:`authorization policy`
- (``__init__.py``).
-* Add :term:`permission` declarations to the ``edit_page`` and ``add_page``
- views (``views.py``).
+- Add password hashing dependencies.
+- Add users and groups (``security.py``, a new module).
+- Add an :term:`ACL` (``models.py``).
+- Add an :term:`authentication policy` and an :term:`authorization policy` (``__init__.py``).
+- Add :term:`permission` declarations to the ``edit_page`` and ``add_page`` views (``views.py``).
Then we will add the login and logout features:
-* Add ``login`` and ``logout`` views (``views.py``).
-* Add a login template (``login.pt``).
-* Make the existing views return a ``logged_in`` flag to the renderer
- (``views.py``).
-* Add a "Logout" link to be shown when logged in and viewing or editing a page
- (``view.pt``, ``edit.pt``).
+- Add ``login`` and ``logout`` views (``views.py``).
+- Add a login template (``login.pt``).
+- Make the existing views return a ``logged_in`` flag to the renderer (``views.py``).
+- Add a "Logout" link to be shown when logged in and viewing or editing a page (``view.pt``, ``edit.pt``).
Access control
@@ -43,14 +37,15 @@ Access control
Add dependencies
~~~~~~~~~~~~~~~~
-Just like in :ref:`wiki_defining_views`, we need a new dependency. We need to add the `bcrypt <https://pypi.org/project/bcrypt/>`_ package, to our tutorial package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function.
+Just like in :ref:`wiki_defining_views`, we need a new dependency.
+We need to add the `bcrypt <https://pypi.org/project/bcrypt/>`_ package to our tutorial package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function.
Open ``setup.py`` and edit it to look like the following:
.. literalinclude:: src/authorization/setup.py
- :linenos:
- :emphasize-lines: 23
- :language: python
+ :linenos:
+ :emphasize-lines: 23
+ :language: python
Only the highlighted line needs to be added.
@@ -58,7 +53,9 @@ Do not forget to run ``pip install -e .`` just like in :ref:`wiki-running-pip-in
.. note::
- We are using the ``bcrypt`` package from PyPI to hash our passwords securely. There are other one-way hash algorithms for passwords if bcrypt is an issue on your system. Just make sure that it's an algorithm approved for storing passwords versus a generic one-way hash.
+ We are using the ``bcrypt`` package from PyPI to hash our passwords securely.
+ There are other one-way hash algorithms for passwords if bcrypt is an issue on your system.
+ Just make sure that it is an algorithm approved for storing passwords versus a generic one-way hash.
Add users and groups
@@ -67,78 +64,78 @@ Add users and groups
Create a new ``tutorial/security.py`` module with the following content:
.. literalinclude:: src/authorization/tutorial/security.py
- :linenos:
- :language: python
+ :linenos:
+ :language: python
-The ``groupfinder`` function accepts a userid and a request and
-returns one of these values:
+The ``groupfinder`` function accepts a ``userid`` and a ``request``
+It returns one of these values:
-- If ``userid`` exists in the system, it will return a sequence of group
- identifiers (or an empty sequence if the user isn't a member of any groups).
-- If the userid *does not* exist in the system, it will return ``None``.
+- If ``userid`` exists in the system, it will return either a sequence of group identifiers, or an empty sequence if the user is not a member of any groups.
+- If the userid *does not* exist in the system, it will return ``None``.
-For example, ``groupfinder('editor', request )`` returns ``['group:editor']``,
-``groupfinder('viewer', request)`` returns ``[]``, and ``groupfinder('admin',
-request)`` returns ``None``. We will use ``groupfinder()`` as an
-:term:`authentication policy` "callback" that will provide the
-:term:`principal` or principals for a user.
+For example:
+
+- ``groupfinder('editor', request )`` returns ``['group:editor']``.
+- ``groupfinder('viewer', request)`` returns ``[]``.
+- ``groupfinder('admin', request)`` returns ``None``.
+
+We will use ``groupfinder()`` as an :term:`authentication policy` "callback" that will provide the :term:`principal` or principals for a user.
There are two helper methods that will help us later to authenticate users.
The first is ``hash_password`` which takes a raw password and transforms it using
-bcrypt into an irreversible representation, a process known as "hashing". The
-second method, ``check_password``, will allow us to compare the hashed value of the
-submitted password against the hashed value of the password stored in the user's
-record. If the two hashed values match, then the submitted
-password is valid, and we can authenticate the user.
+bcrypt into an irreversible representation, a process known as "hashing".
+The second method, ``check_password``, will allow us to compare the hashed value of the submitted password against the hashed value of the password stored in the user's
+record.
+If the two hashed values match, then the submitted password is valid, and we can authenticate the user.
-We hash passwords so that it is impossible to decrypt and use them to
-authenticate in the application. If we stored passwords foolishly in clear text,
-then anyone with access to the database could retrieve any password to authenticate
-as any user.
+We hash passwords so that it is impossible to decrypt and use them to authenticate in the application.
+If we stored passwords foolishly in clear text, then anyone with access to the database could retrieve any password to authenticate as any user.
In a production system, user and group data will most often be saved and come from a
-database, but here we use "dummy" data to represent user and groups sources.
+database.
+Here we use "dummy" data to represent user and groups sources.
+
Add an ACL
~~~~~~~~~~
-Open ``tutorial/models.py`` and add the following import
-statement near the top:
+Open ``tutorial/models.py`` and add the following import statement near the top:
-.. literalinclude:: src/authorization/tutorial/models.py
- :lines: 4-8
- :lineno-match:
- :language: python
+.. literalinclude:: src/authorization/tutorial/models/__init__.py
+ :lines: 4-8
+ :lineno-match:
+ :language: python
Add the following lines to the ``Wiki`` class:
-.. literalinclude:: src/authorization/tutorial/models.py
- :lines: 9-13
- :lineno-match:
- :emphasize-lines: 4-5
- :language: python
-
-We import :data:`~pyramid.security.Allow`, an action that means that
-permission is allowed, and :data:`~pyramid.security.Everyone`, a special
-:term:`principal` that is associated to all requests. Both are used in the
-:term:`ACE` entries that make up the ACL.
-
-The ACL is a list that needs to be named ``__acl__`` and be an attribute of a
-class. We define an :term:`ACL` with two :term:`ACE` entries: the first entry
-allows any user the ``view`` permission. The second entry allows the
-``group:editors`` principal the ``edit`` permission.
-
-The ``Wiki`` class that contains the ACL is the :term:`resource` constructor
-for the :term:`root` resource, which is a ``Wiki`` instance. The ACL is
-provided to each view in the :term:`context` of the request as the ``context``
-attribute.
-
-It's only happenstance that we're assigning this ACL at class scope. An ACL
-can be attached to an object *instance* too; this is how "row level security"
-can be achieved in :app:`Pyramid` applications. We actually need only *one*
-ACL for the entire system, however, because our security requirements are
-simple, so this feature is not demonstrated. See :ref:`assigning_acls` for
-more information about what an :term:`ACL` represents.
+.. literalinclude:: src/authorization/tutorial/models/__init__.py
+ :lines: 9-13
+ :lineno-match:
+ :emphasize-lines: 4-5
+ :language: python
+
+We import :data:`~pyramid.security.Allow`, an action which means that
+permission is allowed.
+We also import :data:`~pyramid.security.Everyone`, a special :term:`principal` that is associated to all requests.
+Both are used in the :term:`ACE` entries that make up the ACL.
+
+The ACL is a list that needs to be named ``__acl__`` and be an attribute of a class.
+We define an :term:`ACL` with two :term:`ACE` entries.
+The first entry allows any user the ``view`` permission.
+The second entry allows the ``group:editors`` principal the ``edit`` permission.
+
+The ``Wiki`` class that contains the ACL is the :term:`resource` constructor for the :term:`root` resource, which is a ``Wiki`` instance.
+The ACL is provided to each view in the :term:`context` of the request as the ``context`` attribute.
+
+It is only happenstance that we assigned this ACL at class scope.
+An ACL can be attached to an object *instance* too.
+This is how "row level security" can be achieved in :app:`Pyramid` applications.
+We actually need only *one* ACL for the entire system, however, because our security requirements are simple, so this feature is not demonstrated.
+
+.. seealso::
+
+ See :ref:`assigning_acls` for more information about what an :term:`ACL` represents.
+
Add authentication and authorization policies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -147,123 +144,113 @@ Open ``tutorial/__init__.py`` and add the highlighted import
statements:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 1-8
- :linenos:
- :emphasize-lines: 3-6,8
- :language: python
+ :lines: 1-8
+ :linenos:
+ :emphasize-lines: 3-6,8
+ :language: python
Now add those policies to the configuration:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 18-25
- :lineno-match:
- :emphasize-lines: 2-4,6-7
- :language: python
+ :lines: 15-25
+ :lineno-match:
+ :emphasize-lines: 4-6,8-9
+ :language: python
Only the highlighted lines need to be added.
-We are enabling an ``AuthTktAuthenticationPolicy``, which is based in an auth
-ticket that may be included in the request. We are also enabling an
-``ACLAuthorizationPolicy``, which uses an ACL to determine the *allow* or
-*deny* outcome for a view.
+We enabled an ``AuthTktAuthenticationPolicy`` which is based in an auth ticket that may be included in the request.
+We also enabled an ``ACLAuthorizationPolicy`` which uses an ACL to determine the *allow* or *deny* outcome for a view.
+
+Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy` constructor accepts two arguments: ``secret`` and ``callback``.
+``secret`` is a string representing an encryption key used by the "authentication ticket" machinery represented by this policy.
+It is required.
+The ``callback`` is the ``groupfinder()`` function that we created earlier.
-Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy`
-constructor accepts two arguments: ``secret`` and ``callback``. ``secret`` is
-a string representing an encryption key used by the "authentication ticket"
-machinery represented by this policy: it is required. The ``callback`` is the
-``groupfinder()`` function that we created before.
Add permission declarations
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/views.py`` and add a ``permission='edit'`` parameter
-to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 49-51
- :emphasize-lines: 2-3
- :language: python
+Open ``tutorial/views/default.py`` and add a ``permission='edit'`` parameter to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``:
+
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 49-51
+ :emphasize-lines: 2-3
+ :language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 68-70
- :emphasize-lines: 2-3
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 68-70
+ :emphasize-lines: 2-3
+ :language: python
-Only the highlighted lines, along with their preceding commas, need to be
-edited and added.
+Only the highlighted lines, along with their preceding commas, need to be edited and added.
-The result is that only users who possess the ``edit`` permission at the time
-of the request may invoke those two views.
+The result is that only users who possess the ``edit`` permission at the time of the request may invoke those two views.
Add a ``permission='view'`` parameter to the ``@view_config`` decorator for
``view_wiki()`` and ``view_page()`` as follows:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 23-24
- :emphasize-lines: 1-2
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 23-24
+ :emphasize-lines: 1-2
+ :language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 28-29
- :emphasize-lines: 1-2
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 28-29
+ :emphasize-lines: 1-2
+ :language: python
-Only the highlighted lines, along with their preceding commas, need to be
-edited and added.
+Only the highlighted lines, along with their preceding commas, need to be edited and added.
This allows anyone to invoke these two views.
-We are done with the changes needed to control access. The changes that
-follow will add the login and logout feature.
+We are done with the changes needed to control access.
+The changes that follow will add the login and logout feature.
+
Login, logout
-------------
+
Add login and logout views
~~~~~~~~~~~~~~~~~~~~~~~~~~
-We'll add a ``login`` view which renders a login form and processes the post
-from the login form, checking credentials.
+We will add a ``login`` view which renders a login form and processes the post from the login form, checking credentials.
-We'll also add a ``logout`` view callable to our application and provide a
-link to it. This view will clear the credentials of the logged in user and
-redirect back to the front page.
+We will also add a ``logout`` view callable to our application and provide a link to it.
+This view will clear the credentials of the logged in user and redirect back to the front page.
-Add the following import statements to the head of
-``tutorial/views.py``:
+Add the following import statements to the head of ``tutorial/views/default.py``:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 6-17
- :emphasize-lines: 1-12
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 4-15
+ :emphasize-lines: 2-10,12
+ :language: python
All the highlighted lines need to be added or edited.
-:meth:`~pyramid.view.forbidden_view_config` will be used to customize the
-default 403 Forbidden page. :meth:`~pyramid.security.remember` and
-:meth:`~pyramid.security.forget` help to create and expire an auth ticket
-cookie.
+:meth:`~pyramid.view.forbidden_view_config` will be used to customize the default 403 Forbidden page.
+:meth:`~pyramid.security.remember` and :meth:`~pyramid.security.forget` help to create and expire an auth ticket cookie.
Now add the ``login`` and ``logout`` views at the end of the file:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 80-
- :lineno-match:
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 78-
+ :lineno-match:
+ :language: python
``login()`` has two decorators:
-- a ``@view_config`` decorator which associates it with the ``login`` route
- and makes it visible when we visit ``/login``,
-- a ``@forbidden_view_config`` decorator which turns it into a
- :term:`forbidden view`. ``login()`` will be invoked when a user tries to
- execute a view callable for which they lack authorization. For example, if
- a user has not logged in and tries to add or edit a Wiki page, they will be
- shown the login form before being allowed to continue.
+- A ``@view_config`` decorator which associates it with the ``login`` route and makes it visible when we visit ``/login``.
+- A ``@forbidden_view_config`` decorator which turns it into a :term:`forbidden view`.
+ ``login()`` will be invoked when a user tries to execute a view callable for which they lack authorization.
+ For example, if a user has not logged in and tries to add or edit a Wiki page, then they will be shown the login form before being allowed to continue.
The order of these two :term:`view configuration` decorators is unimportant.
-``logout()`` is decorated with a ``@view_config`` decorator which associates
-it with the ``logout`` route. It will be invoked when we visit ``/logout``.
+``logout()`` is decorated with a ``@view_config`` decorator which associates it with the ``logout`` route.
+It will be invoked when we visit ``/logout``.
+
Add the ``login.pt`` Template
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -271,134 +258,127 @@ Add the ``login.pt`` Template
Create ``tutorial/templates/login.pt`` with the following content:
.. literalinclude:: src/authorization/tutorial/templates/login.pt
- :language: html
+ :language: html
+
+The above template is referenced in the login view that we just added in ``views.py``.
-The above template is referenced in the login view that we just added in
-``views.py``.
Return a ``logged_in`` flag to the renderer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/views.py`` again. Add a ``logged_in`` parameter to
-the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as
-follows:
+Open ``tutorial/views/default.py`` again.
+Add a ``logged_in`` parameter to the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as follows:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 46-47
- :emphasize-lines: 1-2
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 45-46
+ :emphasize-lines: 1-2
+ :language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 65-66
- :emphasize-lines: 1-2
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 65-66
+ :emphasize-lines: 1-2
+ :language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 76-78
- :emphasize-lines: 2-3
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 77-79
+ :emphasize-lines: 2-3
+ :language: python
Only the highlighted lines need to be added or edited.
-The :meth:`pyramid.request.Request.authenticated_userid` will be ``None`` if
-the user is not authenticated, or a userid if the user is authenticated.
+The :meth:`pyramid.request.Request.authenticated_userid` will be ``None`` if the user is not authenticated, or a ``userid`` if the user is authenticated.
+
Add a "Logout" link when logged in
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/templates/edit.pt`` and
-``tutorial/templates/view.pt`` and add the following code as
-indicated by the highlighted lines.
+Open ``tutorial/templates/edit.pt`` and ``tutorial/templates/view.pt``.
+Add the following code as indicated by the highlighted lines.
.. literalinclude:: src/authorization/tutorial/templates/edit.pt
- :lines: 35-39
- :emphasize-lines: 2-4
- :language: html
+ :lines: 4-8
+ :emphasize-lines: 2-4
+ :language: html
-The attribute ``tal:condition="logged_in"`` will make the element be included
-when ``logged_in`` is any user id. The link will invoke the logout view. The
-above element will not be included if ``logged_in`` is ``None``, such as when
+The attribute ``tal:condition="logged_in"`` will make the element be included when ``logged_in`` is any user id.
+The link will invoke the logout view.
+The above element will not be included if ``logged_in`` is ``None``, such as when
a user is not authenticated.
+
Reviewing our changes
---------------------
-Our ``tutorial/__init__.py`` will look like this when we're done:
+Our ``tutorial/__init__.py`` will look like this when we are done:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :linenos:
- :emphasize-lines: 4-5,8,19-21,23-24
- :language: python
+ :linenos:
+ :emphasize-lines: 3-6,8,18-20,22-23
+ :language: python
Only the highlighted lines need to be added or edited.
-Our ``tutorial/models.py`` will look like this when we're done:
+Our ``tutorial/models.py`` will look like this when we are done:
-.. literalinclude:: src/authorization/tutorial/models.py
- :linenos:
- :emphasize-lines: 4-7,12-13
- :language: python
+.. literalinclude:: src/authorization/tutorial/models/__init__.py
+ :linenos:
+ :emphasize-lines: 4-8,12-13
+ :language: python
Only the highlighted lines need to be added or edited.
-Our ``tutorial/views.py`` will look like this when we're done:
+Our ``tutorial/views/default.py`` will look like this when we are done:
-.. literalinclude:: src/authorization/tutorial/views.py
- :linenos:
- :emphasize-lines: 8,11-15,17,24,29,47,51,66,70,78,80-
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :linenos:
+ :emphasize-lines: 5-12,15,21-22,27-28,45-46,50-51,65-66,70-71,78-
+ :language: python
Only the highlighted lines need to be added or edited.
-Our ``tutorial/templates/edit.pt`` template will look like this when
-we're done:
+Our ``tutorial/templates/edit.pt`` template will look like this when we are done:
.. literalinclude:: src/authorization/tutorial/templates/edit.pt
- :linenos:
- :emphasize-lines: 36-38
- :language: html
+ :linenos:
+ :emphasize-lines: 5-7
+ :language: html
Only the highlighted lines need to be added or edited.
-Our ``tutorial/templates/view.pt`` template will look like this when
-we're done:
+Our ``tutorial/templates/view.pt`` template will look like this when we are done:
.. literalinclude:: src/authorization/tutorial/templates/view.pt
- :linenos:
- :emphasize-lines: 36-38
- :language: html
+ :linenos:
+ :emphasize-lines: 5-7
+ :language: html
Only the highlighted lines need to be added or edited.
Viewing the application in a browser
------------------------------------
-We can finally examine our application in a browser (See
-:ref:`wiki-start-the-application`). Launch a browser and visit each of the
-following URLs, checking that the result is as expected:
-
-- http://localhost:6543/ invokes the ``view_wiki`` view. This always
- redirects to the ``view_page`` view of the ``FrontPage`` Page resource. It
- is executable by any user.
-
-- http://localhost:6543/FrontPage invokes the ``view_page`` view of the
- ``FrontPage`` Page resource. This is because it's the :term:`default view`
- (a view without a ``name``) for ``Page`` resources. It is executable by any
- user.
-
-- http://localhost:6543/FrontPage/edit_page invokes the edit view for the
- FrontPage object. It is executable by only the ``editor`` user. If a
- different user (or the anonymous user) invokes it, a login form will be
- displayed. Supplying the credentials with the username ``editor``, password
- ``editor`` will display the edit page form.
-
-- http://localhost:6543/add_page/SomePageName invokes the add view for a page.
- It is executable by only the ``editor`` user. If a different user (or the
- anonymous user) invokes it, a login form will be displayed. Supplying the
- credentials with the username ``editor``, password ``editor`` will display
- the edit page form.
-
-- After logging in (as a result of hitting an edit or add page and submitting
- the login form with the ``editor`` credentials), we'll see a Logout link in
- the upper right hand corner. When we click it, we're logged out, and
- redirected back to the front page.
+We can finally examine our application in a browser (See :ref:`wiki-start-the-application`).
+Launch a browser and visit each of the following URLs, checking that the result is as expected:
+
+- http://localhost:6543/ invokes the ``view_wiki`` view.
+ This always redirects to the ``view_page`` view of the ``FrontPage`` Page resource.
+ It is executable by any user.
+
+- http://localhost:6543/FrontPage invokes the ``view_page`` view of the ``FrontPage`` Page resource.
+ This is because it is the :term:`default view` (a view without a ``name``) for ``Page`` resources.
+ It is executable by any user.
+
+- http://localhost:6543/FrontPage/edit_page invokes the edit view for the FrontPage object.
+ It is executable by only the ``editor`` user.
+ If a different user (or the anonymous user) invokes it, then a login form will be displayed.
+ Supplying the credentials with the username ``editor`` and password ``editor`` will display the edit page form.
+
+- http://localhost:6543/add_page/SomePageName invokes the add view for a page.
+ It is executable by only the ``editor`` user.
+ If a different user (or the anonymous user) invokes it, a login form will be displayed.
+ Supplying the credentials with the username ``editor``, password ``editor`` will display the edit page form.
+
+- After logging in (as a result of hitting an edit or add page and submitting the login form with the ``editor`` credentials), we will see a Logout link in the upper right hand corner.
+ When we click it, we are logged out, and redirected back to the front page.
+
+- To generate a not found error, visit http://localhost:6543/wakawaka which will invoke the ``notfound_view`` view provided by the cookiecutter.
diff --git a/docs/tutorials/wiki/background.rst b/docs/tutorials/wiki/background.rst
index c10ab9e55..1a076de85 100644
--- a/docs/tutorials/wiki/background.rst
+++ b/docs/tutorials/wiki/background.rst
@@ -15,9 +15,4 @@ To code along with this tutorial, the developer will need a Unix
machine with development tools (macOS with XCode, any Linux or BSD
variant, and so on) *or* a Windows system of any kind.
-.. warning::
-
- This tutorial has been written for Python 2. It is unlikely to work
- without modification under Python 3.
-
Have fun!
diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst
index 49ee6902e..52d3f4670 100644
--- a/docs/tutorials/wiki/basiclayout.rst
+++ b/docs/tutorials/wiki/basiclayout.rst
@@ -4,188 +4,255 @@
Basic Layout
============
-The starter files generated by selecting the ``zodb`` backend in the
-cookiecutter are very basic, but they provide a good orientation for the
-high-level patterns common to most :term:`traversal`-based (and
-:term:`ZODB`-based) :app:`Pyramid` projects.
+The starter files generated by the cookiecutter are very basic, but they provide a good orientation for the high-level patterns common to most :term:`traversal`-based (and :term:`ZODB`-based) :app:`Pyramid` projects.
Application configuration with ``__init__.py``
----------------------------------------------
-A directory on disk can be turned into a Python :term:`package` by containing
-an ``__init__.py`` file. Even if empty, this marks a directory as a Python
-package. We use ``__init__.py`` both as a marker, indicating the directory in
-which it's contained is a package, and to contain application configuration
-code.
+A directory on disk can be turned into a Python :term:`package` by containing an ``__init__.py`` file.
+Even if empty, this marks a directory as a Python package.
+We use ``__init__.py`` both as a marker, indicating the directory in which it is contained is a package, and to contain application configuration code.
-When you run the application using the ``pserve`` command using the
-``development.ini`` generated configuration file, the application
-configuration points at a :term:`Setuptools` :term:`entry point` described as
-``egg:tutorial``. In our application, because the application's ``setup.py``
-file says so, this entry point happens to be the ``main`` function within the
-file named ``__init__.py``.
+When you run the application using the ``pserve`` command using the ``development.ini`` generated configuration file, the application configuration points at a :term:`Setuptools` :term:`entry point` described as ``egg:tutorial``.
+In our application, because the application's ``setup.py`` file says so, this entry point happens to be the ``main`` function within the file named ``__init__.py``.
-Open ``tutorial/__init__.py``. It should already contain the following:
+Open ``tutorial/__init__.py``.
+It should already contain the following:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :linenos:
- :language: py
+ :linenos:
+ :language: py
+
+Let's go over this piece-by-piece.
+First we need some imports to support later code.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :end-before: root_factory
+ :lineno-match:
+ :language: py
+
+Define a :term:`root factory` for our Pyramid application.
+It establishes a connection to ZODB database.
+It returns an ``appmaker``, which we will describe in the next section :ref:`wiki-resources-and-models`.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :pyobject: root_factory
+ :lineno-match:
+ :language: py
+
+``__init__.py`` defines a function named ``main``.
+Here is the entirety of the ``main`` function that we have defined in our ``__init__.py``:
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :pyobject: main
+ :lineno-match:
+ :language: py
+
+When you invoke the ``pserve development.ini`` command, the ``main`` function above is executed.
+It accepts some settings and returns a :term:`WSGI` application.
+See :ref:`startup_chapter` for more about ``pserve``.
+
+Next in ``main``, construct a :term:`Configurator` object using a context manager.
+See also :term:`Deployment settings`.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 14
+ :lineno-match:
+ :language: py
+
+``settings`` is passed to the ``Configurator`` as a keyword argument with the dictionary values passed as the ``**settings`` argument.
+This will be a dictionary of settings parsed from the ``.ini`` file, which contains
+deployment-related values, such as ``pyramid.reload_templates``, ``zodbconn.uri``, and so on.
+
+Next include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction <https://pypi.org/project/transaction/>`_ package.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 15
+ :lineno-match:
+ :language: py
+
+Next include support for ``pyramid_retry`` to retry a request when transient exceptions occur.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 16
+ :lineno-match:
+ :language: py
+
+Next include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 17
+ :lineno-match:
+ :language: py
+
+Next set a root factory using our function named ``root_factory``.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 18
+ :lineno-match:
+ :language: py
+
+Next include support for the :term:`Chameleon` template rendering bindings, allowing us to use the ``.pt`` templates.
-#. *Lines 1-3*. Perform some dependency imports.
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 19
+ :lineno-match:
+ :language: py
-#. *Lines 6-8*. Define a :term:`root factory` for our Pyramid application.
+Next include routes from the ``.routes`` module.
-#. *Line 11*. ``__init__.py`` defines a function named ``main``.
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 20
+ :lineno-match:
+ :language: py
-#. *Line 14*. Use an explicit transaction manager for apps so that they do not implicitly create new transactions when touching the manager outside of the ``pyramid_tm`` lifecycle.
+This registers a "static view" using the :meth:`pyramid.config.Configurator.add_static_view` method.
+This view answers requests whose URL path starts with ``/static``.
+This statement registers a view that will serve up static assets, such as CSS and image files.
+In this case the URL will answer requests at ``http://localhost:6543/static/`` and below.
-#. *Line 15*. Construct a :term:`Configurator` as a :term:`context manager` with the settings keyword parsed by :term:`PasteDeploy`.
+The first argument is the "name" ``static``, which indicates that the URL path prefix of the view will be ``/static``.
-#. *Line 16*. Include support for the :term:`Chameleon` template rendering
- bindings, allowing us to use the ``.pt`` templates.
+The second argument of this method is the "path".
+It is a relative :term:`asset specification`.
+It finds the resources it should serve within the ``static`` directory inside the ``tutorial`` package.
+Alternatively the cookiecutter could have used an *absolute* asset specification as the path (``tutorial:static``).
-#. *Line 17*. Include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction <https://pypi.org/project/transaction/>`_ package.
+The third argument is an optional ``cache_max_age`` which specifies the number of seconds the static asset will be HTTP-cached.
-#. *Line 18*. Include support for ``pyramid_retry`` to retry a request when transient exceptions occur.
+Next perform a :term:`scan`.
-#. *Line 19*. Include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application.
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 21
+ :lineno-match:
+ :language: py
-#. *Line 20*. Set a root factory using our function named ``root_factory``.
+A scan will find :term:`configuration decoration`, such as view configuration decorators (e.g., ``@view_config``) in the source code of the ``tutorial`` package.
+It will take actions based on these decorators.
+We don't pass any arguments to :meth:`~pyramid.config.Configurator.scan`, which implies that the scan should take place in the current package (in this case, ``tutorial``).
+The cookiecutter could have equivalently said ``config.scan('tutorial')``, but it chose to omit the package name argument.
-#. *Line 21*. Register a "static view", which answers requests whose URL
- paths start with ``/static``, using the
- :meth:`pyramid.config.Configurator.add_static_view` method. This
- statement registers a view that will serve up static assets, such as CSS
- and image files, for us, in this case, at
- ``http://localhost:6543/static/`` and below. The first argument is the
- "name" ``static``, which indicates that the URL path prefix of the view
- will be ``/static``. The second argument of this tag is the "path",
- which is a relative :term:`asset specification`, so it finds the resources
- it should serve within the ``static`` directory inside the ``tutorial``
- package. Alternatively the cookiecutter could have used an *absolute* asset
- specification as the path (``tutorial:static``).
+Finally use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application.
-#. *Line 22*. Perform a :term:`scan`. A scan will find :term:`configuration
- decoration`, such as view configuration decorators (e.g., ``@view_config``)
- in the source code of the ``tutorial`` package and will take actions based
- on these decorators. We don't pass any arguments to
- :meth:`~pyramid.config.Configurator.scan`, which implies that the scan
- should take place in the current package (in this case, ``tutorial``).
- The cookiecutter could have equivalently said ``config.scan('tutorial')``, but
- it chose to omit the package name argument.
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 22
+ :lineno-match:
+ :language: py
-#. *Line 23*. Use the
- :meth:`pyramid.config.Configurator.make_wsgi_app` method
- to return a :term:`WSGI` application.
-Resources and models with ``models.py``
----------------------------------------
+.. _wiki-resources-and-models:
-:app:`Pyramid` uses the word :term:`resource` to describe objects arranged
-hierarchically in a :term:`resource tree`. This tree is consulted by
-:term:`traversal` to map URLs to code. In this application, the resource
-tree represents the site structure, but it *also* represents the
-:term:`domain model` of the application, because each resource is a node
-stored persistently in a :term:`ZODB` database. The ``models.py`` file is
-where the ``zodb`` cookiecutter put the classes that implement our
-resource objects, each of which also happens to be a domain model object.
+Resources and models with ``models`` package
+--------------------------------------------
+
+:app:`Pyramid` uses the word :term:`resource` to describe objects arranged hierarchically in a :term:`resource tree`.
+This tree is consulted by :term:`traversal` to map URLs to code.
+In this application, the resource tree represents the site structure, but it *also* represents the :term:`domain model` of the application.
+Each resource is a node stored persistently in a :term:`ZODB` database.
+The ``models.py`` file is where the ``zodb`` cookiecutter put the classes that implement our resource objects, each of which also happens to be a domain model object.
Here is the source for ``models.py``:
-.. literalinclude:: src/basiclayout/tutorial/models.py
+.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
:linenos:
:language: python
-#. *Lines 4-5*. The ``MyModel`` :term:`resource` class is implemented here.
- Instances of this class are capable of being persisted in :term:`ZODB`
- because the class inherits from the
- :class:`persistent.mapping.PersistentMapping` class. The ``__parent__``
- and ``__name__`` are important parts of the :term:`traversal` protocol.
- By default, set these to ``None`` to indicate that this is the
- :term:`root` object.
-
-#. *Lines 8-12*. ``appmaker`` is used to return the *application
- root* object. It is called on *every request* to the
- :app:`Pyramid` application. It also performs bootstrapping by
- *creating* an application root (inside the ZODB root object) if one
- does not already exist. It is used by the ``root_factory`` we've defined
- in our ``__init__.py``.
-
- Bootstrapping is done by first seeing if the database has the persistent
- application root. If not, we make an instance, store it, and commit the
- transaction. We then return the application root object.
-
-Views With ``views.py``
------------------------
-
-Our cookiecutter generated a default ``views.py`` on our behalf. It
-contains a single view, which is used to render the page shown when you visit
-the URL ``http://localhost:6543/``.
-
-Here is the source for ``views.py``:
-
-.. literalinclude:: src/basiclayout/tutorial/views.py
- :linenos:
- :language: python
+#. *Lines 4-5*.
+ The ``MyModel`` :term:`resource` class is implemented here.
+ Instances of this class are capable of being persisted in :term:`ZODB` because the class inherits from the :class:`persistent.mapping.PersistentMapping` class.
+ The ``__parent__`` and ``__name__`` are important parts of the :term:`traversal` protocol.
+ By default, these are set to ``None`` to indicate that this is the :term:`root` object.
+
+#. *Lines 8-12*.
+ ``appmaker`` is used to return the *application root* object.
+ It is called on *every request* to the :app:`Pyramid` application by virtue of the ``root_factory`` defined in our ``__init__.py``.
+ It also performs bootstrapping by *creating* an application root (inside the ZODB root object) if one does not already exist.
+
+ Bootstrapping is done by first seeing if the database has the persistent application root.
+ If not, then we make an instance, store it, and commit the transaction.
+
+ We then return the application root object.
+
+
+View declarations via the ``views`` package
+-------------------------------------------
+
+Our cookiecutter generated a default ``views`` package on our behalf.
+It contains a two views.
+
+The first view is used to render the page shown when you visit the URL ``http://localhost:6543/``.
+Open ``tutorial/views/default.py`` in the ``views`` package.
+It should already contain the following:
+
+.. literalinclude:: src/basiclayout/tutorial/views/default.py
+ :linenos:
+ :language: python
Let's try to understand the components in this module:
-#. *Lines 1-2*. Perform some dependency imports.
-
-#. *Line 5*. Use the :func:`pyramid.view.view_config` :term:`configuration
- decoration` to perform a :term:`view configuration` registration. This
- view configuration registration will be activated when the application is
- started. It will be activated by virtue of it being found as the result
- of a :term:`scan` (when Line 14 of ``__init__.py`` is run).
-
- The ``@view_config`` decorator accepts a number of keyword arguments. We
- use two keyword arguments here: ``context`` and ``renderer``.
-
- The ``context`` argument signifies that the decorated view callable should
- only be run when :term:`traversal` finds the ``tutorial.models.MyModel``
- :term:`resource` to be the :term:`context` of a request. In English, this
- means that when the URL ``/`` is visited, because ``MyModel`` is the root
- model, this view callable will be invoked.
-
- The ``renderer`` argument names an :term:`asset specification` of
- ``templates/mytemplate.pt``. This asset specification points at a
- :term:`Chameleon` template which lives in the ``mytemplate.pt`` file
- within the ``templates`` directory of the ``tutorial`` package. And
- indeed if you look in the ``templates`` directory of this package, you'll
- see a ``mytemplate.pt`` template file, which renders the default home page
- of the generated project. This asset specification is *relative* (to the
- view.py's current package). Alternatively we could have used the
- absolute asset specification ``tutorial:templates/mytemplate.pt``, but
- chose to use the relative version.
-
- Since this call to ``@view_config`` doesn't pass a ``name`` argument, the
- ``my_view`` function which it decorates represents the "default" view
- callable used when the context is of the type ``MyModel``.
-
-#. *Lines 6-7*. We define a :term:`view callable` named ``my_view``, which
- we decorated in the step above. This view callable is a *function* we
- write generated by the ``zodb`` cookiecutter that is given a
- ``request`` and which returns a dictionary. The ``mytemplate.pt``
- :term:`renderer` named by the asset specification in the step above will
- convert this dictionary to a :term:`response` on our behalf.
-
- The function returns the dictionary ``{'project':'tutorial'}``. This
- dictionary is used by the template named by the ``mytemplate.pt`` asset
- specification to fill in certain values on the page.
+#. *Lines 1-3*.
+ Perform some dependency imports.
+
+#. *Line 6*.
+ Use the :func:`pyramid.view.view_config` :term:`configuration decoration` to perform a :term:`view configuration` registration.
+ This view configuration registration will be activated when the application is started.
+ Remember in our application's ``__init__.py`` when we executed the :meth:`pyramid.config.Configurator.scan` method ``config.scan()``?
+ By calling the scan method, Pyramid's configurator will find and process this ``@view_config`` decorator, and create a view configuration within our application.
+ Without being processed by ``scan``, the decorator effectively does nothing.
+ ``@view_config`` is inert without being detected via a :term:`scan`.
+
+ The ``@view_config`` decorator accepts a number of keyword arguments.
+ We use two keyword arguments here: ``context`` and ``renderer``.
+
+ The ``context`` argument signifies that the decorated view callable ``my_view`` should only be run when :term:`traversal` finds the ``tutorial.models.MyModel`` :term:`resource` as the :term:`context` of a request.
+ In English this means that when the URL ``/`` is visited, and because ``MyModel`` is the root model, this view callable will be invoked.
+
+ The ``renderer`` argument names an :term:`asset specification` of ``templates/mytemplate.pt``.
+ This asset specification points at a :term:`Chameleon` template which lives in the ``mytemplate.pt`` file within the ``templates`` directory of the ``tutorial`` package.
+ And indeed if you look in the ``templates`` directory of this package, you will see a ``mytemplate.pt`` template file
+ This template renders the default home page of the generated project.
+ This asset specification is *relative* to the ``views`` package.
+ Alternatively we could have used the absolute asset specification ``tutorial:templates/mytemplate.pt``.
+
+ Since this call to ``@view_config`` doesn't pass a ``name`` argument, the ``my_view`` function which it decorates represents the "default" view callable used when the context is of the type ``MyModel``.
+
+#. *Lines 7-8*.
+ A :term:`view callable` named ``my_view`` is defined, which is decorated in the step above.
+ This view callable is a *function* generated by the cookiecutter.
+ It is given a single argument, ``request``.
+ This is the standard call signature for a Pyramid :term:`view callable`.
+ The function returns the dictionary ``{'project': 'myproj'}``.
+ This dictionary is used by the template named by the ``mytemplate.pt`` asset specification to fill in certain values on the page.
+
+Let us open ``tutorial/views/default.py`` in the ``views`` package to look at the second view.
+
+.. literalinclude:: src/basiclayout/tutorial/views/notfound.py
+ :linenos:
+ :language: python
+
+Without repeating ourselves, we will point out the differences between this view and the previous.
+
+#. *Line 4*.
+ The ``notfound_view`` function is decorated with ``@notfound_view_config``.
+ This decorator registers a :term:`Not Found View` using :meth:`pyramid.config.Configurator.add_notfound_view`.
+
+ The ``renderer`` argument names an :term:`asset specification` of ``templates/404.pt``.
+
+#. *Lines 5-7*.
+ A :term:`view callable` named ``notfound_view`` is defined, which is decorated in the step above.
+ It sets the HTTP response status code to ``404``.
+ The function returns an empty dictionary to the template ``404.pt``, which accepts no parameters anyway.
+
Configuration in ``development.ini``
------------------------------------
-The ``development.ini`` (in the ``tutorial`` :term:`project` directory, as
-opposed to the ``tutorial`` :term:`package` directory) looks like this:
+The ``development.ini`` (in the ``tutorial`` :term:`project` directory, as opposed to the ``tutorial`` :term:`package` directory) looks like this:
.. literalinclude:: src/basiclayout/development.ini
:language: ini
-Note the existence of a ``[app:main]`` section which specifies our WSGI
-application. Our ZODB database settings are specified as the
-``zodbconn.uri`` setting within this section. This value, and the other
-values within this section, are passed as ``**settings`` to the ``main``
-function we defined in ``__init__.py`` when the server is started via
-``pserve``.
+Note the existence of a ``[app:main]`` section which specifies our WSGI application.
+Our ZODB database settings are specified as the ``zodbconn.uri`` setting within this section.
+When the server is started via ``pserve``, the values within this section are passed as ``**settings`` to the ``main`` function defined in ``__init__.py``.
diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst
index e973cfdfe..3a340e6f7 100644
--- a/docs/tutorials/wiki/definingmodels.rst
+++ b/docs/tutorials/wiki/definingmodels.rst
@@ -4,90 +4,111 @@
Defining the Domain Model
=========================
-The first change we'll make to our stock cookiecutter-generated application will
-be to define two :term:`resource` constructors, one representing a wiki page,
-and another representing the wiki as a mapping of wiki page names to page
-objects. We'll do this inside our ``models.py`` file.
+Let's make changes to our stock cookiecutter-generated application.
+We will define two :term:`resource` constructors, one representing a wiki page, and another representing the wiki as a mapping of wiki page names to page objects.
+We will do this inside our ``models.py`` file.
-Because we're using :term:`ZODB` to represent our
-:term:`resource tree`, each of these resource constructors represents a
-:term:`domain model` object, so we'll call these constructors "model
-constructors". Both our Page and Wiki constructors will be class objects. A
-single instance of the "Wiki" class will serve as a container for "Page"
-objects, which will be instances of the "Page" class.
+Because we are using :term:`ZODB` to represent our :term:`resource tree`, each of these resource constructors represents a :term:`domain model` object.
+We will call these constructors "model constructors".
+Both our ``Page`` and ``Wiki`` constructors will be class objects.
+A single instance of the "Wiki" class will serve as a container for "Page" objects, which will be instances of the "Page" class.
+
+.. seealso::
+
+ We will introduce a lot of concepts throughout the remainder of this tutorial.
+ See also the chapter :ref:`resources_chapter` for a complete description of resources and the chapter :ref:`traversal_chapter` for the technical details of how traversal works in Pyramid.
Delete the database
-------------------
-In the next step, we're going to remove the ``MyModel`` Python model
-class from our ``models.py`` file. Since this class is referred to within
-our persistent storage (represented on disk as a file named ``Data.fs``),
-we'll have strange things happen the next time we want to visit the
-application in a browser. Remove the ``Data.fs`` from the ``tutorial``
-directory before proceeding any further. It's always fine to do this as long
-as you don't care about the content of the database; the database itself will
-be recreated as necessary.
+In the next step, we will remove the ``MyModel`` Python model class from our ``models`` package.
+Since this class is referred to within our persistent storage (represented on disk as a file named ``Data.fs``), we will have strange things happen the next time we want to visit the application in a browser.
+
+Remove the ``Data.fs`` from the ``tutorial`` directory before proceeding any further.
+It is always fine to do this as long as you don't care about the content of the database.
+The database itself will be recreated as necessary.
-Edit ``models.py``
-------------------
+
+Edit ``models`` package
+-----------------------
.. note::
- There is nothing special about the filename ``models.py``. A
- project may have many models throughout its codebase in arbitrarily named
- files. Files implementing models often have ``model`` in their filenames
- or they may live in a Python subpackage of your application package named
- ``models``, but this is only by convention.
+ There is nothing special about the package name ``models``.
+ A project may have many models throughout its codebase in arbitrarily named files and directories.
+ Files that implement models often have ``model`` in their names, or they may live in a Python subpackage of your application package named ``models``, but this is only by convention.
-Open ``tutorial/models.py`` file and edit it to look like the following:
+Open ``tutorial/models/__init__.py`` file and edit it to look like the following:
-.. literalinclude:: src/models/tutorial/models.py
+.. literalinclude:: src/models/tutorial/models/__init__.py
:linenos:
:language: python
-The first thing we want to do is remove the ``MyModel`` class from the
-generated ``models.py`` file. The ``MyModel`` class is only a sample and
-we're not going to use it.
-
-Then we'll add an import at the top for the :class:`persistent.Persistent` class. We'll use this for a new ``Page`` class in a moment.
-
-Then we'll add a ``Wiki`` class. We want it to inherit from the
-:class:`persistent.mapping.PersistentMapping` class because it provides
-mapping behavior, and it makes sure that our Wiki page is stored as a
-"first-class" persistent object in our ZODB database.
-
-Our ``Wiki`` class should have two attributes set to ``None`` at
-class scope: ``__parent__`` and ``__name__``. If a model has a
-``__parent__`` attribute of ``None`` in a traversal-based :app:`Pyramid`
-application, it means that it's the :term:`root` model. The ``__name__``
-of the root model is also always ``None``.
-
-Then we'll add a ``Page`` class. This class should inherit from the
-:class:`persistent.Persistent` class. We'll also give it an ``__init__``
-method that accepts a single parameter named ``data``. This parameter will
-contain the :term:`reStructuredText` body representing the wiki page content.
-Note that ``Page`` objects don't have an initial ``__name__`` or
-``__parent__`` attribute. All objects in a traversal graph must have a
-``__name__`` and a ``__parent__`` attribute. We don't specify these here
-because both ``__name__`` and ``__parent__`` will be set by a :term:`view`
-function when a Page is added to our Wiki mapping.
-
-As a last step, we want to change the ``appmaker`` function in our
-``models.py`` file so that the :term:`root` :term:`resource` of our
-application is a Wiki instance. We'll also slot a single page object (the
-front page) into the Wiki within the ``appmaker``. This will provide
-:term:`traversal` a :term:`resource tree` to work against when it attempts to
-resolve URLs to resources.
+Remove the ``MyModel`` class from the generated ``models/__init__.py`` file.
+The ``MyModel`` class is only a sample and we're not going to use it.
+
+Next we add an import at the top for the :class:`persistent.Persistent` class.
+We will use this for a new ``Page`` class in a moment.
+
+.. literalinclude:: src/models/tutorial/models/__init__.py
+ :lines: 1-2
+ :lineno-match:
+ :emphasize-lines: 1
+ :language: py
+
+Then we add a ``Wiki`` class.
+
+.. literalinclude:: src/models/tutorial/models/__init__.py
+ :lines: 4-6
+ :lineno-match:
+ :language: py
+
+We want it to inherit from the :class:`persistent.mapping.PersistentMapping` class because it provides mapping behavior.
+It also makes sure that our ``Wiki`` page is stored as a "first-class" persistent object in our ZODB database.
+
+Our ``Wiki`` class should have two attributes set to ``None`` at class scope: ``__parent__`` and ``__name__``.
+If a model has a ``__parent__`` attribute of ``None`` in a traversal-based :app:`Pyramid` application, it means that it is the :term:`root` model.
+The ``__name__`` of the root model is also always ``None``.
+
+Now we add a ``Page`` class.
+
+.. literalinclude:: src/models/tutorial/models/__init__.py
+ :lines: 8-10
+ :lineno-match:
+ :language: py
+
+This class should inherit from the :class:`persistent.Persistent` class.
+We will give it an ``__init__`` method that accepts a single parameter named ``data``.
+This parameter will contain the :term:`reStructuredText` body representing the wiki page content.
+
+Note that ``Page`` objects don't have an initial ``__name__`` or ``__parent__`` attribute.
+All objects in a traversal graph must have a ``__name__`` and a ``__parent__`` attribute.
+We do not specify these here.
+Instead both ``__name__`` and ``__parent__`` will be set by a :term:`view` function when a ``Page`` is added to our ``Wiki`` mapping.
+We will create this function in the next chapter.
+
+As a last step, edit the ``appmaker`` function.
+
+.. literalinclude:: src/models/tutorial/models/__init__.py
+ :lines: 12-20
+ :lineno-match:
+ :emphasize-lines: 4-8
+ :language: py
+
+The :term:`root` :term:`resource` of our application is a Wiki instance.
+
+We will also slot a single page object (the front page) into the Wiki within the ``appmaker``.
+This will provide :term:`traversal` a :term:`resource tree` to work against when it attempts to resolve URLs to resources.
+
View the application in a browser
---------------------------------
-We can't. At this point, our system is in a "non-runnable" state; we'll need
-to change view-related files in the next chapter to be able to start the
-application successfully. If you try to start the application (See
-:ref:`wiki-start-the-application`), you'll wind
-up with a Python traceback on your console that ends with this exception:
+We cannot.
+At this point, our system is in a "non-runnable" state
+We will need to change view-related files in the next chapter to be able to start the application successfully.
+If you try to start the application (See :ref:`wiki-start-the-application`), you will wind up with a Python traceback on your console that ends with this exception:
.. code-block:: text
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index d584a1b41..bd8dc6ecf 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -4,44 +4,37 @@
Defining Views
==============
-A :term:`view callable` in a :term:`traversal`-based :app:`Pyramid`
-application is typically a simple Python function that accepts two
-parameters: :term:`context` and :term:`request`. A view callable is
-assumed to return a :term:`response` object.
+A :term:`view callable` in a :term:`traversal`-based :app:`Pyramid` application is typically a simple Python function that accepts two parameters: :term:`context` and :term:`request`.
+A view callable is assumed to return a :term:`response` object.
.. note::
- A :app:`Pyramid` view can also be defined as callable
- which accepts *only* a :term:`request` argument. You'll see
- this one-argument pattern used in other :app:`Pyramid` tutorials
- and applications. Either calling convention will work in any
- :app:`Pyramid` application; the calling conventions can be used
- interchangeably as necessary. In :term:`traversal`-based applications,
- URLs are mapped to a context :term:`resource`, and since our
- :term:`resource tree` also represents our application's
- "domain model", we're often interested in the context because
- it represents the persistent storage of our application. For
- this reason, in this tutorial we define views as callables that
- accept ``context`` in the callable argument list. If you do
- need the ``context`` within a view function that only takes
- the request as a single argument, you can obtain it via
- ``request.context``.
-
-We're going to define several :term:`view callable` functions, then wire them
-into :app:`Pyramid` using some :term:`view configuration`.
+ A :app:`Pyramid` view can also be defined as callable which accepts *only* a :term:`request` argument.
+ You will see this one-argument pattern used in other :app:`Pyramid` tutorials and applications.
+ Either calling convention will work in any :app:`Pyramid` application.
+ The calling conventions can be used interchangeably as necessary.
+
+ In :term:`traversal`-based applications, URLs are mapped to a context :term:`resource`.
+ Since our :term:`resource tree` also represents our application's "domain model", we are often interested in the context because it represents the persistent storage of our application.
+ For this reason, in this tutorial we define views as callables that accept ``context`` in the callable argument list.
+ If you do need the ``context`` within a view function that only takes the request as a single argument, you can obtain it via ``request.context``.
+
+We will define several :term:`view callable` functions, then wire them into :app:`Pyramid` using some :term:`view configuration`.
+
+.. seealso::
+
+ This chapter will introduce more concepts, as did the previous.
+ See also the chapter :ref:`resources_chapter` for a complete description of resources and the chapter :ref:`traversal_chapter` for the technical details of how traversal works in Pyramid.
Declaring Dependencies in Our ``setup.py`` File
===============================================
-The view code in our application will depend on a package which is not a
-dependency of the original "tutorial" application. The original "tutorial"
-application was generated by the cookiecutter; it doesn't know
-about our custom application requirements.
+The view code in our application will depend on a package which is not a dependency of the original "tutorial" application.
+The original "tutorial" application was generated by the cookiecutter.
+It does not know about our custom application requirements.
-We need to add a dependency on the ``docutils`` package to our ``tutorial``
-package's ``setup.py`` file by assigning this dependency to the ``requires``
-parameter in the ``setup()`` function.
+We need to add a dependency on the ``docutils`` package to our ``tutorial`` package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function.
Open ``setup.py`` and edit it to look like the following:
@@ -52,17 +45,15 @@ Open ``setup.py`` and edit it to look like the following:
Only the highlighted line needs to be added.
+
.. _wiki-running-pip-install:
Running ``pip install -e .``
============================
-Since a new software dependency was added, you will need to run ``pip install
--e .`` again inside the root of the ``tutorial`` package to obtain and register
-the newly added dependency distribution.
+Since a new software dependency was added, you need to run ``pip install -e .`` again inside the root of the ``tutorial`` package to obtain and register the newly added dependency distribution.
-Make sure your current working directory is the root of the project (the
-directory in which ``setup.py`` lives) and execute the following command.
+Make sure your current working directory is the root of the project (the directory in which ``setup.py`` lives) and execute the following command.
On Unix:
@@ -78,237 +69,238 @@ On Windows:
cd tutorial
%VENV%\Scripts\pip install -e .
-Success executing this command will end with a line to the console something
-like:
+Success executing this command will end with a line to the console similar to the following:
.. code-block:: text
- Successfully installed docutils-0.13.1 tutorial
+ Successfully installed docutils-0.14 tutorial
-Adding view functions in ``views.py``
-=====================================
+Adding view functions in the ``views`` package
+==============================================
-It's time for a major change. Open ``tutorial/views.py`` and edit it to look
-like the following:
+It is time for a major change.
+Open ``tutorial/views/default.py`` and edit it to look like the following:
-.. literalinclude:: src/views/tutorial/views.py
+.. literalinclude:: src/views/tutorial/views/default.py
:linenos:
:language: python
We added some imports and created a regular expression to find "WikiWords".
-We got rid of the ``my_view`` view function and its decorator that was added
-when originally rendered after we selected the ``zodb`` backend option in the
-cookiecutter. It was only an example and isn't relevant to our application.
+We got rid of the ``my_view`` view function and its decorator that was added when originally rendered after we selected the ``zodb`` backend option in the cookiecutter.
+It was only an example and is not relevant to our application.
-Then we added four :term:`view callable` functions to our ``views.py``
-module:
+Then we added four :term:`view callable` functions to our ``views.py`` module:
* ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL.
* ``view_page()`` - Displays an individual page.
* ``add_page()`` - Allows the user to add a page.
* ``edit_page()`` - Allows the user to edit a page.
-We'll describe each one briefly in the following sections.
+We will describe each one briefly in the following sections.
.. note::
- There is nothing special about the filename ``views.py``. A project may
- have many view callables throughout its codebase in arbitrarily named
- files. Files implementing view callables often have ``view`` in their
- filenames (or may live in a Python subpackage of your application package
- named ``views``), but this is only by convention.
+ There is nothing special about the filename ``views.py``.
+ A project may have many view callables throughout its codebase in arbitrarily named files.
+ Files that implement view callables often have ``view`` in their names (or may live in a Python subpackage of your application package named ``views``), but this is only by convention.
+
The ``view_wiki`` view function
-------------------------------
Following is the code for the ``view_wiki`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 12-14
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 13-15
:lineno-match:
:language: python
-.. note:: In our code, we use an *import* that is *relative* to our package
- named ``tutorial``, meaning we can omit the name of the package in the
- ``import`` and ``context`` statements. In our narrative, however, we refer
- to a *class* and thus we use the *absolute* form, meaning that the name of
- the package is included.
-
-``view_wiki()`` is the :term:`default view` that gets called when a request is
-made to the root URL of our wiki. It always redirects to an URL which
-represents the path to our "FrontPage".
-
-We provide it with a ``@view_config`` decorator which names the class
-``tutorial.models.Wiki`` as its context. This means that when a Wiki resource
-is the context and no :term:`view name` exists in the request, then this view
-will be used. The view configuration associated with ``view_wiki`` does not
-use a ``renderer`` because the view callable always returns a :term:`response`
-object rather than a dictionary. No renderer is necessary when a view returns
-a response object.
-
-The ``view_wiki`` view callable always redirects to the URL of a Page resource
-named "FrontPage". To do so, it returns an instance of the
-:class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement
-the :class:`pyramid.interfaces.IResponse` interface, like
-:class:`pyramid.response.Response` does). It uses the
-:meth:`pyramid.request.Request.route_url` API to construct an URL to the
-``FrontPage`` page resource (i.e., ``http://localhost:6543/FrontPage``), and
-uses it as the "location" of the ``HTTPFound`` response, forming an HTTP
-redirect.
+.. note::
+
+ In our code, we use an *import* that is *relative* to our package named ``tutorial``.
+ This means we can omit the name of the package in the ``import`` and ``context`` statements.
+ In our narrative, however, we refer to a *class* and thus we use the *absolute* form.
+ This means that the name of the package is included.
+
+``view_wiki()`` is the :term:`default view` that gets called when a request is made to the root URL of our wiki.
+It always redirects to an URL which represents the path to our ``FrontPage``.
+
+We provide it with a ``@view_config`` decorator which names the class ``tutorial.models.Wiki`` as its context.
+This means that when a ``Wiki`` resource is the context and no :term:`view name` exists in the request, then this view will be used.
+The view configuration associated with ``view_wiki`` does not use a ``renderer`` because the view callable always returns a :term:`response` object rather than a dictionary.
+No renderer is necessary when a view returns a response object.
+
+The ``view_wiki`` view callable always redirects to the URL of a ``Page`` resource named ``FrontPage``.
+To do so, it returns an instance of the :class:`pyramid.httpexceptions.HTTPFound` class.
+Instances of this class implement the :class:`pyramid.interfaces.IResponse` interface, similar to :class:`pyramid.response.Response`.
+It uses the :meth:`pyramid.request.Request.route_url` API to construct an URL to the ``FrontPage`` page resource (in other words, ``http://localhost:6543/FrontPage``), and uses it as the ``location`` of the ``HTTPFound`` response, forming an HTTP redirect.
+
The ``view_page`` view function
-------------------------------
Here is the code for the ``view_page`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 16-33
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 18-35
:lineno-match:
:language: python
-The ``view_page`` function is configured to respond as the default view
-of a Page resource. We provide it with a ``@view_config`` decorator which
-names the class ``tutorial.models.Page`` as its context. This means that
-when a Page resource is the context, and no :term:`view name` exists in the
-request, this view will be used. We inform :app:`Pyramid` this view will use
-the ``templates/view.pt`` template file as a ``renderer``.
-
-The ``view_page`` function generates the :term:`reStructuredText` body of a
-page (stored as the ``data`` attribute of the context passed to the view; the
-context will be a ``Page`` resource) as HTML. Then it substitutes an HTML
-anchor for each *WikiWord* reference in the rendered HTML using a compiled
-regular expression.
-
-The curried function named ``check`` is used as the first argument to
-``wikiwords.sub``, indicating that it should be called to provide a value for
-each WikiWord match found in the content. If the wiki (our page's
-``__parent__``) already contains a page with the matched WikiWord name, the
-``check`` function generates a view link to be used as the substitution value
-and returns it. If the wiki does not already contain a page with the
-matched WikiWord name, the function generates an "add" link as the
-substitution value and returns it.
-
-As a result, the ``content`` variable is now a fully formed bit of HTML
-containing various view and add links for WikiWords based on the content of
-our current page resource.
-
-We then generate an edit URL because it's easier to do here than in the
-template, and we wrap up a number of arguments in a dictionary and return
-it.
-
-The arguments we wrap into a dictionary include ``page``, ``content``, and
-``edit_url``. As a result, the *template* associated with this view callable
-(via ``renderer=`` in its configuration) will be able to use these names to
-perform various rendering tasks. The template associated with this view
-callable will be a template which lives in ``templates/view.pt``.
-
-Note the contrast between this view callable and the ``view_wiki`` view
-callable. In the ``view_wiki`` view callable, we unconditionally return a
-:term:`response` object. In the ``view_page`` view callable, we return a
-*dictionary*. It is *always* fine to return a :term:`response` object from a
-:app:`Pyramid` view. Returning a dictionary is allowed only when there is a
-:term:`renderer` associated with the view callable in the view configuration.
+The ``view_page`` function is configured to respond as the default view of a ``Page`` resource.
+We provide it with a ``@view_config`` decorator which names the class ``tutorial.models.Page`` as its context.
+This means that when a ``Page`` resource is the context, and no :term:`view name` exists in the request, this view will be used.
+We inform :app:`Pyramid` this view will use the ``templates/view.pt`` template file as a ``renderer``.
+
+The ``view_page`` function generates the :term:`reStructuredText` body of a page as HTML.
+The body is stored as the ``data`` attribute of the context passed to the view.
+The context will be a ``Page`` resource.
+Then it substitutes an HTML anchor for each *WikiWord* reference in the rendered HTML using a compiled regular expression.
+
+The curried function named ``check`` is used as the first argument to ``wikiwords.sub``, indicating that it should be called to provide a value for each ``WikiWord`` match found in the content.
+If the wiki (our page's ``__parent__``) already contains a page with the matched ``WikiWord`` name, the ``check`` function generates a view link to be used as the substitution value and returns it.
+If the wiki does not already contain a page with the matched ``WikiWord`` name, the function generates an "add" link as the substitution value and returns it.
+
+As a result, the ``page_text`` variable is now a fully formed bit of HTML containing various view and add links for ``WikiWord``\s based on the content of our current page resource.
+
+We then generate an edit URL because it is easier to do here than in the template.
+Finally we wrap up a number of arguments in a dictionary and return it.
+
+The arguments we wrap into a dictionary include ``page``, ``page_text``, and ``edit_url``.
+As a result, the *template* associated with this view callable (via ``renderer=`` in its configuration) will be able to use these names to perform various rendering tasks.
+The template associated with this view callable will be a template which lives in ``templates/view.pt``.
+
+Note the contrast between this view callable and the ``view_wiki`` view callable.
+In the ``view_wiki`` view callable, we unconditionally return a :term:`response` object.
+In the ``view_page`` view callable, we return a *dictionary*. It is *always* fine to return a :term:`response` object from a :app:`Pyramid` view.
+Returning a dictionary is allowed only when there is a :term:`renderer` associated with the view callable in the view configuration.
+
The ``add_page`` view function
------------------------------
Here is the code for the ``add_page`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 35-50
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 38-53
:lineno-match:
:language: python
-The ``add_page`` function is configured to respond when the context resource
-is a Wiki and the :term:`view name` is ``add_page``. We provide it with a
-``@view_config`` decorator which names the string ``add_page`` as its
-:term:`view name` (via ``name=``), the class ``tutorial.models.Wiki`` as its
-context, and the renderer named ``templates/edit.pt``. This means that when a
-Wiki resource is the context, and a :term:`view name` named ``add_page``
-exists as the result of traversal, this view will be used. We inform
-:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a
-``renderer``. We share the same template between add and edit views, thus
-``edit.pt`` instead of ``add.pt``.
-
-The ``add_page`` function will be invoked when a user clicks on a WikiWord
-which isn't yet represented as a page in the system. The ``check`` function
-within the ``view_page`` view generates URLs to this view. It also acts as a
-handler for the form that is generated when we want to add a page resource.
-The ``context`` of the ``add_page`` view is always a Wiki resource (*not* a
-Page resource).
-
-The request :term:`subpath` in :app:`Pyramid` is the sequence of names that
-are found *after* the :term:`view name` in the URL segments given in the
-``PATH_INFO`` of the WSGI request as the result of :term:`traversal`. If our
-add view is invoked via, e.g., ``http://localhost:6543/add_page/SomeName``,
-the :term:`subpath` will be a tuple: ``('SomeName',)``.
-
-The add view takes the zero\ :sup:`th` element of the subpath (the wiki page name),
-and aliases it to the name attribute in order to know the name of the page
-we're trying to add.
-
-If the view rendering is *not* a result of a form submission (if the
-expression ``'form.submitted' in request.params`` is ``False``), the view
-renders a template. To do so, it generates a "save url" which the template
-uses as the form post URL during rendering. We're lazy here, so we're trying
-to use the same template (``templates/edit.pt``) for the add view as well as
-the page edit view. To do so, we create a dummy Page resource object in
-order to satisfy the edit form's desire to have *some* page object exposed as
-``page``, and we'll render the template to a response.
-
-If the view rendering *is* a result of a form submission (if the expression
-``'form.submitted' in request.params`` is ``True``), we grab the page body
-from the form data, create a Page object using the name in the subpath and
-the page body, and save it into "our context" (the Wiki) using the
-``__setitem__`` method of the context. We then redirect back to the
-``view_page`` view (the default view for a page) for the newly created page.
+The ``add_page`` function is configured to respond when the context resource is a ``Wiki`` and the :term:`view name` is ``add_page``.
+We provide it with a ``@view_config`` decorator which names the string ``add_page`` as its :term:`view name` (via ``name=``), the class ``tutorial.models.Wiki`` as its context, and the renderer named ``templates/edit.pt``.
+This means that when a ``Wiki`` resource is the context, and a :term:`view name` named ``add_page`` exists as the result of traversal, then this view will be used.
+We inform :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a ``renderer``.
+We share the same template between add and edit views, thus ``edit.pt`` instead of ``add.pt``.
+
+The ``add_page`` function will be invoked when a user clicks on a ``WikiWord`` that is not yet represented as a page in the system.
+The ``check`` function within the ``view_page`` view generates URLs to this view.
+It also acts as a handler for the form that is generated when we want to add a page resource.
+The ``context`` of the ``add_page`` view is always a ``Wiki`` resource (*not* a ``Page`` resource).
+
+The request :term:`subpath` in :app:`Pyramid` is the sequence of names that are found *after* the :term:`view name` in the URL segments given in the ``PATH_INFO`` of the WSGI request as the result of :term:`traversal`.
+If our add view is invoked via, for example, ``http://localhost:6543/add_page/SomeName``, then the :term:`subpath` will be a tuple ``('SomeName',)``.
+
+The add view takes the zero\ :sup:`th` element of the subpath (the wiki page name), then aliases it to the name attribute to know the name of the page we are trying to add.
+
+If the view rendering is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``False``), then the view renders a template.
+To do so, it generates a ``save_url`` which the template uses as the form post URL during rendering.
+We are lazy here, so we try to use the same template (``templates/edit.pt``) for both the add and edit views.
+To do so, we create a dummy ``Page`` resource object to satisfy the edit form's desire to have *some* page object exposed as ``page``.
+We then set the ``Page`` object's ``__name__`` and ``__parent__``.
+Then we will render the template to a response.
+
+If the view rendering *is* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``True``), then do the following:
+
+- Grab the page body from the form data as ``body``.
+- Create a ``Page`` object using the name in the subpath and the page body as ``page``.
+- Set the ``Page`` object's ``__name__`` and ``__parent__``.
+- Save it into "our context" (the ``Wiki``) using the ``__setitem__`` method of the context.
+- We then redirect back to the ``view_page`` view (the default view for a page) for the newly created page.
+
+.. seealso::
+
+ In the :ref:`previous chapter <wiki_defining_the_domain_model>`, we mentioned that all objects in a traversal graph must have a ``__name__`` and a ``__parent__`` attribute.
+ That provides location awareness for resources.
+ See also the section on :ref:`location_aware` in the :ref:`resources_chapter` chapter for a complete discussion.
+
The ``edit_page`` view function
-------------------------------
Here is the code for the ``edit_page`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 52-60
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 56-64
:lineno-match:
:language: python
-The ``edit_page`` function is configured to respond when the context is
-a Page resource and the :term:`view name` is ``edit_page``. We provide it
-with a ``@view_config`` decorator which names the string ``edit_page`` as its
-:term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its
-context, and the renderer named ``templates/edit.pt``. This means that when
-a Page resource is the context, and a :term:`view name` exists as the result
-of traversal named ``edit_page``, this view will be used. We inform
-:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as
-a ``renderer``.
-
-The ``edit_page`` function will be invoked when a user clicks the "Edit this
-Page" button on the view form. It renders an edit form but it also acts as
-the form post view callable for the form it renders. The ``context`` of the
-``edit_page`` view will *always* be a Page resource (never a Wiki resource).
-
-If the view execution is *not* a result of a form submission (if the
-expression ``'form.submitted' in request.params`` is ``False``), the view
-simply renders the edit form, passing the page resource, and a ``save_url``
-which will be used as the action of the generated form.
-
-If the view execution *is* a result of a form submission (if the expression
-``'form.submitted' in request.params`` is ``True``), the view grabs the
-``body`` element of the request parameter and sets it as the ``data``
-attribute of the page context. It then redirects to the default view of the
-context (the page), which will always be the ``view_page`` view.
+The ``edit_page`` function is configured to respond when the context is a ``Page`` resource and the :term:`view name` is ``edit_page``.
+We provide it with a ``@view_config`` decorator which names the string ``edit_page`` as its :term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its context, and the renderer named ``templates/edit.pt``.
+This means that when a ``Page`` resource is the context, and a :term:`view name` exists as the result of traversal named ``edit_page``, this view will be used.
+We inform :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a ``renderer``.
+
+The ``edit_page`` function will be invoked when a user clicks the "Edit this Page" button on the view form.
+It renders an edit form.
+It also acts as the form post view callable for the form it renders.
+The ``context`` of the ``edit_page`` view will *always* be a ``Page`` resource (never a ``Wiki`` resource).
+
+If the view execution is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``False``), then the view renders the edit form, passing the page resource, and a ``save_url`` which will be used as the action of the generated form.
+
+If the view execution *is* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``True``), the view grabs the ``body`` element of the request parameter and sets it as the ``data`` attribute of the page context.
+It then redirects to the default view of the context (the page), which will always be the ``view_page`` view.
+
+
+Modifying the ``notfound_view`` in ``notfound.py``
+--------------------------------------------------
+
+We have one more view to modify.
+Open ``tutorial/views/notfound.py`` and make the changes shown by the emphasized lines.
+
+.. literalinclude:: src/views/tutorial/views/notfound.py
+ :linenos:
+ :language: python
+ :emphasize-lines: 3-4, 9-12
+
+We need to import the ``Page`` from our models.
+We eventually return a ``Page`` object as ``page`` into the template ``layout.pt`` to display its name in the title tag.
+
Adding templates
================
-The ``view_page``, ``add_page`` and ``edit_page`` views that we've added
-reference a :term:`template`. Each template is a :term:`Chameleon`
-:term:`ZPT` template. These templates will live in the ``templates``
-directory of our tutorial package. Chameleon templates must have a ``.pt``
-extension to be recognized as such.
+The ``view_page``, ``add_page``, and ``edit_page`` views that we added reference a :term:`template`.
+Each template is a :term:`Chameleon` :term:`ZPT` template.
+These templates will live in the ``templates`` directory of our tutorial package.
+Chameleon templates must have a ``.pt`` extension to be recognized as such.
+
+
+The ``layout.pt`` template
+--------------------------
+
+Update ``tutorial/templates/layout.pt`` with the following content, as indicated by the emphasized lines:
+
+.. literalinclude:: src/views/tutorial/templates/layout.pt
+ :linenos:
+ :emphasize-lines: 11-12, 37-41
+ :language: html
+
+Since we are using a templating engine, we can factor common boilerplate out of our page templates into reusable components.
+We can do this via :term:`METAL` macros and slots.
+
+- The cookiecutter defined a macro named ``layout`` (line 1).
+ This macro consists of the entire template.
+- We changed the ``title`` tag to use the ``name`` attribute of a ``page`` object, or if it does not exist then the page title (lines 11-12).
+- The cookiecutter defined a macro customization point or `slot` (line 36).
+ This slot is inside the macro ``layout``.
+ Therefore it can be replaced by content, customizing the macro.
+- We added a ``div`` element with a link to allow the user to return to the front page (lines 37-41).
+- We removed the row of icons and links from the original cookiecutter.
+
+.. seealso::
+
+ Please refer to the Chameleon documentation for more information about using `METAL <https://chameleon.readthedocs.io/en/latest/>`_ for defining and using macros and slots.
+
The ``view.pt`` template
------------------------
@@ -318,16 +310,18 @@ Rename ``tutorial/templates/mytemplate.pt`` to ``tutorial/templates/view.pt`` an
.. literalinclude:: src/views/tutorial/templates/view.pt
:linenos:
:language: html
- :emphasize-lines: 11-12,37-52
+ :emphasize-lines: 5-16
This template is used by ``view_page()`` for displaying a single
wiki page. It includes:
-- A ``div`` element that is replaced with the ``content`` value provided by
- the view (lines 37-39). ``content`` contains HTML, so the ``structure``
- keyword is used to prevent escaping it (i.e., changing ">" to "&gt;", etc.)
-- A link that points at the "edit" URL which invokes the ``edit_page`` view
- for the page being viewed (lines 41-43).
+- The use of a macro to load the entire template ``layout.pt``.
+- The template fills the slot named ``content`` (line 2) with a ``div`` element.
+- A ``div`` element that is replaced with the ``page_text`` value provided by the view (line 5).
+ ``page_text`` contains HTML, so the ``structure`` keyword is used to prevent escaping HTML entities, such as changing ``>`` to ``&gt;``.
+- A link that points at the "edit" URL, which invokes the ``edit_page`` view for the page being viewed (lines 9-11).
+- A ``span`` whose content is replaced by the name of the page, if present.
+
The ``edit.pt`` template
------------------------
@@ -337,59 +331,52 @@ Copy ``tutorial/templates/view.pt`` to ``tutorial/templates/edit.pt`` and edit t
.. literalinclude:: src/views/tutorial/templates/edit.pt
:linenos:
:language: html
+ :emphasize-lines: 5-20
-This template is used by ``add_page()`` and ``edit_page()`` for adding and
-editing a wiki page. It displays a page containing a form that includes:
+This template is used by ``add_page()`` and ``edit_page()`` for adding and editing a wiki page.
+It displays a page containing a form that includes:
-- A 10-row by 60-column ``textarea`` field named ``body`` that is filled
- with any existing page data when it is rendered (line 46).
-- A submit button that has the name ``form.submitted`` (line 49).
+- A 10-row by 60-column ``textarea`` field named ``body`` that is filled with any existing page data when it is rendered (lines 11-13).
+- A submit button that has the name ``form.submitted`` (lines 16-18).
-The form POSTs back to the ``save_url`` argument supplied by the view (line
-44). The view will use the ``body`` and ``form.submitted`` values.
+When submitted, the form sends a POST request to the ``save_url`` argument supplied by the view (line 9).
+The view will use the ``body`` and ``form.submitted`` values.
-.. note:: Our templates use a ``request`` object that none of our tutorial
- views return in their dictionary. ``request`` is one of several names that
- are available "by default" in a template when a template renderer is used.
- See :ref:`renderer_system_values` for information about other names that
- are available by default when a template is used as a renderer.
+.. note::
+
+ Our templates use a ``request`` object that 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:`renderer_system_values` for information about other names that are available by default when a template is used as a renderer.
Static assets
-------------
-Our templates name static assets, including CSS and images. We don't need
-to create these files within our package's ``static`` directory because they
-were provided at the time we created the project.
+Our templates name static assets, including CSS and images.
+We don't need to create these files within our package's ``static`` directory because they were provided by the cookiecutter at the time we created the project.
-As an example, the CSS file will be accessed via
-``http://localhost:6543/static/theme.css`` by virtue of the call to the
-``add_static_view`` directive we've made in the ``__init__.py`` file. Any
-number and type of static assets can be placed in this directory (or
-subdirectories) and are just referred to by URL or by using the convenience
-method ``static_url``, e.g.,
-``request.static_url('<package>:static/foo.css')`` within templates.
+As an example, the CSS file will be accessed via ``http://localhost:6543/static/theme.css`` by virtue of the call to the ``add_static_view`` directive in the ``routes.py`` file.
+Any number and type of static assets can be placed in this directory (or subdirectories)
+They are referred to by either URL or using the convenience method ``static_url``, for example ``request.static_url('<package>:static/foo.css')``, within templates.
Viewing the application in a browser
====================================
-We can finally examine our application in a browser (See
-:ref:`wiki-start-the-application`). Launch a browser and visit
-each of the following URLs, checking that the result is as expected:
+We can finally examine our application in a browser (See :ref:`wiki-start-the-application`).
+Launch a browser and visit each of the following URLs, checking that the result is as expected:
+
+- http://localhost:6543/ invokes the ``view_wiki`` view.
+ This always redirects to the ``view_page`` view of the ``FrontPage`` ``Page`` resource.
-- http://localhost:6543/ invokes the ``view_wiki`` view. This always
- redirects to the ``view_page`` view of the ``FrontPage`` Page resource.
+- http://localhost:6543/FrontPage/ invokes the ``view_page`` view of the front page resource.
+ This is because it is the :term:`default view` (a view without a ``name``) for ``Page`` resources.
-- http://localhost:6543/FrontPage/ invokes the ``view_page`` view of the front
- page resource. This is because it's the :term:`default view` (a view
- without a ``name``) for Page resources.
+- http://localhost:6543/FrontPage/edit_page invokes the edit view for the ``FrontPage`` ``Page`` resource.
-- http://localhost:6543/FrontPage/edit_page invokes the edit view for the
- ``FrontPage`` Page resource.
+- http://localhost:6543/add_page/SomePageName invokes the add view for a ``Page``.
-- http://localhost:6543/add_page/SomePageName invokes the add view for a Page.
+- To generate an error, visit http://localhost:6543/add_page which will generate an ``IndexError: tuple index out of range`` error.
+ You will see an interactive traceback facility provided by :term:`pyramid_debugtoolbar`.
-- To generate an error, visit http://localhost:6543/add_page which will
- generate an ``IndexError: tuple index out of range`` error. You'll see an
- interactive traceback facility provided by :term:`pyramid_debugtoolbar`.
+- To generate a not found error, visit http://localhost:6543/wakawaka which will invoke the ``notfound_view`` view provided by the cookiecutter.
diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst
index 30d443bb8..5c86293f6 100644
--- a/docs/tutorials/wiki/design.rst
+++ b/docs/tutorials/wiki/design.rst
@@ -4,85 +4,77 @@
Design
======
-Following is a quick overview of the design of our wiki application, to help
-us understand the changes that we will be making as we work through the
-tutorial.
+Following is a quick overview of the design of our wiki application to help us understand the changes that we will make as we work through the tutorial.
+
Overall
-------
-We choose to use :term:`reStructuredText` markup in the wiki text. Translation
-from reStructuredText to HTML is provided by the widely used ``docutils``
-Python module. We will add this module in the dependency list on the project
-``setup.py`` file.
+We choose to use :term:`reStructuredText` markup in the wiki text.
+Conversion from reStructuredText to HTML is provided by the widely used ``docutils`` Python module.
+We will add this module in the dependency list on the project ``setup.py`` file.
+
Models
------
-The root resource named ``Wiki`` will be a mapping of wiki page
-names to page resources. The page resources will be instances
-of a *Page* class and they store the text content.
+The root resource named ``Wiki`` will be a mapping of wiki page names to page resources.
+The page resources will be instances of a *Page* class.
+They store the text content.
+
+URLs like ``/PageName`` will be traversed using Wiki[ *PageName* ] => page.
+The resulting context is the page resource of an existing page.
-URLs like ``/PageName`` will be traversed using Wiki[
-*PageName* ] => page, and the context that results is the page
-resource of an existing page.
+To add a page to the wiki, a new instance of the page resource is created.
+Its name and reference are added to the Wiki mapping.
-To add a page to the wiki, a new instance of the page resource
-is created and its name and reference are added to the Wiki
-mapping.
+A page named ``FrontPage`` containing the text *This is the front page* will be created when the storage is initialized.
+It will be used as the wiki home page.
-A page named ``FrontPage`` containing the text *This is the front page*, will
-be created when the storage is initialized, and will be used as the wiki home
-page.
Views
-----
-There will be three views to handle the normal operations of adding,
-editing, and viewing wiki pages, plus one view for the wiki front page.
-Two templates will be used, one for viewing, and one for both adding
-and editing wiki pages.
+There will be three views to handle the normal operations of adding, editing, and viewing wiki pages, plus one view for the wiki front page.
+Two templates will be used, one for viewing, and one for both adding and editing wiki pages.
-As of version 1.5 :app:`Pyramid` no longer ships with templating systems. In this tutorial, we will use :term:`Chameleon`. Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language.
+As of version 1.5 :app:`Pyramid` no longer ships with templating systems.
+In this tutorial we will use :term:`Chameleon`.
+Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language.
Security
--------
-We'll eventually be adding security to our application. The components we'll
-use to do this are below.
+We'll eventually add security to our application.
+The components we'll use to do this are below.
-- USERS, a dictionary mapping :term:`userids <userid>` to their
- corresponding passwords.
+- USERS, a dictionary mapping :term:`userids <userid>` to their corresponding passwords.
-- GROUPS, a dictionary mapping :term:`userids <userid>` to a
- list of groups to which they belong.
+- GROUPS, a dictionary mapping :term:`userids <userid>` to a list of groups to which they belong.
-- ``groupfinder``, an *authorization callback* that looks up USERS and
- GROUPS. It will be provided in a new ``security.py`` file.
+- ``groupfinder``, an *authorization callback* that looks up USERS and GROUPS.
+ It will be provided in a new ``security.py`` file.
-- An :term:`ACL` is attached to the root :term:`resource`. Each row below
- details an :term:`ACE`:
+- An :term:`ACL` is attached to the root :term:`resource`.
+ Each row below details an :term:`ACE`:
- +----------+----------------+----------------+
- | Action | Principal | Permission |
- +==========+================+================+
- | Allow | Everyone | View |
- +----------+----------------+----------------+
- | Allow | group:editors | Edit |
- +----------+----------------+----------------+
+ +----------+----------------+----------------+
+ | Action | Principal | Permission |
+ +==========+================+================+
+ | Allow | Everyone | View |
+ +----------+----------------+----------------+
+ | Allow | group:editors | Edit |
+ +----------+----------------+----------------+
-- Permission declarations are added to the views to assert the security
- policies as each request is handled.
+- Permission declarations are added to the views to assert the security policies as each request is handled.
-Two additional views and one template will handle the login and
-logout tasks.
+Two additional views and one template will handle the login and logout tasks.
Summary
-------
-The URL, context, actions, template and permission associated to each view are
-listed in the following table:
+The URL, context, actions, template and permission associated to each view are listed in the following table:
+----------------------+-------------+-----------------+-----------------------+------------+------------+
| URL | View | Context | Action | Template | Permission |
@@ -139,10 +131,6 @@ listed in the following table:
| | | | /FrontPage | | |
+----------------------+-------------+-----------------+-----------------------+------------+------------+
-.. [1] This is the default view for a Page context
- when there is no view name.
-.. [2] Pyramid will return a default 404 Not Found page
- if the page *PageName* does not exist yet.
-.. [3] ``pyramid.exceptions.Forbidden`` is reached when a
- user tries to invoke a view that is
- not authorized by the authorization policy.
+.. [1] This is the default view for a Page context when there is no view name.
+.. [2] Pyramid will return a default 404 Not Found page if the page *PageName* does not exist yet.
+.. [3] ``pyramid.exceptions.Forbidden`` is reached when a user tries to invoke a view that is not authorized by the authorization policy.
diff --git a/docs/tutorials/wiki/distributing.rst b/docs/tutorials/wiki/distributing.rst
index 36d00adb4..c23f79b5a 100644
--- a/docs/tutorials/wiki/distributing.rst
+++ b/docs/tutorials/wiki/distributing.rst
@@ -4,10 +4,8 @@
Distributing Your Application
=============================
-Once your application works properly, you can create a "tarball" from it by
-using the ``setup.py sdist`` command. The following commands assume your
-current working directory contains the ``tutorial`` package and the
-``setup.py`` file.
+Once your application works properly, you can create a :term:`distribution` from it by using the ``setup.py sdist`` command.
+The following commands assume your current working directory contains the ``tutorial`` package and the ``setup.py`` file.
On Unix:
@@ -31,10 +29,8 @@ The output of such a command will be something like:
Creating tar archive
removing 'tutorial-0.0' (and everything under it)
-Note that this command creates a tarball in the "dist" subdirectory named
-``tutorial-0.0.tar.gz``. You can send this file to your friends to show them
-your cool new application. They should be able to install it by pointing the
-``pip install`` command directly at it. Or you can upload it to `PyPI
-<https://pypi.org/>`_ and share it with the rest of the world, where
-it can be downloaded via ``pip install`` remotely like any other package people
-download from PyPI.
+This command creates a subdirectory named ``dist``.
+Inside that is a tarball named ``tutorial-0.0.tar.gz``, which is the :term:`distribution` of your application.
+You can send this file to your friends to show them your cool new application.
+They should be able to install it by pointing the ``pip install`` command directly at it.
+Or you can upload it to `PyPI <https://pypi.org/>`_ and share it with the rest of the world, where it can be downloaded via ``pip install`` remotely like any other package people download from PyPI.
diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst
index d0037e584..37e3498b2 100644
--- a/docs/tutorials/wiki/installation.rst
+++ b/docs/tutorials/wiki/installation.rst
@@ -4,12 +4,13 @@
Installation
============
+
Before you begin
----------------
-This tutorial assumes that you have already followed the steps in
-:ref:`installing_chapter`, except **do not create a virtual environment or
-install Pyramid**. Thereby you will satisfy the following requirements.
+This tutorial assumes that you have already followed the steps in :ref:`installing_chapter`, except **do not create a virtual environment or
+install Pyramid**.
+Thereby you will satisfy the following requirements.
* A Python interpreter is installed on your operating system.
* You've satisfied the :ref:`requirements-for-installing-packages`.
@@ -17,13 +18,17 @@ install Pyramid**. Thereby you will satisfy the following requirements.
Install cookiecutter
--------------------
-We will use a :term:`cookiecutter` to create a Python package project from a Python package project template. See `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ for instructions.
+We will use a :term:`cookiecutter` to create a Python package project from a Python package project template.
+See `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ for instructions.
Generate a Pyramid project from a cookiecutter
----------------------------------------------
-We will create a Pyramid project in your home directory for Unix or at the root for Windows. It is assumed you know the path to where you installed ``cookiecutter``. Issue the following commands and override the defaults in the prompts as follows.
+We will create a Pyramid project in your home directory for Unix or at the root for Windows.
+It is assumed you know the path to where you installed ``cookiecutter``.
+Issue the following commands and override the defaults in the prompts as follows.
+
On Unix
^^^^^^^
@@ -33,6 +38,7 @@ On Unix
cd ~
cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master
+
On Windows
^^^^^^^^^^
@@ -41,30 +47,33 @@ On Windows
cd \
cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master
+
On all operating systems
^^^^^^^^^^^^^^^^^^^^^^^^
If prompted for the first item, accept the default ``yes`` by hitting return.
.. code-block:: text
- You've cloned ~/.cookiecutters/pyramid-cookiecutter-theone before.
- Is it okay to delete and re-clone it? [yes]: yes
+ You've downloaded ~/.cookiecutters/pyramid-cookiecutter-starter before.
+ Is it okay to delete and re-download it? [yes]: yes
project_name [Pyramid Scaffold]: myproj
repo_name [myproj]: tutorial
Select template_language:
1 - jinja2
2 - chameleon
3 - mako
- Choose from 1, 2, 3 [1]: 1
+ Choose from 1, 2, 3 [1]: 2
Select backend:
1 - none
2 - sqlalchemy
3 - zodb
Choose from 1, 2, 3 [1]: 3
+
Change directory into your newly created project
------------------------------------------------
+
On Unix
^^^^^^^
@@ -72,6 +81,7 @@ On Unix
cd tutorial
+
On Windows
^^^^^^^^^^
@@ -85,6 +95,7 @@ Set and use a ``VENV`` environment variable
We will set the ``VENV`` environment variable to the absolute path of the virtual environment, and use it going forward.
+
On Unix
^^^^^^^
@@ -92,6 +103,7 @@ On Unix
export VENV=~/tutorial
+
On Windows
^^^^^^^^^^
@@ -103,6 +115,7 @@ On Windows
Create a virtual environment
----------------------------
+
On Unix
^^^^^^^
@@ -110,17 +123,10 @@ On Unix
python3 -m venv $VENV
+
On Windows
^^^^^^^^^^
-Each version of Python uses different paths, so you might need to adjust the path to the command for your Python version. Recent versions of the Python 3 installer for Windows now install a Python launcher.
-
-Python 2.7:
-
-.. code-block:: doscon
-
- c:\Python27\Scripts\virtualenv %VENV%
-
Python 3.7:
.. code-block:: doscon
@@ -131,6 +137,7 @@ Python 3.7:
Upgrade packaging tools in the virtual environment
--------------------------------------------------
+
On Unix
^^^^^^^
@@ -138,6 +145,7 @@ On Unix
$VENV/bin/pip install --upgrade pip setuptools
+
On Windows
^^^^^^^^^^
@@ -151,7 +159,10 @@ On Windows
Installing the project in development mode
------------------------------------------
-In order to do development on the project easily, you must "register" the project as a development egg in your workspace. We will install testing requirements at the same time. We do so with the following command.
+In order to work on the project, you must "register" the project as a development egg in your workspace.
+We will install testing requirements at the same time.
+We do so with the following command.
+
On Unix
^^^^^^^
@@ -160,6 +171,7 @@ On Unix
$VENV/bin/pip install -e ".[testing]"
+
On Windows
^^^^^^^^^^
@@ -167,6 +179,7 @@ On Windows
%VENV%\Scripts\pip install -e ".[testing]"
+
On all operating systems
^^^^^^^^^^^^^^^^^^^^^^^^
@@ -174,16 +187,18 @@ The console will show ``pip`` checking for packages and installing missing packa
.. code-block:: bash
- Successfully installed BTrees-4.3.1 Chameleon-3.0 Mako-1.0.6 \
- MarkupSafe-0.23 PasteDeploy-1.5.2 Pygments-2.1.3 WebOb-1.6.3 \
- WebTest-2.0.23 ZConfig-3.1.0 ZEO-5.0.4 ZODB-5.1.1 ZODB3-3.11.0 \
- beautifulsoup4-4.5.1 coverage-4.2 mock-2.0.0 pbr-1.10.0 persistent-4.2.2 \
- py-1.4.31 pyramid-1.7.3 pyramid-chameleon-0.3 pyramid-debugtoolbar-3.0.5 \
- pyramid-mako-1.0.2 pyramid-tm-1.1.1 pyramid-zodbconn-0.7 pytest-3.0.5 \
- pytest-cov-2.4.0 repoze.lru-0.6 six-1.10.0 transaction-2.0.3 \
- translationstring-1.3 tutorial venusian-1.0 waitress-1.0.1 \
- zc.lockfile-1.2.1 zdaemon-4.2.0 zodbpickle-0.6.0 zodburi-2.0 \
- zope.deprecation-4.2.0 zope.interface-4.3.3
+ Successfully installed BTrees-4.5.1 Chameleon-3.5 Mako-1.0.7 \
+ MarkupSafe-1.1.0 PasteDeploy-1.5.2 Pygments-2.2.0 WebTest-2.0.32 \
+ ZConfig-3.3.0 ZEO-5.2.0 ZODB-5.5.1 ZODB3-3.11.0 atomicwrites-1.2.1 \
+ attrs-18.2.0 beautifulsoup4-4.6.3 coverage-4.5.2 hupper-1.4.1 \
+ more-itertools-4.3.0 persistent-4.4.3 plaster-1.0 plaster-pastedeploy-0.6 \
+ pluggy-0.8.0 py-1.7.0 pyramid-1.10.1 pyramid-chameleon-0.3 \
+ pyramid-debugtoolbar-4.5 pyramid-mako-1.0.2 pyramid-retry-1.0 \
+ pyramid-tm-2.2.1 pyramid-zodbconn-0.8.1 pytest-4.0.0 pytest-cov-2.6.0 \
+ repoze.lru-0.7 six-1.11.0 transaction-2.4.0 translationstring-1.3 \
+ tutorial venusian-1.1.0 waitress-1.1.0 webob-1.8.4 zc.lockfile-1.4 \
+ zdaemon-4.3 zodbpickle-1.0.2 zodburi-2.3.0 zope.deprecation-4.3.0 \
+ zope.interface-4.6.0
Testing requirements are defined in our project's ``setup.py`` file, in the ``tests_require`` and ``extras_require`` stanzas.
@@ -208,6 +223,7 @@ requirements, you may run the tests for the project. The following commands
provide options to ``pytest`` that specify the module for which its tests shall be
run, and to run ``pytest`` in quiet mode.
+
On Unix
^^^^^^^
@@ -215,6 +231,7 @@ On Unix
$VENV/bin/pytest -q
+
On Windows
^^^^^^^^^^
@@ -233,13 +250,11 @@ For a successful test run, you should see output that ends like this:
Expose test coverage information
--------------------------------
-You can run the ``pytest`` command to see test coverage information. This
-runs the tests in the same way that ``pytest`` does, but provides additional
-:term:`coverage` information, exposing which lines of your project are covered by the
-tests.
+You can run the ``pytest`` command to see test coverage information.
+This runs the tests in the same way that ``pytest`` does, but provides additional :term:`coverage` information, exposing which lines of your project are covered by the tests.
+
+We've already installed the ``pytest-cov`` package into our virtual environment, so we can run the tests with coverage.
-We've already installed the ``pytest-cov`` package into our virtual
-environment, so we can run the tests with coverage.
On Unix
^^^^^^^
@@ -248,6 +263,7 @@ On Unix
$VENV/bin/pytest --cov --cov-report=term-missing
+
On Windows
^^^^^^^^^^
@@ -259,23 +275,30 @@ If successful, you will see output something like this:
.. code-block:: bash
- ======================== test session starts ========================
- platform Python 3.6.0, pytest-3.0.5, py-1.4.31, pluggy-0.4.0
- rootdir: /Users/stevepiercy/tutorial, inifile:
- plugins: cov-2.4.0
- collected 1 items
+ ======================== test session starts =========================
+ platform darwin -- Python 3.7.0, pytest-4.0.0, py-1.7.0, pluggy-0.8.0
+ rootdir: /Users/stevepiercy/projects/hack-on-pyramid/tutorial, inifile: pytest.ini
+ plugins: cov-2.6.0
+ collected 1 item
tutorial/tests.py .
- ------------------ coverage: platform Python 3.6.0 ------------------
- Name Stmts Miss Cover Missing
- -------------------------------------------------------
- tutorial/__init__.py 14 9 36% 7-8, 14-20
- tutorial/models.py 10 6 40% 9-14
- tutorial/views.py 4 0 100%
- -------------------------------------------------------
- TOTAL 28 15 46%
+ [100%]
+
+ ---------- coverage: platform darwin, python 3.7.0-final-0 -----------
+ Name Stmts Miss Cover Missing
+ -----------------------------------------------------------
+ tutorial/__init__.py 17 12 29% 7-8, 14-23
+ tutorial/models/__init__.py 8 4 50% 9-12
+ tutorial/pshell.py 6 6 0% 1-12
+ tutorial/routes.py 2 2 0% 1-2
+ tutorial/views/__init__.py 0 0 100%
+ tutorial/views/default.py 4 0 100%
+ tutorial/views/notfound.py 4 4 0% 1-7
+ -----------------------------------------------------------
+ TOTAL 41 28 32%
+
- ===================== 1 passed in 0.31 seconds ======================
+ ===================== 1 passed in 0.31 seconds =======================
Our package doesn't quite have 100% test coverage.
@@ -285,11 +308,10 @@ Our package doesn't quite have 100% test coverage.
Test and coverage cookiecutter defaults
---------------------------------------
-The Pyramid cookiecutter includes configuration defaults for ``pytest`` and
-test coverage. These configuration files are ``pytest.ini`` and
-``.coveragerc``, located at the root of your package. Without these defaults,
-we would need to specify the path to the module on which we want to run tests
-and coverage.
+The Pyramid cookiecutter includes configuration defaults for ``pytest`` and test coverage.
+These configuration files are ``pytest.ini`` and ``.coveragerc``, located at the root of your package.
+Without these defaults, we would need to specify the path to the module on which we want to run tests and coverage.
+
On Unix
^^^^^^^
@@ -305,13 +327,11 @@ On Windows
%VENV%\Scripts\pytest --cov=tutorial tutorial\tests.py -q
-``pytest`` follows :ref:`conventions for Python test discovery
-<pytest:test discovery>`, and the configuration defaults from the cookiecutter
-tell ``pytest`` where to find the module on which we want to run tests and
-coverage.
-.. seealso:: See ``pytest``'s documentation for :ref:`pytest:usage` or invoke
- ``pytest -h`` to see its full set of options.
+``pytest`` follows :ref:`conventions for Python test discovery <pytest:test discovery>`.
+The configuration defaults from the cookiecutter tell ``pytest`` where to find the module on which we want to run tests and coverage.
+
+.. seealso:: See ``pytest``'s documentation for :ref:`pytest:usage` or invoke ``pytest -h`` to see its full set of options.
.. _wiki-start-the-application:
@@ -319,8 +339,9 @@ coverage.
Start the application
---------------------
-Start the application. See :ref:`what_is_this_pserve_thing` for more
-information on ``pserve``.
+Start the application.
+See :ref:`what_is_this_pserve_thing` for more information on ``pserve``.
+
On Unix
^^^^^^^
@@ -329,6 +350,7 @@ On Unix
$VENV/bin/pserve development.ini --reload
+
On Windows
^^^^^^^^^^
@@ -336,10 +358,10 @@ On Windows
%VENV%\Scripts\pserve development.ini --reload
+
.. note::
- Your OS firewall, if any, may pop up a dialog asking for authorization
- to allow python to accept incoming network connections.
+ Your OS firewall, if any, may pop up a dialog asking for authorization to allow python to accept incoming network connections.
If successful, you will see something like this on your console:
@@ -356,13 +378,12 @@ This means the server is ready to accept requests.
Visit the application in a browser
----------------------------------
-In a browser, visit http://localhost:6543/. You will see the generated
-application's default page.
+In a browser, visit http://localhost:6543/.
+You will see the generated application's default page.
-One thing you'll notice is the "debug toolbar" icon on right hand side of the
-page. You can read more about the purpose of the icon at
-:ref:`debug_toolbar`. It allows you to get information about your
-application while you develop.
+One thing you'll notice is the "debug toolbar" icon on right hand side of the page.
+You can read more about the purpose of the icon at :ref:`debug_toolbar`.
+It allows you to get information about your application while you develop.
Decisions the cookiecutter backend option ``zodb`` has made for you
@@ -371,27 +392,11 @@ Decisions the cookiecutter backend option ``zodb`` has made for you
When creating a project and selecting the backend option of ``zodb``, the cookiecutter makes the following assumptions:
- You are willing to use :term:`ZODB` for persistent storage.
-
- You are willing to use :term:`traversal` to map URLs to code.
-
-- You want to use pyramid_zodbconn_, pyramid_tm_, and the transaction_ packages
- to manage connections and transactions with :term:`ZODB`.
+- You want to use `pyramid_zodbconn <https://docs.pylonsproject.org/projects/pyramid-zodbconn/en/latest/>`_, `pyramid_tm <https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/>`_, and the `transaction <https://zodb.readthedocs.io/en/latest/transactions.html>`_ packages to manage connections and transactions with :term:`ZODB`.
.. note::
- :app:`Pyramid` supports any persistent storage mechanism (e.g., an SQL
- database or filesystem files). It also supports an additional mechanism to
- map URLs to code (:term:`URL dispatch`). However, for the purposes of this
- tutorial, we'll only be using :term:`traversal` and :term:`ZODB`.
-
-.. _pyramid_chameleon:
- https://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/
-
-.. _pyramid_tm:
- https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/
-
-.. _pyramid_zodbconn:
- https://docs.pylonsproject.org/projects/pyramid-zodbconn/en/latest/
-
-.. _transaction:
- https://zodb.readthedocs.io/en/latest/transactions.html
+ :app:`Pyramid` supports any persistent storage mechanism (e.g., an SQL database or filesystem files).
+ It also supports an additional mechanism to map URLs to code (:term:`URL dispatch`).
+ However, for the purposes of this tutorial, we will only use :term:`traversal` and :term:`ZODB`.
diff --git a/docs/tutorials/wiki/src/authorization/MANIFEST.in b/docs/tutorials/wiki/src/authorization/MANIFEST.in
index 81beba1b1..05cc195d9 100644
--- a/docs/tutorials/wiki/src/authorization/MANIFEST.in
+++ b/docs/tutorials/wiki/src/authorization/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki/src/authorization/pytest.ini b/docs/tutorials/wiki/src/authorization/pytest.ini
index 8b76bc410..a3489cdf8 100644
--- a/docs/tutorials/wiki/src/authorization/pytest.ini
+++ b/docs/tutorials/wiki/src/authorization/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
testpaths = tutorial
-python_files = *.py
+python_files = test*.py
diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py
index 7011387f6..fa5948acb 100644
--- a/docs/tutorials/wiki/src/authorization/setup.py
+++ b/docs/tutorials/wiki/src/authorization/setup.py
@@ -10,22 +10,22 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'plaster_pastedeploy',
- 'pyramid >= 1.9a',
+ 'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'waitress',
'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'waitress',
'docutils',
'bcrypt',
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
index 58635ea74..935a5d6d2 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
@@ -15,18 +15,17 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
authn_policy = AuthTktAuthenticationPolicy(
'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
with Configurator(settings=settings) as config:
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
- config.include('pyramid_chameleon')
config.include('pyramid_tm')
config.include('pyramid_retry')
config.include('pyramid_zodbconn')
config.set_root_factory(root_factory)
- config.add_static_view('static', 'static', cache_max_age=3600)
+ config.include('pyramid_chameleon')
+ config.include('.routes')
config.scan()
- return config.make_wsgi_app()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/models.py b/docs/tutorials/wiki/src/authorization/tutorial/models/__init__.py
index ebd70e912..ebd70e912 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/models.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/models/__init__.py
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/pshell.py b/docs/tutorials/wiki/src/authorization/tutorial/pshell.py
index 3d026291b..a7cfa6a27 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/pshell.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/pshell.py
@@ -1,5 +1,6 @@
from . import models
+
def setup(env):
request = env['request']
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/routes.py b/docs/tutorials/wiki/src/authorization/tutorial/routes.py
new file mode 100644
index 000000000..3c0a37992
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/tutorial/routes.py
@@ -0,0 +1,2 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css b/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css
index 0f4b1a4d4..a70ee557a 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css
+++ b/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css
@@ -17,6 +17,9 @@ h6 {
p {
font-weight: 300;
}
+button, input, optgroup, select, textarea {
+ color: black;
+}
.font-normal {
font-weight: 400;
}
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt
new file mode 100644
index 000000000..07298940c
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt
@@ -0,0 +1,10 @@
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
+
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+ </div>
+
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
index eedb83da4..6438b1569 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
@@ -1,72 +1,27 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
- <![endif]-->
- </head>
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <p tal:condition="logged_in" class="pull-right">
+ <div class="content">
+ <p tal:condition="logged_in" class="pull-right">
<a href="${request.application_url}/logout">Logout</a>
- </p>
- <p>
- Editing <strong><span tal:replace="page.__name__">
- Page Name Goes Here</span></strong>
- </p>
- <p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
- <form action="${save_url}" method="post">
+ </p>
+ <p>
+ Editing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
+ <form action="${save_url}" method="post">
<div class="form-group">
- <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea>
+ <textarea tal:content="page.data"
+ class="form-control" name="body"
+ rows="10" cols="60"></textarea>
</div>
<div class="form-group">
- <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+ <button type="submit"
+ name="form.submitted" value="Save"
+ class="btn btn-default">Save</button>
</div>
- </form>
- </div>
- </div>
+ </form>
</div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt
new file mode 100644
index 000000000..b606e8dad
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt
@@ -0,0 +1,59 @@
+<!DOCTYPE html metal:define-macro="layout">
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title><span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on
+ TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div metal:define-slot="content">No content</div>
+ <div class="content">
+ <p>You can return to the
+ <a href="${request.application_url}">FrontPage</a>.
+ </p>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
index 626db6637..acc4876cf 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
@@ -1,39 +1,7 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>Login - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
- <![endif]-->
- </head>
-
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
+ <div class="content">
<p>
<strong>
Login
@@ -54,22 +22,7 @@
<button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
</div>
</form>
- </div>
- </div>
</div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
index f2a9249ef..911ab0c99 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
@@ -1,72 +1,23 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
- <![endif]-->
- </head>
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <p tal:condition="logged_in" class="pull-right">
+ <div class="content">
+ <p tal:condition="logged_in" class="pull-right">
<a href="${request.application_url}/logout">Logout</a>
- </p>
- <div tal:replace="structure content">
- Page text goes here.
- </div>
- <p>
+ </p>
+ <div tal:replace="structure page_text">
+ Page text goes here.
+ </div>
+ <p>
<a tal:attributes="href edit_url" href="">
Edit this page
</a>
- </p>
- <p>
- Viewing <strong><span tal:replace="page.__name__">
- Page Name Goes Here</span></strong>
- </p>
- <p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
+ </p>
+ <p>
+ Viewing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
</div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/tests.py b/docs/tutorials/wiki/src/authorization/tutorial/tests.py
index ca7a47279..6279d9f66 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/tests.py
@@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase):
testing.tearDown()
def test_my_view(self):
- from .views import my_view
+ from .views.default import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'myproj')
+
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views/default.py
index ea2da01af..3a3b170e2 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views/default.py
@@ -2,30 +2,29 @@ from docutils.core import publish_parts
import re
from pyramid.httpexceptions import HTTPFound
-
-from pyramid.view import (
- view_config,
- forbidden_view_config,
- )
-
from pyramid.security import (
- remember,
forget,
+ remember,
+)
+from pyramid.view import (
+ forbidden_view_config,
+ view_config,
)
-
-from .security import USERS, check_password
-from .models import Page
+from ..models import Page
+from ..security import check_password, USERS
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-@view_config(context='.models.Wiki',
+
+@view_config(context='..models.Wiki',
permission='view')
def view_wiki(context, request):
return HTTPFound(location=request.resource_url(context, 'FrontPage'))
-@view_config(context='.models.Page', renderer='templates/view.pt',
+
+@view_config(context='..models.Page', renderer='../templates/view.pt',
permission='view')
def view_page(context, request):
wiki = context.__parent__
@@ -40,14 +39,15 @@ def view_page(context, request):
add_url = request.application_url + '/add_page/' + word
return '<a href="%s">%s</a>' % (add_url, word)
- content = publish_parts(context.data, writer_name='html')['html_body']
- content = wikiwords.sub(check, content)
+ page_text = publish_parts(context.data, writer_name='html')['html_body']
+ page_text = wikiwords.sub(check, page_text)
edit_url = request.resource_url(context, 'edit_page')
- return dict(page=context, content=content, edit_url=edit_url,
+ return dict(page=context, page_text=page_text, edit_url=edit_url,
logged_in=request.authenticated_userid)
-@view_config(name='add_page', context='.models.Wiki',
- renderer='templates/edit.pt',
+
+@view_config(name='add_page', context='..models.Wiki',
+ renderer='../templates/edit.pt',
permission='edit')
def add_page(context, request):
pagename = request.subpath[0]
@@ -65,8 +65,9 @@ def add_page(context, request):
return dict(page=page, save_url=save_url,
logged_in=request.authenticated_userid)
-@view_config(name='edit_page', context='.models.Page',
- renderer='templates/edit.pt',
+
+@view_config(name='edit_page', context='..models.Page',
+ renderer='../templates/edit.pt',
permission='edit')
def edit_page(context, request):
if 'form.submitted' in request.params:
@@ -77,9 +78,10 @@ def edit_page(context, request):
save_url=request.resource_url(context, 'edit_page'),
logged_in=request.authenticated_userid)
-@view_config(context='.models.Wiki', name='login',
- renderer='templates/login.pt')
-@forbidden_view_config(renderer='templates/login.pt')
+
+@view_config(context='..models.Wiki', name='login',
+ renderer='../templates/login.pt')
+@forbidden_view_config(renderer='../templates/login.pt')
def login(request):
login_url = request.resource_url(request.context, 'login')
referrer = request.url
@@ -104,10 +106,11 @@ def login(request):
came_from=came_from,
login=login,
password=password,
+ title='Login',
)
-@view_config(context='.models.Wiki', name='logout')
+@view_config(context='..models.Wiki', name='logout')
def logout(request):
headers = forget(request)
return HTTPFound(location=request.resource_url(request.context),
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py b/docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py
new file mode 100644
index 000000000..d44b4d0e6
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py
@@ -0,0 +1,12 @@
+from pyramid.view import notfound_view_config
+
+from ..models import Page
+
+
+@notfound_view_config(renderer='../templates/404.pt')
+def notfound_view(request):
+ request.response.status = 404
+ pagename = request.path
+ page = Page(pagename)
+ page.__name__ = pagename
+ return dict(page=page)
diff --git a/docs/tutorials/wiki/src/basiclayout/MANIFEST.in b/docs/tutorials/wiki/src/basiclayout/MANIFEST.in
index 81beba1b1..05cc195d9 100644
--- a/docs/tutorials/wiki/src/basiclayout/MANIFEST.in
+++ b/docs/tutorials/wiki/src/basiclayout/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki/src/basiclayout/pytest.ini b/docs/tutorials/wiki/src/basiclayout/pytest.ini
index 8b76bc410..a3489cdf8 100644
--- a/docs/tutorials/wiki/src/basiclayout/pytest.ini
+++ b/docs/tutorials/wiki/src/basiclayout/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
testpaths = tutorial
-python_files = *.py
+python_files = test*.py
diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py
index e05e279e2..d6d488ed2 100644
--- a/docs/tutorials/wiki/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki/src/basiclayout/setup.py
@@ -10,20 +10,20 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'plaster_pastedeploy',
- 'pyramid >= 1.9a',
+ 'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'waitress',
'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'waitress',
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
index f2b3c9568..830a607f3 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
@@ -11,13 +11,12 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
with Configurator(settings=settings) as config:
- config.include('pyramid_chameleon')
config.include('pyramid_tm')
config.include('pyramid_retry')
config.include('pyramid_zodbconn')
config.set_root_factory(root_factory)
- config.add_static_view('static', 'static', cache_max_age=3600)
+ config.include('pyramid_chameleon')
+ config.include('.routes')
config.scan()
- return config.make_wsgi_app()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki/src/basiclayout/tutorial/models/__init__.py
index aca6a4129..aca6a4129 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/models/__init__.py
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py b/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py
index 3d026291b..a7cfa6a27 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py
@@ -1,5 +1,6 @@
from . import models
+
def setup(env):
request = env['request']
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/routes.py b/docs/tutorials/wiki/src/basiclayout/tutorial/routes.py
new file mode 100644
index 000000000..3c0a37992
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/routes.py
@@ -0,0 +1,2 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css b/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css
index 0f4b1a4d4..a70ee557a 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css
@@ -17,6 +17,9 @@ h6 {
p {
font-weight: 300;
}
+button, input, optgroup, select, textarea {
+ color: black;
+}
.font-normal {
font-weight: 400;
}
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt
new file mode 100644
index 000000000..07298940c
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt
@@ -0,0 +1,10 @@
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
+
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+ </div>
+
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt
new file mode 100644
index 000000000..9fdaef00f
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt
@@ -0,0 +1,62 @@
+<!DOCTYPE html metal:define-macro="layout">
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>Cookiecutter Starter project for the Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div metal:define-slot="content">No content</div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
+ <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
index d63ea8c45..adac4fe35 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
@@ -1,65 +1,11 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>Cookiecutter ZODB project for the Pyramid Web Framework</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
- <![endif]-->
- </head>
-
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB Project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
- </div>
- </div>
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, a&nbsp;Pyramid
+ application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
- <div class="row">
- <div class="links">
- <ul>
- <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
- <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
- </ul>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
index ca7a47279..6279d9f66 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
@@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase):
testing.tearDown()
def test_my_view(self):
- from .views import my_view
+ from .views.default import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'myproj')
+
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py
deleted file mode 100644
index c1878bdd0..000000000
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from pyramid.view import view_config
-from .models import MyModel
-
-
-@view_config(context=MyModel, renderer='templates/mytemplate.pt')
-def my_view(request):
- return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py
new file mode 100644
index 000000000..5d708d15c
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py
@@ -0,0 +1,8 @@
+from pyramid.view import view_config
+
+from ..models import MyModel
+
+
+@view_config(context=MyModel, renderer='../templates/mytemplate.pt')
+def my_view(request):
+ return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py
new file mode 100644
index 000000000..728791d0a
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.pt')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki/src/installation/MANIFEST.in b/docs/tutorials/wiki/src/installation/MANIFEST.in
index 81beba1b1..05cc195d9 100644
--- a/docs/tutorials/wiki/src/installation/MANIFEST.in
+++ b/docs/tutorials/wiki/src/installation/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki/src/installation/pytest.ini b/docs/tutorials/wiki/src/installation/pytest.ini
index 8b76bc410..a3489cdf8 100644
--- a/docs/tutorials/wiki/src/installation/pytest.ini
+++ b/docs/tutorials/wiki/src/installation/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
testpaths = tutorial
-python_files = *.py
+python_files = test*.py
diff --git a/docs/tutorials/wiki/src/installation/setup.py b/docs/tutorials/wiki/src/installation/setup.py
index e05e279e2..d6d488ed2 100644
--- a/docs/tutorials/wiki/src/installation/setup.py
+++ b/docs/tutorials/wiki/src/installation/setup.py
@@ -10,20 +10,20 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'plaster_pastedeploy',
- 'pyramid >= 1.9a',
+ 'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'waitress',
'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'waitress',
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki/src/installation/tutorial/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
index f2b3c9568..830a607f3 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
@@ -11,13 +11,12 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
with Configurator(settings=settings) as config:
- config.include('pyramid_chameleon')
config.include('pyramid_tm')
config.include('pyramid_retry')
config.include('pyramid_zodbconn')
config.set_root_factory(root_factory)
- config.add_static_view('static', 'static', cache_max_age=3600)
+ config.include('pyramid_chameleon')
+ config.include('.routes')
config.scan()
- return config.make_wsgi_app()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/installation/tutorial/models.py b/docs/tutorials/wiki/src/installation/tutorial/models/__init__.py
index aca6a4129..aca6a4129 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/models.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/models/__init__.py
diff --git a/docs/tutorials/wiki/src/installation/tutorial/pshell.py b/docs/tutorials/wiki/src/installation/tutorial/pshell.py
index 3d026291b..a7cfa6a27 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/pshell.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/pshell.py
@@ -1,5 +1,6 @@
from . import models
+
def setup(env):
request = env['request']
diff --git a/docs/tutorials/wiki/src/installation/tutorial/routes.py b/docs/tutorials/wiki/src/installation/tutorial/routes.py
new file mode 100644
index 000000000..3c0a37992
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/routes.py
@@ -0,0 +1,2 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/installation/tutorial/static/theme.css b/docs/tutorials/wiki/src/installation/tutorial/static/theme.css
index 0f4b1a4d4..a70ee557a 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/static/theme.css
+++ b/docs/tutorials/wiki/src/installation/tutorial/static/theme.css
@@ -17,6 +17,9 @@ h6 {
p {
font-weight: 300;
}
+button, input, optgroup, select, textarea {
+ color: black;
+}
.font-normal {
font-weight: 400;
}
diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/404.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/404.pt
new file mode 100644
index 000000000..07298940c
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/templates/404.pt
@@ -0,0 +1,10 @@
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
+
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+ </div>
+
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt
new file mode 100644
index 000000000..9fdaef00f
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt
@@ -0,0 +1,62 @@
+<!DOCTYPE html metal:define-macro="layout">
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>Cookiecutter Starter project for the Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div metal:define-slot="content">No content</div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
+ <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
index d63ea8c45..adac4fe35 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
@@ -1,65 +1,11 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>Cookiecutter ZODB project for the Pyramid Web Framework</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
- <![endif]-->
- </head>
-
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB Project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
- </div>
- </div>
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, a&nbsp;Pyramid
+ application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
- <div class="row">
- <div class="links">
- <ul>
- <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
- <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
- </ul>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/installation/tutorial/tests.py b/docs/tutorials/wiki/src/installation/tutorial/tests.py
index ca7a47279..6279d9f66 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/tests.py
@@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase):
testing.tearDown()
def test_my_view(self):
- from .views import my_view
+ from .views.default import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'myproj')
+
diff --git a/docs/tutorials/wiki/src/installation/tutorial/views.py b/docs/tutorials/wiki/src/installation/tutorial/views.py
deleted file mode 100644
index c1878bdd0..000000000
--- a/docs/tutorials/wiki/src/installation/tutorial/views.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from pyramid.view import view_config
-from .models import MyModel
-
-
-@view_config(context=MyModel, renderer='templates/mytemplate.pt')
-def my_view(request):
- return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/installation/tutorial/views/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki/src/installation/tutorial/views/default.py b/docs/tutorials/wiki/src/installation/tutorial/views/default.py
new file mode 100644
index 000000000..5d708d15c
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/views/default.py
@@ -0,0 +1,8 @@
+from pyramid.view import view_config
+
+from ..models import MyModel
+
+
+@view_config(context=MyModel, renderer='../templates/mytemplate.pt')
+def my_view(request):
+ return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/installation/tutorial/views/notfound.py b/docs/tutorials/wiki/src/installation/tutorial/views/notfound.py
new file mode 100644
index 000000000..728791d0a
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.pt')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki/src/models/MANIFEST.in b/docs/tutorials/wiki/src/models/MANIFEST.in
index 81beba1b1..05cc195d9 100644
--- a/docs/tutorials/wiki/src/models/MANIFEST.in
+++ b/docs/tutorials/wiki/src/models/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki/src/models/pytest.ini b/docs/tutorials/wiki/src/models/pytest.ini
index 8b76bc410..a3489cdf8 100644
--- a/docs/tutorials/wiki/src/models/pytest.ini
+++ b/docs/tutorials/wiki/src/models/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
testpaths = tutorial
-python_files = *.py
+python_files = test*.py
diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py
index e05e279e2..d6d488ed2 100644
--- a/docs/tutorials/wiki/src/models/setup.py
+++ b/docs/tutorials/wiki/src/models/setup.py
@@ -10,20 +10,20 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'plaster_pastedeploy',
- 'pyramid >= 1.9a',
+ 'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'waitress',
'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'waitress',
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py
index f2b3c9568..830a607f3 100644
--- a/docs/tutorials/wiki/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py
@@ -11,13 +11,12 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
with Configurator(settings=settings) as config:
- config.include('pyramid_chameleon')
config.include('pyramid_tm')
config.include('pyramid_retry')
config.include('pyramid_zodbconn')
config.set_root_factory(root_factory)
- config.add_static_view('static', 'static', cache_max_age=3600)
+ config.include('pyramid_chameleon')
+ config.include('.routes')
config.scan()
- return config.make_wsgi_app()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/models/tutorial/models.py b/docs/tutorials/wiki/src/models/tutorial/models/__init__.py
index 7c6597afa..7c6597afa 100644
--- a/docs/tutorials/wiki/src/models/tutorial/models.py
+++ b/docs/tutorials/wiki/src/models/tutorial/models/__init__.py
diff --git a/docs/tutorials/wiki/src/models/tutorial/pshell.py b/docs/tutorials/wiki/src/models/tutorial/pshell.py
index 3d026291b..a7cfa6a27 100644
--- a/docs/tutorials/wiki/src/models/tutorial/pshell.py
+++ b/docs/tutorials/wiki/src/models/tutorial/pshell.py
@@ -1,5 +1,6 @@
from . import models
+
def setup(env):
request = env['request']
diff --git a/docs/tutorials/wiki/src/models/tutorial/routes.py b/docs/tutorials/wiki/src/models/tutorial/routes.py
new file mode 100644
index 000000000..3c0a37992
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/routes.py
@@ -0,0 +1,2 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/theme.css b/docs/tutorials/wiki/src/models/tutorial/static/theme.css
index 0f4b1a4d4..a70ee557a 100644
--- a/docs/tutorials/wiki/src/models/tutorial/static/theme.css
+++ b/docs/tutorials/wiki/src/models/tutorial/static/theme.css
@@ -17,6 +17,9 @@ h6 {
p {
font-weight: 300;
}
+button, input, optgroup, select, textarea {
+ color: black;
+}
.font-normal {
font-weight: 400;
}
diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/404.pt b/docs/tutorials/wiki/src/models/tutorial/templates/404.pt
new file mode 100644
index 000000000..07298940c
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/templates/404.pt
@@ -0,0 +1,10 @@
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
+
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+ </div>
+
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/models/tutorial/templates/layout.pt
new file mode 100644
index 000000000..9fdaef00f
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/templates/layout.pt
@@ -0,0 +1,62 @@
+<!DOCTYPE html metal:define-macro="layout">
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>Cookiecutter Starter project for the Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div metal:define-slot="content">No content</div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
+ <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
index d63ea8c45..adac4fe35 100644
--- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
@@ -1,65 +1,11 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>Cookiecutter ZODB project for the Pyramid Web Framework</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
- <![endif]-->
- </head>
-
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB Project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
- </div>
- </div>
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, a&nbsp;Pyramid
+ application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
- <div class="row">
- <div class="links">
- <ul>
- <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
- <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
- </ul>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/models/tutorial/tests.py b/docs/tutorials/wiki/src/models/tutorial/tests.py
index ca7a47279..6279d9f66 100644
--- a/docs/tutorials/wiki/src/models/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/models/tutorial/tests.py
@@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase):
testing.tearDown()
def test_my_view(self):
- from .views import my_view
+ from .views.default import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'myproj')
+
diff --git a/docs/tutorials/wiki/src/models/tutorial/views.py b/docs/tutorials/wiki/src/models/tutorial/views.py
deleted file mode 100644
index c1878bdd0..000000000
--- a/docs/tutorials/wiki/src/models/tutorial/views.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from pyramid.view import view_config
-from .models import MyModel
-
-
-@view_config(context=MyModel, renderer='templates/mytemplate.pt')
-def my_view(request):
- return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/models/tutorial/views/__init__.py b/docs/tutorials/wiki/src/models/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki/src/models/tutorial/views/default.py b/docs/tutorials/wiki/src/models/tutorial/views/default.py
new file mode 100644
index 000000000..5d708d15c
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/views/default.py
@@ -0,0 +1,8 @@
+from pyramid.view import view_config
+
+from ..models import MyModel
+
+
+@view_config(context=MyModel, renderer='../templates/mytemplate.pt')
+def my_view(request):
+ return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/models/tutorial/views/notfound.py b/docs/tutorials/wiki/src/models/tutorial/views/notfound.py
new file mode 100644
index 000000000..728791d0a
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.pt')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki/src/tests/MANIFEST.in b/docs/tutorials/wiki/src/tests/MANIFEST.in
index 81beba1b1..05cc195d9 100644
--- a/docs/tutorials/wiki/src/tests/MANIFEST.in
+++ b/docs/tutorials/wiki/src/tests/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki/src/tests/pytest.ini b/docs/tutorials/wiki/src/tests/pytest.ini
index 8b76bc410..a3489cdf8 100644
--- a/docs/tutorials/wiki/src/tests/pytest.ini
+++ b/docs/tutorials/wiki/src/tests/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
testpaths = tutorial
-python_files = *.py
+python_files = test*.py
diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py
index 7011387f6..fa5948acb 100644
--- a/docs/tutorials/wiki/src/tests/setup.py
+++ b/docs/tutorials/wiki/src/tests/setup.py
@@ -10,22 +10,22 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'plaster_pastedeploy',
- 'pyramid >= 1.9a',
+ 'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'waitress',
'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'waitress',
'docutils',
'bcrypt',
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
index 58635ea74..935a5d6d2 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
@@ -15,18 +15,17 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
authn_policy = AuthTktAuthenticationPolicy(
'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
with Configurator(settings=settings) as config:
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
- config.include('pyramid_chameleon')
config.include('pyramid_tm')
config.include('pyramid_retry')
config.include('pyramid_zodbconn')
config.set_root_factory(root_factory)
- config.add_static_view('static', 'static', cache_max_age=3600)
+ config.include('pyramid_chameleon')
+ config.include('.routes')
config.scan()
- return config.make_wsgi_app()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/tests/tutorial/models.py b/docs/tutorials/wiki/src/tests/tutorial/models/__init__.py
index ebd70e912..ebd70e912 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/models.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/models/__init__.py
diff --git a/docs/tutorials/wiki/src/tests/tutorial/pshell.py b/docs/tutorials/wiki/src/tests/tutorial/pshell.py
index 3d026291b..a7cfa6a27 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/pshell.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/pshell.py
@@ -1,5 +1,6 @@
from . import models
+
def setup(env):
request = env['request']
diff --git a/docs/tutorials/wiki/src/tests/tutorial/routes.py b/docs/tutorials/wiki/src/tests/tutorial/routes.py
new file mode 100644
index 000000000..3c0a37992
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/routes.py
@@ -0,0 +1,2 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/tests/tutorial/static/theme.css b/docs/tutorials/wiki/src/tests/tutorial/static/theme.css
index 0f4b1a4d4..a70ee557a 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/static/theme.css
+++ b/docs/tutorials/wiki/src/tests/tutorial/static/theme.css
@@ -17,6 +17,9 @@ h6 {
p {
font-weight: 300;
}
+button, input, optgroup, select, textarea {
+ color: black;
+}
.font-normal {
font-weight: 400;
}
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/404.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/404.pt
new file mode 100644
index 000000000..07298940c
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/404.pt
@@ -0,0 +1,10 @@
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
+
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+ </div>
+
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
index eedb83da4..6438b1569 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
@@ -1,72 +1,27 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
- <![endif]-->
- </head>
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <p tal:condition="logged_in" class="pull-right">
+ <div class="content">
+ <p tal:condition="logged_in" class="pull-right">
<a href="${request.application_url}/logout">Logout</a>
- </p>
- <p>
- Editing <strong><span tal:replace="page.__name__">
- Page Name Goes Here</span></strong>
- </p>
- <p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
- <form action="${save_url}" method="post">
+ </p>
+ <p>
+ Editing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
+ <form action="${save_url}" method="post">
<div class="form-group">
- <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea>
+ <textarea tal:content="page.data"
+ class="form-control" name="body"
+ rows="10" cols="60"></textarea>
</div>
<div class="form-group">
- <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+ <button type="submit"
+ name="form.submitted" value="Save"
+ class="btn btn-default">Save</button>
</div>
- </form>
- </div>
- </div>
+ </form>
</div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt
new file mode 100644
index 000000000..b606e8dad
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt
@@ -0,0 +1,59 @@
+<!DOCTYPE html metal:define-macro="layout">
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title><span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on
+ TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div metal:define-slot="content">No content</div>
+ <div class="content">
+ <p>You can return to the
+ <a href="${request.application_url}">FrontPage</a>.
+ </p>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
index 626db6637..acc4876cf 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
@@ -1,39 +1,7 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>Login - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
- <![endif]-->
- </head>
-
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
+ <div class="content">
<p>
<strong>
Login
@@ -54,22 +22,7 @@
<button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
</div>
</form>
- </div>
- </div>
</div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
index f2a9249ef..911ab0c99 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
@@ -1,72 +1,23 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
- <![endif]-->
- </head>
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <p tal:condition="logged_in" class="pull-right">
+ <div class="content">
+ <p tal:condition="logged_in" class="pull-right">
<a href="${request.application_url}/logout">Logout</a>
- </p>
- <div tal:replace="structure content">
- Page text goes here.
- </div>
- <p>
+ </p>
+ <div tal:replace="structure page_text">
+ Page text goes here.
+ </div>
+ <p>
<a tal:attributes="href edit_url" href="">
Edit this page
</a>
- </p>
- <p>
- Viewing <strong><span tal:replace="page.__name__">
- Page Name Goes Here</span></strong>
- </p>
- <p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
+ </p>
+ <p>
+ Viewing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
</div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py
index 098e9c1bd..ff1c07b7c 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py
@@ -8,12 +8,12 @@ class PageModelTests(unittest.TestCase):
from .models import Page
return Page
- def _makeOne(self, data=u'some data'):
+ def _makeOne(self, data='some data'):
return self._getTargetClass()(data=data)
def test_constructor(self):
instance = self._makeOne()
- self.assertEqual(instance.data, u'some data')
+ self.assertEqual(instance.data, 'some data')
class WikiModelTests(unittest.TestCase):
@@ -43,7 +43,7 @@ class AppmakerTests(unittest.TestCase):
class ViewWikiTests(unittest.TestCase):
def test_it(self):
- from .views import view_wiki
+ from .views.default import view_wiki
context = testing.DummyResource()
request = testing.DummyRequest()
response = view_wiki(context, request)
@@ -51,7 +51,7 @@ class ViewWikiTests(unittest.TestCase):
class ViewPageTests(unittest.TestCase):
def _callFUT(self, context, request):
- from .views import view_page
+ from .views.default import view_page
return view_page(context, request)
def test_it(self):
@@ -64,7 +64,7 @@ class ViewPageTests(unittest.TestCase):
info = self._callFUT(context, request)
self.assertEqual(info['page'], context)
self.assertEqual(
- info['content'],
+ info['page_text'],
'<div class="document">\n'
'<p>Hello <a href="http://example.com/add_page/CruelWorld">'
'CruelWorld</a> '
@@ -77,7 +77,7 @@ class ViewPageTests(unittest.TestCase):
class AddPageTests(unittest.TestCase):
def _callFUT(self, context, request):
- from .views import add_page
+ from .views.default import add_page
return add_page(context, request)
def test_it_notsubmitted(self):
@@ -103,7 +103,7 @@ class AddPageTests(unittest.TestCase):
class EditPageTests(unittest.TestCase):
def _callFUT(self, context, request):
- from .views import edit_page
+ from .views.default import edit_page
return edit_page(context, request)
def test_it_notsubmitted(self):
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views/default.py
index ea2da01af..3a3b170e2 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/views/default.py
@@ -2,30 +2,29 @@ from docutils.core import publish_parts
import re
from pyramid.httpexceptions import HTTPFound
-
-from pyramid.view import (
- view_config,
- forbidden_view_config,
- )
-
from pyramid.security import (
- remember,
forget,
+ remember,
+)
+from pyramid.view import (
+ forbidden_view_config,
+ view_config,
)
-
-from .security import USERS, check_password
-from .models import Page
+from ..models import Page
+from ..security import check_password, USERS
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-@view_config(context='.models.Wiki',
+
+@view_config(context='..models.Wiki',
permission='view')
def view_wiki(context, request):
return HTTPFound(location=request.resource_url(context, 'FrontPage'))
-@view_config(context='.models.Page', renderer='templates/view.pt',
+
+@view_config(context='..models.Page', renderer='../templates/view.pt',
permission='view')
def view_page(context, request):
wiki = context.__parent__
@@ -40,14 +39,15 @@ def view_page(context, request):
add_url = request.application_url + '/add_page/' + word
return '<a href="%s">%s</a>' % (add_url, word)
- content = publish_parts(context.data, writer_name='html')['html_body']
- content = wikiwords.sub(check, content)
+ page_text = publish_parts(context.data, writer_name='html')['html_body']
+ page_text = wikiwords.sub(check, page_text)
edit_url = request.resource_url(context, 'edit_page')
- return dict(page=context, content=content, edit_url=edit_url,
+ return dict(page=context, page_text=page_text, edit_url=edit_url,
logged_in=request.authenticated_userid)
-@view_config(name='add_page', context='.models.Wiki',
- renderer='templates/edit.pt',
+
+@view_config(name='add_page', context='..models.Wiki',
+ renderer='../templates/edit.pt',
permission='edit')
def add_page(context, request):
pagename = request.subpath[0]
@@ -65,8 +65,9 @@ def add_page(context, request):
return dict(page=page, save_url=save_url,
logged_in=request.authenticated_userid)
-@view_config(name='edit_page', context='.models.Page',
- renderer='templates/edit.pt',
+
+@view_config(name='edit_page', context='..models.Page',
+ renderer='../templates/edit.pt',
permission='edit')
def edit_page(context, request):
if 'form.submitted' in request.params:
@@ -77,9 +78,10 @@ def edit_page(context, request):
save_url=request.resource_url(context, 'edit_page'),
logged_in=request.authenticated_userid)
-@view_config(context='.models.Wiki', name='login',
- renderer='templates/login.pt')
-@forbidden_view_config(renderer='templates/login.pt')
+
+@view_config(context='..models.Wiki', name='login',
+ renderer='../templates/login.pt')
+@forbidden_view_config(renderer='../templates/login.pt')
def login(request):
login_url = request.resource_url(request.context, 'login')
referrer = request.url
@@ -104,10 +106,11 @@ def login(request):
came_from=came_from,
login=login,
password=password,
+ title='Login',
)
-@view_config(context='.models.Wiki', name='logout')
+@view_config(context='..models.Wiki', name='logout')
def logout(request):
headers = forget(request)
return HTTPFound(location=request.resource_url(request.context),
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views/notfound.py b/docs/tutorials/wiki/src/tests/tutorial/views/notfound.py
new file mode 100644
index 000000000..d44b4d0e6
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/views/notfound.py
@@ -0,0 +1,12 @@
+from pyramid.view import notfound_view_config
+
+from ..models import Page
+
+
+@notfound_view_config(renderer='../templates/404.pt')
+def notfound_view(request):
+ request.response.status = 404
+ pagename = request.path
+ page = Page(pagename)
+ page.__name__ = pagename
+ return dict(page=page)
diff --git a/docs/tutorials/wiki/src/views/MANIFEST.in b/docs/tutorials/wiki/src/views/MANIFEST.in
index 81beba1b1..05cc195d9 100644
--- a/docs/tutorials/wiki/src/views/MANIFEST.in
+++ b/docs/tutorials/wiki/src/views/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki/src/views/pytest.ini b/docs/tutorials/wiki/src/views/pytest.ini
index 8b76bc410..a3489cdf8 100644
--- a/docs/tutorials/wiki/src/views/pytest.ini
+++ b/docs/tutorials/wiki/src/views/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
testpaths = tutorial
-python_files = *.py
+python_files = test*.py
diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py
index a11ae6c8f..6f3cae397 100644
--- a/docs/tutorials/wiki/src/views/setup.py
+++ b/docs/tutorials/wiki/src/views/setup.py
@@ -10,21 +10,21 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'plaster_pastedeploy',
- 'pyramid >= 1.9a',
+ 'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'waitress',
'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'waitress',
'docutils',
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py
index f2b3c9568..830a607f3 100644
--- a/docs/tutorials/wiki/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py
@@ -11,13 +11,12 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
with Configurator(settings=settings) as config:
- config.include('pyramid_chameleon')
config.include('pyramid_tm')
config.include('pyramid_retry')
config.include('pyramid_zodbconn')
config.set_root_factory(root_factory)
- config.add_static_view('static', 'static', cache_max_age=3600)
+ config.include('pyramid_chameleon')
+ config.include('.routes')
config.scan()
- return config.make_wsgi_app()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/views/tutorial/models.py b/docs/tutorials/wiki/src/views/tutorial/models/__init__.py
index 7c6597afa..7c6597afa 100644
--- a/docs/tutorials/wiki/src/views/tutorial/models.py
+++ b/docs/tutorials/wiki/src/views/tutorial/models/__init__.py
diff --git a/docs/tutorials/wiki/src/views/tutorial/pshell.py b/docs/tutorials/wiki/src/views/tutorial/pshell.py
index 3d026291b..a7cfa6a27 100644
--- a/docs/tutorials/wiki/src/views/tutorial/pshell.py
+++ b/docs/tutorials/wiki/src/views/tutorial/pshell.py
@@ -1,5 +1,6 @@
from . import models
+
def setup(env):
request = env['request']
diff --git a/docs/tutorials/wiki/src/views/tutorial/routes.py b/docs/tutorials/wiki/src/views/tutorial/routes.py
new file mode 100644
index 000000000..3c0a37992
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tutorial/routes.py
@@ -0,0 +1,2 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/theme.css b/docs/tutorials/wiki/src/views/tutorial/static/theme.css
index 0f4b1a4d4..a70ee557a 100644
--- a/docs/tutorials/wiki/src/views/tutorial/static/theme.css
+++ b/docs/tutorials/wiki/src/views/tutorial/static/theme.css
@@ -17,6 +17,9 @@ h6 {
p {
font-weight: 300;
}
+button, input, optgroup, select, textarea {
+ color: black;
+}
.font-normal {
font-weight: 400;
}
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/404.pt b/docs/tutorials/wiki/src/views/tutorial/templates/404.pt
new file mode 100644
index 000000000..07298940c
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/404.pt
@@ -0,0 +1,10 @@
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
+
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+ </div>
+
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt
index 2db3ca79c..488e7a6af 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt
@@ -1,69 +1,24 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
- <![endif]-->
- </head>
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <p>
- Editing <strong><span tal:replace="page.__name__">
- Page Name Goes Here</span></strong>
- </p>
- <p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
- <form action="${save_url}" method="post">
+ <div class="content">
+ <p>
+ Editing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
+ <form action="${save_url}" method="post">
<div class="form-group">
- <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea>
+ <textarea tal:content="page.data"
+ class="form-control" name="body"
+ rows="10" cols="60"></textarea>
</div>
<div class="form-group">
- <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+ <button type="submit"
+ name="form.submitted" value="Save"
+ class="btn btn-default">Save</button>
</div>
- </form>
- </div>
- </div>
+ </form>
</div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt
new file mode 100644
index 000000000..b606e8dad
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt
@@ -0,0 +1,59 @@
+<!DOCTYPE html metal:define-macro="layout">
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title><span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on
+ TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div metal:define-slot="content">No content</div>
+ <div class="content">
+ <p>You can return to the
+ <a href="${request.application_url}">FrontPage</a>.
+ </p>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
index 1feeab5ef..b8a6fbbaf 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
@@ -1,70 +1,20 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
- <![endif]-->
- </head>
-
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <div tal:replace="structure content">
- Page text goes here.
- </div>
- <p>
+ <div class="content">
+ <div tal:replace="structure page_text">
+ Page text goes here.
+ </div>
+ <p>
<a tal:attributes="href edit_url" href="">
Edit this page
</a>
- </p>
- <p>
- Viewing <strong><span tal:replace="page.__name__">
- Page Name Goes Here</span></strong>
- </p>
- <p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
+ </p>
+ <p>
+ Viewing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
</div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/views/tutorial/tests.py b/docs/tutorials/wiki/src/views/tutorial/tests.py
index ca7a47279..6279d9f66 100644
--- a/docs/tutorials/wiki/src/views/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/views/tutorial/tests.py
@@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase):
testing.tearDown()
def test_my_view(self):
- from .views import my_view
+ from .views.default import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'myproj')
+
diff --git a/docs/tutorials/wiki/src/views/tutorial/views/__init__.py b/docs/tutorials/wiki/src/views/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views/default.py
index fd2b0edc1..b4b65a49b 100644
--- a/docs/tutorials/wiki/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki/src/views/tutorial/views/default.py
@@ -4,16 +4,18 @@ import re
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
-from .models import Page
+from ..models import Page
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-@view_config(context='.models.Wiki')
+
+@view_config(context='..models.Wiki')
def view_wiki(context, request):
return HTTPFound(location=request.resource_url(context, 'FrontPage'))
-@view_config(context='.models.Page', renderer='templates/view.pt')
+
+@view_config(context='..models.Page', renderer='../templates/view.pt')
def view_page(context, request):
wiki = context.__parent__
@@ -27,13 +29,14 @@ def view_page(context, request):
add_url = request.application_url + '/add_page/' + word
return '<a href="%s">%s</a>' % (add_url, word)
- content = publish_parts(context.data, writer_name='html')['html_body']
- content = wikiwords.sub(check, content)
+ page_text = publish_parts(context.data, writer_name='html')['html_body']
+ page_text = wikiwords.sub(check, page_text)
edit_url = request.resource_url(context, 'edit_page')
- return dict(page=context, content=content, edit_url=edit_url)
+ return dict(page=context, page_text=page_text, edit_url=edit_url)
-@view_config(name='add_page', context='.models.Wiki',
- renderer='templates/edit.pt')
+
+@view_config(name='add_page', context='..models.Wiki',
+ renderer='../templates/edit.pt')
def add_page(context, request):
pagename = request.subpath[0]
if 'form.submitted' in request.params:
@@ -49,12 +52,13 @@ def add_page(context, request):
page.__parent__ = context
return dict(page=page, save_url=save_url)
-@view_config(name='edit_page', context='.models.Page',
- renderer='templates/edit.pt')
+
+@view_config(name='edit_page', context='..models.Page',
+ renderer='../templates/edit.pt')
def edit_page(context, request):
if 'form.submitted' in request.params:
context.data = request.params['body']
return HTTPFound(location=request.resource_url(context))
return dict(page=context,
- save_url=request.resource_url(context, 'edit_page')) \ No newline at end of file
+ save_url=request.resource_url(context, 'edit_page'))
diff --git a/docs/tutorials/wiki/src/views/tutorial/views/notfound.py b/docs/tutorials/wiki/src/views/tutorial/views/notfound.py
new file mode 100644
index 000000000..d44b4d0e6
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tutorial/views/notfound.py
@@ -0,0 +1,12 @@
+from pyramid.view import notfound_view_config
+
+from ..models import Page
+
+
+@notfound_view_config(renderer='../templates/404.pt')
+def notfound_view(request):
+ request.response.status = 404
+ pagename = request.path
+ page = Page(pagename)
+ page.__name__ = pagename
+ return dict(page=page)
diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst
index fdd218add..a0872e605 100644
--- a/docs/tutorials/wiki/tests.rst
+++ b/docs/tutorials/wiki/tests.rst
@@ -4,58 +4,56 @@
Adding Tests
============
-We will now add tests for the models and the views and a few functional tests
-in ``tests.py``. Tests ensure that an application works, and that it
-continues to work when changes are made in the future.
+We will now add tests for the models and the views and a few functional tests in ``tests.py``.
+Tests ensure that an application works, and that it continues to work when changes are made in the future.
+
Test the models
===============
-We write tests for the ``model`` classes and the ``appmaker``. Changing
-``tests.py``, we'll write a separate test class for each ``model`` class, and
-we'll write a test class for the ``appmaker``.
+We write tests for the ``model`` classes and the ``appmaker``.
+Changing ``tests.py``, we will write a separate test class for each ``model`` class
+We will also write a test class for the ``appmaker``.
+
+To do so, we will retain the ``tutorial.tests.ViewTests`` class that was generated from choosing the ``zodb`` backend option.
+We will add three test classes, one for each of the following:
+
+- the ``Page`` model named ``PageModelTests``
+- the ``Wiki`` model named ``WikiModelTests``
+- the appmaker named ``AppmakerTests``
-To do so, we'll retain the ``tutorial.tests.ViewTests`` class that was
-generated from choosing the ``zodb`` backend option. We'll add three test
-classes: one for the ``Page`` model named ``PageModelTests``, one for the
-``Wiki`` model named ``WikiModelTests``, and one for the appmaker named
-``AppmakerTests``.
Test the views
==============
-We'll modify our ``tests.py`` file, adding tests for each view function we
-added previously. As a result, we'll delete the ``ViewTests`` class that the
-``zodb`` backend option provided, and add four other test classes:
-``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``.
-These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page``
-views.
+We will modify our ``tests.py`` file, adding tests for each view function that we added previously.
+As a result, we will delete the ``ViewTests`` class that the ``zodb`` backend option provided, and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``.
+These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page`` views.
+
Functional tests
================
-We'll test the whole application, covering security aspects that are not
-tested in the unit tests, like logging in, logging out, checking that
-the ``viewer`` user cannot add or edit pages, but the ``editor`` user
-can, and so on.
+We will test the whole application, covering security aspects that are not tested in the unit tests, such as logging in, logging out, checking that the ``viewer`` user cannot add or edit pages, but the ``editor`` user can, and so on.
+
View the results of all our edits to ``tests.py``
=================================================
-Open the ``tutorial/tests.py`` module, and edit it such that it appears as
-follows:
+Open the ``tutorial/tests.py`` module, and edit it such that it appears as follows:
.. literalinclude:: src/tests/tutorial/tests.py
:linenos:
:language: python
+
Running the tests
=================
-We can run these tests by using ``pytest`` similarly to how we did in
-:ref:`running_tests`. Courtesy of the cookiecutter, our testing dependencies have
-already been satisfied and ``pytest`` and coverage have already been
-configured, so we can jump right to running tests.
+We can run these tests by using ``pytest`` similarly to how we did in :ref:`running_tests`.
+Courtesy of the cookiecutter, our testing dependencies have already been satisfied.
+``pytest`` and coverage have already been configured.
+We can jump right to running tests.
On Unix:
@@ -75,3 +73,6 @@ The expected result should look like the following:
.........................
25 passed in 6.87 seconds
+
+If you use Python 3.7, you may see deprecation warnings from the docutils 0.14 package.
+You can apply a [patch](https://sourceforge.net/p/docutils/patches/144/) to fix the issue, or ignore it and wait for the next release of docutils.
diff --git a/docs/tutorials/wiki2/background.rst b/docs/tutorials/wiki2/background.rst
index c14d3cb7d..09315a77d 100644
--- a/docs/tutorials/wiki2/background.rst
+++ b/docs/tutorials/wiki2/background.rst
@@ -17,6 +17,6 @@ variant, etc.) *or* a Windows system of any kind.
.. note::
- This tutorial runs on both Python 2 and 3 without modification.
+ This tutorial runs on Python 3 without modification.
Have fun!
diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst
index 924927cd4..705979065 100644
--- a/docs/tutorials/wiki2/installation.rst
+++ b/docs/tutorials/wiki2/installation.rst
@@ -126,16 +126,6 @@ On Unix
On Windows
^^^^^^^^^^
-Each version of Python uses different paths, so you will need to adjust the path to the command for your Python version. Recent versions of the Python 3 installer for Windows now install a Python launcher.
-
-Python 2.7:
-
-.. code-block:: doscon
-
- c:\Python27\Scripts\virtualenv %VENV%
-
-Python 3.7:
-
.. code-block:: doscon
python -m venv %VENV%
diff --git a/docs/tutorials/wiki2/src/authentication/setup.py b/docs/tutorials/wiki2/src/authentication/setup.py
index e2a30c0e7..f71998afc 100644
--- a/docs/tutorials/wiki2/src/authentication/setup.py
+++ b/docs/tutorials/wiki2/src/authentication/setup.py
@@ -25,8 +25,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
index 8ed90d5b2..2f0210255 100644
--- a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
@@ -1,4 +1,4 @@
-from pyramid.compat import escape
+from html import escape
import re
from docutils.core import publish_parts
diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py
index e2a30c0e7..f71998afc 100644
--- a/docs/tutorials/wiki2/src/authorization/setup.py
+++ b/docs/tutorials/wiki2/src/authorization/setup.py
@@ -25,8 +25,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
index ad271fb46..ad8491b7b 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
@@ -1,4 +1,4 @@
-from pyramid.compat import escape
+from html import escape
import re
from docutils.core import publish_parts
diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py
index 11725dd51..746012a74 100644
--- a/docs/tutorials/wiki2/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki2/src/basiclayout/setup.py
@@ -23,8 +23,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/installation/setup.py b/docs/tutorials/wiki2/src/installation/setup.py
index 11725dd51..746012a74 100644
--- a/docs/tutorials/wiki2/src/installation/setup.py
+++ b/docs/tutorials/wiki2/src/installation/setup.py
@@ -23,8 +23,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py
index 09e3126ea..b9dc9d93f 100644
--- a/docs/tutorials/wiki2/src/models/setup.py
+++ b/docs/tutorials/wiki2/src/models/setup.py
@@ -24,8 +24,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py
index e2a30c0e7..f71998afc 100644
--- a/docs/tutorials/wiki2/src/tests/setup.py
+++ b/docs/tutorials/wiki2/src/tests/setup.py
@@ -25,8 +25,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
index ad271fb46..ad8491b7b 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
@@ -1,4 +1,4 @@
-from pyramid.compat import escape
+from html import escape
import re
from docutils.core import publish_parts
diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py
index e2a30c0e7..f71998afc 100644
--- a/docs/tutorials/wiki2/src/views/setup.py
+++ b/docs/tutorials/wiki2/src/views/setup.py
@@ -25,8 +25,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views/default.py b/docs/tutorials/wiki2/src/views/tutorial/views/default.py
index a866af1de..5e28b64fd 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/views/default.py
@@ -1,4 +1,4 @@
-from pyramid.compat import escape
+from html import escape
import re
from docutils.core import publish_parts
diff --git a/pyproject.toml b/pyproject.toml
index b30e4f465..d8ec559df 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -15,9 +15,6 @@ exclude = '''
| dist
| build
| docs
- | src/pyramid/scaffolds/alchemy
- | src/pyramid/scaffolds/starter
- | src/pyramid/scaffolds/zodb
)/
'''
diff --git a/setup.py b/setup.py
index 0143764b8..3176442e1 100644
--- a/setup.py
+++ b/setup.py
@@ -44,9 +44,9 @@ tests_require = [
docs_extras = [
- 'Sphinx >= 1.8.1',
+ 'Sphinx >= 1.8.1', # Unicode characters in tree diagrams
'docutils',
- 'pylons-sphinx-themes >= 1.0.8',
+ 'pylons-sphinx-themes >= 1.0.8', # Ethical Ads
'pylons_sphinx_latesturl',
'repoze.sphinx.autointerface',
'sphinxcontrib-autoprogram',
@@ -69,7 +69,6 @@ setup(
"Development Status :: 6 - Mature",
"Intended Audience :: Developers",
"Programming Language :: Python",
- "Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
@@ -96,13 +95,9 @@ setup(
package_dir={'': 'src'},
include_package_data=True,
zip_safe=False,
- python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*',
+ python_requires='>=3.4',
install_requires=install_requires,
- extras_require={
- ':python_version<"3.2"': ['repoze.lru >= 0.4'],
- 'testing': testing_extras,
- 'docs': docs_extras,
- },
+ extras_require={'testing': testing_extras, 'docs': docs_extras},
tests_require=tests_require,
test_suite="tests",
entry_points="""\
diff --git a/src/pyramid/asset.py b/src/pyramid/asset.py
index 0d7575a85..a32babe6c 100644
--- a/src/pyramid/asset.py
+++ b/src/pyramid/asset.py
@@ -1,13 +1,11 @@
import os
import pkg_resources
-from pyramid.compat import string_types
-
from pyramid.path import package_path, package_name
def resolve_asset_spec(spec, pname='__main__'):
- if pname and not isinstance(pname, string_types):
+ if pname and not isinstance(pname, str):
pname = pname.__name__ # as package
if os.path.isabs(spec):
return None, spec
diff --git a/src/pyramid/authentication.py b/src/pyramid/authentication.py
index 7cb6b6811..21cfc0c0e 100644
--- a/src/pyramid/authentication.py
+++ b/src/pyramid/authentication.py
@@ -6,28 +6,18 @@ import hashlib
import base64
import re
import time as time_mod
+from urllib.parse import quote, unquote
import warnings
from zope.interface import implementer
from webob.cookies import CookieProfile
-from pyramid.compat import (
- long,
- text_type,
- binary_type,
- url_unquote,
- url_quote,
- bytes_,
- ascii_native_,
- native_,
-)
-
from pyramid.interfaces import IAuthenticationPolicy, IDebugLogger
from pyramid.security import Authenticated, Everyone
-from pyramid.util import strings_differ
+from pyramid.util import strings_differ, bytes_, ascii_, text_
from pyramid.util import SimpleSerializer
VALID_TOKEN = re.compile(r"^[A-Za-z][A-Za-z0-9+_-]*$")
@@ -727,11 +717,7 @@ class AuthTicket(object):
)
def cookie_value(self):
- v = '%s%08x%s!' % (
- self.digest(),
- int(self.time),
- url_quote(self.userid),
- )
+ v = '%s%08x%s!' % (self.digest(), int(self.time), quote(self.userid))
if self.tokens:
v += self.tokens + '!'
v += self.user_data
@@ -759,7 +745,7 @@ def parse_ticket(secret, ticket, ip, hashalg='md5'):
If the ticket cannot be parsed, a ``BadTicket`` exception will be raised
with an explanation.
"""
- ticket = native_(ticket).strip('"')
+ ticket = text_(ticket).strip('"')
digest_size = hashlib.new(hashalg).digest_size * 2
digest = ticket[:digest_size]
try:
@@ -770,7 +756,7 @@ def parse_ticket(secret, ticket, ip, hashalg='md5'):
userid, data = ticket[digest_size + 8 :].split('!', 1)
except ValueError:
raise BadTicket('userid is not followed by !')
- userid = url_unquote(userid)
+ userid = unquote(userid)
if '!' in data:
tokens, user_data = data.split('!', 1)
else: # pragma: no cover (never generated)
@@ -857,9 +843,8 @@ class AuthTktCookieHelper(object):
userid_type_encoders = {
int: ('int', str),
- long: ('int', str),
- text_type: ('b64unicode', lambda x: b64encode(utf_8_encode(x)[0])),
- binary_type: ('b64str', lambda x: b64encode(x)),
+ str: ('b64unicode', lambda x: b64encode(utf_8_encode(x)[0])),
+ bytes: ('b64str', lambda x: b64encode(x)),
}
def __init__(
@@ -879,16 +864,13 @@ class AuthTktCookieHelper(object):
domain=None,
samesite='Lax',
):
-
- serializer = SimpleSerializer()
-
self.cookie_profile = CookieProfile(
cookie_name=cookie_name,
secure=secure,
max_age=max_age,
httponly=http_only,
path=path,
- serializer=serializer,
+ serializer=SimpleSerializer(),
samesite=samesite,
)
@@ -1048,7 +1030,7 @@ class AuthTktCookieHelper(object):
"type provided.".format(type(userid)),
RuntimeWarning,
)
- encoding, encoder = self.userid_type_encoders.get(text_type)
+ encoding, encoder = self.userid_type_encoders.get(str)
userid = str(userid)
userid = encoder(userid)
@@ -1056,9 +1038,9 @@ class AuthTktCookieHelper(object):
new_tokens = []
for token in tokens:
- if isinstance(token, text_type):
+ if isinstance(token, str):
try:
- token = ascii_native_(token)
+ token = ascii_(token)
except UnicodeEncodeError:
raise ValueError("Invalid token %r" % (token,))
if not (isinstance(token, str) and VALID_TOKEN.match(token)):
diff --git a/src/pyramid/authorization.py b/src/pyramid/authorization.py
index 974748765..6056a8d25 100644
--- a/src/pyramid/authorization.py
+++ b/src/pyramid/authorization.py
@@ -4,10 +4,10 @@ from pyramid.interfaces import IAuthorizationPolicy
from pyramid.location import lineage
-from pyramid.compat import is_nonstr_iter
-
from pyramid.security import ACLAllowed, ACLDenied, Allow, Deny, Everyone
+from pyramid.util import is_nonstr_iter
+
@implementer(IAuthorizationPolicy)
class ACLAuthorizationPolicy(object):
diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py
deleted file mode 100644
index 31832c874..000000000
--- a/src/pyramid/compat.py
+++ /dev/null
@@ -1,326 +0,0 @@
-import inspect
-import platform
-import sys
-import types
-
-WIN = platform.system() == 'Windows'
-
-try: # pragma: no cover
- import __pypy__
-
- PYPY = True
-except BaseException: # pragma: no cover
- __pypy__ = None
- PYPY = False
-
-try:
- import cPickle as pickle
-except ImportError: # pragma: no cover
- import pickle
-
-try:
- from functools import lru_cache
-except ImportError:
- from repoze.lru import lru_cache
-
-# PY3 is left as bw-compat but PY2 should be used for most checks.
-PY2 = sys.version_info[0] == 2
-PY3 = sys.version_info[0] == 3
-
-if PY2:
- string_types = (basestring,)
- integer_types = (int, long)
- class_types = (type, types.ClassType)
- text_type = unicode
- binary_type = str
- long = long
-else:
- string_types = (str,)
- integer_types = (int,)
- class_types = (type,)
- text_type = str
- binary_type = bytes
- long = int
-
-
-def text_(s, encoding='latin-1', errors='strict'):
- """ If ``s`` is an instance of ``binary_type``, return
- ``s.decode(encoding, errors)``, otherwise return ``s``"""
- if isinstance(s, binary_type):
- return s.decode(encoding, errors)
- return s
-
-
-def bytes_(s, encoding='latin-1', errors='strict'):
- """ If ``s`` is an instance of ``text_type``, return
- ``s.encode(encoding, errors)``, otherwise return ``s``"""
- if isinstance(s, text_type):
- return s.encode(encoding, errors)
- return s
-
-
-if PY2:
-
- def ascii_native_(s):
- if isinstance(s, text_type):
- s = s.encode('ascii')
- return str(s)
-
-
-else:
-
- def ascii_native_(s):
- if isinstance(s, text_type):
- s = s.encode('ascii')
- return str(s, 'ascii', 'strict')
-
-
-ascii_native_.__doc__ = """
-Python 3: If ``s`` is an instance of ``text_type``, return
-``s.encode('ascii')``, otherwise return ``str(s, 'ascii', 'strict')``
-
-Python 2: If ``s`` is an instance of ``text_type``, return
-``s.encode('ascii')``, otherwise return ``str(s)``
-"""
-
-
-if PY2:
-
- def native_(s, encoding='latin-1', errors='strict'):
- """ If ``s`` is an instance of ``text_type``, return
- ``s.encode(encoding, errors)``, otherwise return ``str(s)``"""
- if isinstance(s, text_type):
- return s.encode(encoding, errors)
- return str(s)
-
-
-else:
-
- def native_(s, encoding='latin-1', errors='strict'):
- """ If ``s`` is an instance of ``text_type``, return
- ``s``, otherwise return ``str(s, encoding, errors)``"""
- if isinstance(s, text_type):
- return s
- return str(s, encoding, errors)
-
-
-native_.__doc__ = """
-Python 3: If ``s`` is an instance of ``text_type``, return ``s``, otherwise
-return ``str(s, encoding, errors)``
-
-Python 2: If ``s`` is an instance of ``text_type``, return
-``s.encode(encoding, errors)``, otherwise return ``str(s)``
-"""
-
-if PY2:
- import urlparse
- from urllib import quote as url_quote
- from urllib import quote_plus as url_quote_plus
- from urllib import unquote as url_unquote
- from urllib import urlencode as url_encode
- from urllib2 import urlopen as url_open
-
- def url_unquote_text(
- v, encoding='utf-8', errors='replace'
- ): # pragma: no cover
- v = url_unquote(v)
- return v.decode(encoding, errors)
-
- def url_unquote_native(
- v, encoding='utf-8', errors='replace'
- ): # pragma: no cover
- return native_(url_unquote_text(v, encoding, errors))
-
-
-else:
- from urllib import parse
-
- urlparse = parse
- from urllib.parse import quote as url_quote
- from urllib.parse import quote_plus as url_quote_plus
- from urllib.parse import unquote as url_unquote
- from urllib.parse import urlencode as url_encode
- from urllib.request import urlopen as url_open
-
- url_unquote_text = url_unquote
- url_unquote_native = url_unquote
-
-
-if PY2: # pragma: no cover
-
- def exec_(code, globs=None, locs=None):
- """Execute code in a namespace."""
- if globs is None:
- frame = sys._getframe(1)
- globs = frame.f_globals
- if locs is None:
- locs = frame.f_locals
- del frame
- elif locs is None:
- locs = globs
- exec("""exec code in globs, locs""")
-
- exec_(
- """def reraise(tp, value, tb=None):
- raise tp, value, tb
-"""
- )
-
-else: # pragma: no cover
- import builtins
-
- exec_ = getattr(builtins, "exec")
-
- def reraise(tp, value, tb=None):
- if value is None:
- value = tp
- if value.__traceback__ is not tb:
- raise value.with_traceback(tb)
- raise value
-
- del builtins
-
-
-if PY2: # pragma: no cover
-
- def iteritems_(d):
- return d.iteritems()
-
- def itervalues_(d):
- return d.itervalues()
-
- def iterkeys_(d):
- return d.iterkeys()
-
-
-else: # pragma: no cover
-
- def iteritems_(d):
- return d.items()
-
- def itervalues_(d):
- return d.values()
-
- def iterkeys_(d):
- return d.keys()
-
-
-if PY2:
- map_ = map
-else:
-
- def map_(*arg):
- return list(map(*arg))
-
-
-if PY2:
-
- def is_nonstr_iter(v):
- return hasattr(v, '__iter__')
-
-
-else:
-
- def is_nonstr_iter(v):
- if isinstance(v, str):
- return False
- return hasattr(v, '__iter__')
-
-
-if PY2:
- im_func = 'im_func'
- im_self = 'im_self'
-else:
- im_func = '__func__'
- im_self = '__self__'
-
-try:
- import configparser
-except ImportError:
- import ConfigParser as configparser
-
-try:
- from http.cookies import SimpleCookie
-except ImportError:
- from Cookie import SimpleCookie
-
-if PY2:
- from cgi import escape
-else:
- from html import escape
-
-if PY2:
- input_ = raw_input
-else:
- input_ = input
-
-if PY2:
- from io import BytesIO as NativeIO
-else:
- from io import StringIO as NativeIO
-
-# "json" is not an API; it's here to support older pyramid_debugtoolbar
-# versions which attempt to import it
-import json
-
-if PY2:
-
- def decode_path_info(path):
- return path.decode('utf-8')
-
-
-else:
- # see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before
- # decoding it to utf-8
- def decode_path_info(path):
- return path.encode('latin-1').decode('utf-8')
-
-
-if PY2:
- from urlparse import unquote as unquote_to_bytes
-
- def unquote_bytes_to_wsgi(bytestring):
- return unquote_to_bytes(bytestring)
-
-
-else:
- # see PEP 3333 for why we decode the path to latin-1
- from urllib.parse import unquote_to_bytes
-
- def unquote_bytes_to_wsgi(bytestring):
- return unquote_to_bytes(bytestring).decode('latin-1')
-
-
-def is_bound_method(ob):
- return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None
-
-
-# support annotations and keyword-only arguments in PY3
-if PY2:
- from inspect import getargspec
-else:
- from inspect import getfullargspec as getargspec
-
-if PY2:
- from itertools import izip_longest as zip_longest
-else:
- from itertools import zip_longest
-
-
-def is_unbound_method(fn):
- """
- This consistently verifies that the callable is bound to a
- class.
- """
- is_bound = is_bound_method(fn)
-
- if not is_bound and inspect.isroutine(fn):
- spec = getargspec(fn)
- has_self = len(spec.args) > 0 and spec.args[0] == 'self'
-
- if PY2 and inspect.ismethod(fn):
- return True
- elif inspect.isfunction(fn) and has_self:
- return True
-
- return False
diff --git a/src/pyramid/config/__init__.py b/src/pyramid/config/__init__.py
index 475f0d9a2..072b654c4 100644
--- a/src/pyramid/config/__init__.py
+++ b/src/pyramid/config/__init__.py
@@ -19,8 +19,6 @@ from pyramid.asset import resolve_asset_spec
from pyramid.authorization import ACLAuthorizationPolicy
-from pyramid.compat import text_, string_types
-
from pyramid.events import ApplicationCreated
from pyramid.exceptions import ConfigurationError
@@ -37,7 +35,7 @@ from pyramid.settings import aslist
from pyramid.threadlocal import manager
-from pyramid.util import WeakOrderedSet, object_description
+from pyramid.util import WeakOrderedSet, get_callable_name, object_description
from pyramid.config.actions import action_method, ActionState
from pyramid.config.predicates import not_
@@ -59,7 +57,6 @@ from pyramid.config.zca import ZCAConfiguratorMixin
from pyramid.path import DottedNameResolver
-empty = text_('')
_marker = object()
not_ = not_ # api
@@ -367,7 +364,7 @@ class Configurator(
self._set_settings(settings)
- if isinstance(debug_logger, string_types):
+ if isinstance(debug_logger, str):
debug_logger = logging.getLogger(debug_logger)
if debug_logger is None:
@@ -489,11 +486,7 @@ class Configurator(
if not hasattr(_registry, 'registerSelfAdapter'):
def registerSelfAdapter(
- required=None,
- provided=None,
- name=empty,
- info=empty,
- event=True,
+ required=None, provided=None, name='', info='', event=True
):
return _registry.registerAdapter(
lambda x: x,
@@ -705,6 +698,7 @@ class Configurator(
``add_directive`` does not participate in conflict detection, and
later calls to ``add_directive`` will override earlier calls.
"""
+ name = get_callable_name(name)
c = self.maybe_dotted(directive)
if not hasattr(self.registry, '_directives'):
self.registry._directives = {}
@@ -759,7 +753,7 @@ class Configurator(
when generating an absolute asset specification. If the
provided ``relative_spec`` argument is already absolute, or if
the ``relative_spec`` is not a string, it is simply returned."""
- if not isinstance(relative_spec, string_types):
+ if not isinstance(relative_spec, str):
return relative_spec
return self._make_spec(relative_spec)
diff --git a/src/pyramid/config/actions.py b/src/pyramid/config/actions.py
index 9c1227d4a..4a526e242 100644
--- a/src/pyramid/config/actions.py
+++ b/src/pyramid/config/actions.py
@@ -5,7 +5,6 @@ import sys
import traceback
from zope.interface import implementer
-from pyramid.compat import reraise
from pyramid.exceptions import (
ConfigurationConflictError,
ConfigurationError,
@@ -14,6 +13,7 @@ from pyramid.exceptions import (
from pyramid.interfaces import IActionInfo
from pyramid.registry import undefer
from pyramid.util import is_nonstr_iter
+from pyramid.util import reraise
class ActionConfiguratorMixin(object):
diff --git a/src/pyramid/config/predicates.py b/src/pyramid/config/predicates.py
index 31e770562..3eb07c17d 100644
--- a/src/pyramid/config/predicates.py
+++ b/src/pyramid/config/predicates.py
@@ -1,12 +1,12 @@
from hashlib import md5
from webob.acceptparse import Accept
-from pyramid.compat import bytes_, is_nonstr_iter
from pyramid.exceptions import ConfigurationError
from pyramid.interfaces import IPredicateList, PHASE1_CONFIG
from pyramid.predicates import Notted
from pyramid.registry import predvalseq
from pyramid.util import TopologicalSorter
+from pyramid.util import is_nonstr_iter, bytes_
MAX_ORDER = 1 << 30
diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py
index 52540c935..4b26b7481 100644
--- a/src/pyramid/config/routes.py
+++ b/src/pyramid/config/routes.py
@@ -1,7 +1,7 @@
import contextlib
+from urllib.parse import urlparse
import warnings
-from pyramid.compat import urlparse
from pyramid.interfaces import (
IRequest,
IRouteRequest,
@@ -358,7 +358,7 @@ class RoutesConfiguratorMixin(object):
# check for an external route; an external route is one which is
# is a full url (e.g. 'http://example.com/{id}')
- parsed = urlparse.urlparse(pattern)
+ parsed = urlparse(pattern)
external_url = pattern
if parsed.hostname:
diff --git a/src/pyramid/config/testing.py b/src/pyramid/config/testing.py
index bba5054e6..9c998840a 100644
--- a/src/pyramid/config/testing.py
+++ b/src/pyramid/config/testing.py
@@ -9,7 +9,7 @@ from pyramid.interfaces import (
from pyramid.renderers import RendererHelper
-from pyramid.traversal import decode_path_info, split_path_info
+from pyramid.traversal import split_path_info
from pyramid.config.actions import action_method
@@ -95,7 +95,7 @@ class TestingConfiguratorMixin(object):
self.context = context
def __call__(self, request):
- path = decode_path_info(request.environ['PATH_INFO'])
+ path = request.path_info
ob = resources[path]
traversed = split_path_info(path)
return {
diff --git a/src/pyramid/config/tweens.py b/src/pyramid/config/tweens.py
index 7fc786a97..c85639d14 100644
--- a/src/pyramid/config/tweens.py
+++ b/src/pyramid/config/tweens.py
@@ -2,13 +2,15 @@ from zope.interface import implementer
from pyramid.interfaces import ITweens
-from pyramid.compat import string_types, is_nonstr_iter
-
from pyramid.exceptions import ConfigurationError
from pyramid.tweens import MAIN, INGRESS, EXCVIEW
-from pyramid.util import is_string_or_iterable, TopologicalSorter
+from pyramid.util import (
+ is_nonstr_iter,
+ is_string_or_iterable,
+ TopologicalSorter,
+)
from pyramid.config.actions import action_method
@@ -105,7 +107,7 @@ class TweensConfiguratorMixin(object):
@action_method
def _add_tween(self, tween_factory, under=None, over=None, explicit=False):
- if not isinstance(tween_factory, string_types):
+ if not isinstance(tween_factory, str):
raise ConfigurationError(
'The "tween_factory" argument to add_tween must be a '
'dotted name to a globally importable object, not %r'
diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py
index 0c4a17376..ac531ecb2 100644
--- a/src/pyramid/config/views.py
+++ b/src/pyramid/config/views.py
@@ -5,6 +5,7 @@ import operator
import os
import warnings
+from urllib.parse import quote, urljoin, urlparse, urlunparse
from webob.acceptparse import Accept
from zope.interface import Interface, implementedBy, implementer
from zope.interface.interfaces import IInterface
@@ -32,13 +33,6 @@ from pyramid.interfaces import (
from pyramid import renderers
from pyramid.asset import resolve_asset_spec
-from pyramid.compat import (
- string_types,
- urlparse,
- url_quote,
- WIN,
- is_nonstr_iter,
-)
from pyramid.decorator import reify
@@ -59,7 +53,12 @@ from pyramid.url import parse_url_overrides
from pyramid.view import AppendSlashNotFoundViewFactory
-from pyramid.util import as_sorted_tuple, TopologicalSorter
+from pyramid.util import (
+ as_sorted_tuple,
+ is_nonstr_iter,
+ TopologicalSorter,
+ WIN,
+)
import pyramid.predicates
import pyramid.viewderivers
@@ -83,9 +82,6 @@ from pyramid.config.predicates import (
sort_accept_offers,
)
-urljoin = urlparse.urljoin
-url_parse = urlparse.urlparse
-
DefaultViewMapper = DefaultViewMapper # bw-compat
preserve_view_attrs = preserve_view_attrs # bw-compat
requestonly = requestonly # bw-compat
@@ -889,7 +885,7 @@ class ViewsConfiguratorMixin(object):
if not IInterface.providedBy(r_context):
r_context = implementedBy(r_context)
- if isinstance(renderer, string_types):
+ if isinstance(renderer, str):
renderer = renderers.RendererHelper(
name=renderer, package=self.package, registry=self.registry
)
@@ -1582,7 +1578,7 @@ class ViewsConfiguratorMixin(object):
):
view = self.maybe_dotted(view)
mapper = self.maybe_dotted(mapper)
- if isinstance(renderer, string_types):
+ if isinstance(renderer, str):
renderer = renderers.RendererHelper(
name=renderer, package=self.package, registry=self.registry
)
@@ -2197,14 +2193,12 @@ class StaticURLInfo(object):
return request.route_url(route_name, **kw)
else:
app_url, qs, anchor = parse_url_overrides(request, kw)
- parsed = url_parse(url)
+ parsed = urlparse(url)
if not parsed.scheme:
- url = urlparse.urlunparse(
- parsed._replace(
- scheme=request.environ['wsgi.url_scheme']
- )
+ url = urlunparse(
+ parsed._replace(scheme=request.scheme)
)
- subpath = url_quote(subpath)
+ subpath = quote(subpath)
result = urljoin(url, subpath)
return result + qs + anchor
@@ -2233,7 +2227,7 @@ class StaticURLInfo(object):
# make sure it ends with a slash
name = name + '/'
- if url_parse(name).netloc:
+ if urlparse(name).netloc:
# it's a URL
# url, spec, route_name
url = name
diff --git a/src/pyramid/csrf.py b/src/pyramid/csrf.py
index fba5d9baa..26c628acc 100644
--- a/src/pyramid/csrf.py
+++ b/src/pyramid/csrf.py
@@ -1,14 +1,20 @@
+from urllib.parse import urlparse
import uuid
from webob.cookies import CookieProfile
from zope.interface import implementer
-from pyramid.compat import bytes_, urlparse, text_
from pyramid.exceptions import BadCSRFOrigin, BadCSRFToken
from pyramid.interfaces import ICSRFStoragePolicy
from pyramid.settings import aslist
-from pyramid.util import SimpleSerializer, is_same_domain, strings_differ
+from pyramid.util import (
+ SimpleSerializer,
+ is_same_domain,
+ strings_differ,
+ bytes_,
+ text_,
+)
@implementer(ICSRFStoragePolicy)
@@ -117,7 +123,6 @@ class CookieCSRFStoragePolicy(object):
path='/',
samesite='Lax',
):
- serializer = SimpleSerializer()
self.cookie_profile = CookieProfile(
cookie_name=cookie_name,
secure=secure,
@@ -125,7 +130,7 @@ class CookieCSRFStoragePolicy(object):
httponly=httponly,
path=path,
domains=[domain],
- serializer=serializer,
+ serializer=SimpleSerializer(),
samesite=samesite,
)
self.cookie_name = cookie_name
@@ -303,7 +308,7 @@ def check_csrf_origin(request, trusted_origins=None, raises=True):
# Parse our origin so we we can extract the required information from
# it.
- originp = urlparse.urlparse(origin)
+ originp = urlparse(origin)
# Ensure that our Referer is also secure.
if originp.scheme != "https":
diff --git a/src/pyramid/encode.py b/src/pyramid/encode.py
index 2cf2247da..ed8e177b8 100644
--- a/src/pyramid/encode.py
+++ b/src/pyramid/encode.py
@@ -1,17 +1,14 @@
-from pyramid.compat import (
- text_type,
- binary_type,
- is_nonstr_iter,
- url_quote as _url_quote,
- url_quote_plus as _quote_plus,
-)
+from urllib.parse import quote as _url_quote
+from urllib.parse import quote_plus as _quote_plus
+
+from pyramid.util import is_nonstr_iter
def url_quote(val, safe=''): # bw compat api
cls = val.__class__
- if cls is text_type:
+ if cls is str:
val = val.encode('utf-8')
- elif cls is not binary_type:
+ elif cls is not bytes:
val = str(val).encode('utf-8')
return _url_quote(val, safe=safe)
@@ -19,9 +16,9 @@ def url_quote(val, safe=''): # bw compat api
# bw compat api (dnr)
def quote_plus(val, safe=''):
cls = val.__class__
- if cls is text_type:
+ if cls is str:
val = val.encode('utf-8')
- elif cls is not binary_type:
+ elif cls is not bytes:
val = str(val).encode('utf-8')
return _quote_plus(val, safe=safe)
@@ -29,8 +26,8 @@ def quote_plus(val, safe=''):
def urlencode(query, doseq=True, quote_via=quote_plus):
"""
An alternate implementation of Python's stdlib
- :func:`urllib.parse.urlencode` function which accepts unicode keys and
- values within the ``query`` dict/sequence; all Unicode keys and values are
+ :func:`urllib.parse.urlencode` function which accepts string keys and
+ values within the ``query`` dict/sequence; all string keys and values are
first converted to UTF-8 before being used to compose the query string.
The value of ``query`` must be a sequence of two-tuples
diff --git a/src/pyramid/httpexceptions.py b/src/pyramid/httpexceptions.py
index 959a45f37..56797dc88 100644
--- a/src/pyramid/httpexceptions.py
+++ b/src/pyramid/httpexceptions.py
@@ -137,22 +137,21 @@ from zope.interface import implementer
from webob import html_escape as _html_escape
from webob.acceptparse import create_accept_header
-from pyramid.compat import class_types, text_type, binary_type, text_
-
from pyramid.interfaces import IExceptionResponse
from pyramid.response import Response
+from pyramid.util import text_
def _no_escape(value):
if value is None:
return ''
- if not isinstance(value, text_type):
+ if not isinstance(value, str):
if hasattr(value, '__unicode__'):
value = value.__unicode__()
- if isinstance(value, binary_type):
+ if isinstance(value, bytes):
value = text_(value, 'utf-8')
else:
- value = text_type(value)
+ value = str(value)
return value
@@ -326,7 +325,7 @@ ${body}'''
args[k.lower()] = escape(v)
body = body_tmpl.substitute(args)
page = page_template.substitute(status=self.status, body=body)
- if isinstance(page, text_type):
+ if isinstance(page, str):
page = page.encode(self.charset if self.charset else 'UTF-8')
self.app_iter = [page]
self.body = page
@@ -1331,7 +1330,7 @@ status_map = {}
code = None
for name, value in list(globals().items()):
if (
- isinstance(value, class_types)
+ isinstance(value, type)
and issubclass(value, HTTPException)
and value not in {HTTPClientError, HTTPServerError}
and not name.startswith('_')
diff --git a/src/pyramid/i18n.py b/src/pyramid/i18n.py
index e99a29aab..a20503be2 100644
--- a/src/pyramid/i18n.py
+++ b/src/pyramid/i18n.py
@@ -8,7 +8,6 @@ from translationstring import (
TranslationStringFactory, # API
)
-from pyramid.compat import PY2
from pyramid.decorator import reify
from pyramid.interfaces import (
@@ -46,12 +45,11 @@ class Localizer(object):
``translate`` method accepts three arguments: ``tstring``
(required), ``domain`` (optional) and ``mapping`` (optional).
When called, it will translate the ``tstring`` translation
- string to a ``unicode`` object using the current locale. If
- the current locale could not be determined, the result of
- interpolation of the default value is returned. The optional
- ``domain`` argument can be used to specify or override the
- domain of the ``tstring`` (useful when ``tstring`` is a normal
- string rather than a translation string). The optional
+ string using the current locale. If the current locale could not be
+ determined, the result of interpolation of the default value is
+ returned. The optional ``domain`` argument can be used to specify
+ or override the domain of the ``tstring`` (useful when ``tstring``
+ is a normal string rather than a translation string). The optional
``mapping`` argument can specify or override the ``tstring``
interpolation mapping, useful when the ``tstring`` argument is
a simple string instead of a translation string.
@@ -75,11 +73,11 @@ class Localizer(object):
def pluralize(self, singular, plural, n, domain=None, mapping=None):
"""
- Return a Unicode string translation by using two
+ Return a string translation by using two
:term:`message identifier` objects as a singular/plural pair
and an ``n`` value representing the number that appears in the
message using gettext plural forms support. The ``singular``
- and ``plural`` objects should be unicode strings. There is no
+ and ``plural`` objects should be strings. There is no
reason to use translation string objects as arguments as all
metadata is ignored.
@@ -353,10 +351,7 @@ class Translations(gettext.GNUTranslations, object):
"""Like ``ugettext()``, but look the message up in the specified
domain.
"""
- if PY2:
- return self._domains.get(domain, self).ugettext(message)
- else:
- return self._domains.get(domain, self).gettext(message)
+ return self._domains.get(domain, self).gettext(message)
def dngettext(self, domain, singular, plural, num):
"""Like ``ngettext()``, but look the message up in the specified
@@ -374,14 +369,7 @@ class Translations(gettext.GNUTranslations, object):
"""Like ``ungettext()`` but look the message up in the specified
domain.
"""
- if PY2:
- return self._domains.get(domain, self).ungettext(
- singular, plural, num
- )
- else:
- return self._domains.get(domain, self).ngettext(
- singular, plural, num
- )
+ return self._domains.get(domain, self).ngettext(singular, plural, num)
class LocalizerRequestMixin(object):
diff --git a/src/pyramid/interfaces.py b/src/pyramid/interfaces.py
index 31bcd7e88..f1e238c6b 100644
--- a/src/pyramid/interfaces.py
+++ b/src/pyramid/interfaces.py
@@ -1,7 +1,5 @@
from zope.interface import Attribute, Interface
-from pyramid.compat import PY2
-
# public API interfaces
@@ -366,19 +364,6 @@ class IDict(Interface):
def values():
""" Return a list of values from the dictionary """
- if PY2:
-
- def iterkeys():
- """ Return an iterator of keys from the dictionary """
-
- def iteritems():
- """ Return an iterator of (k,v) pairs from the dictionary """
-
- def itervalues():
- """ Return an iterator of values from the dictionary """
-
- has_key = __contains__
-
def pop(k, default=None):
""" Pop the key k from the dictionary and return its value. If k
doesn't exist, and default is provided, return the default. If k
diff --git a/src/pyramid/path.py b/src/pyramid/path.py
index c70be99db..47877ce5d 100644
--- a/src/pyramid/path.py
+++ b/src/pyramid/path.py
@@ -7,8 +7,6 @@ from zope.interface import implementer
from pyramid.interfaces import IAssetDescriptor
-from pyramid.compat import string_types
-
ignore_types = [imp.C_EXTENSION, imp.C_BUILTIN]
init_names = [
'__init__%s' % x[0]
@@ -101,7 +99,7 @@ class Resolver(object):
if package in (None, CALLER_PACKAGE):
self.package = package
else:
- if isinstance(package, string_types):
+ if isinstance(package, str):
try:
__import__(package)
except ImportError:
@@ -307,7 +305,7 @@ class DottedNameResolver(Resolver):
v = r.resolve('xml') # v is the xml module
"""
- if not isinstance(dotted, string_types):
+ if not isinstance(dotted, str):
raise ValueError('%r is not a string' % (dotted,))
package = self.package
if package is CALLER_PACKAGE:
@@ -328,7 +326,7 @@ class DottedNameResolver(Resolver):
v = r.maybe_resolve(xml)
# v is the xml module; no exception raised
"""
- if isinstance(dotted, string_types):
+ if isinstance(dotted, str):
package = self.package
if package is CALLER_PACKAGE:
package = caller_package()
diff --git a/src/pyramid/predicates.py b/src/pyramid/predicates.py
index 280f6c03c..5a1127fb3 100644
--- a/src/pyramid/predicates.py
+++ b/src/pyramid/predicates.py
@@ -2,8 +2,6 @@ import re
from pyramid.exceptions import ConfigurationError
-from pyramid.compat import is_nonstr_iter
-
from pyramid.csrf import check_csrf_token
from pyramid.traversal import (
find_interface,
@@ -12,7 +10,7 @@ from pyramid.traversal import (
)
from pyramid.urldispatch import _compile_route
-from pyramid.util import as_sorted_tuple, object_description
+from pyramid.util import as_sorted_tuple, is_nonstr_iter, object_description
_marker = object()
diff --git a/src/pyramid/registry.py b/src/pyramid/registry.py
index c24125830..7b2547dd7 100644
--- a/src/pyramid/registry.py
+++ b/src/pyramid/registry.py
@@ -4,15 +4,12 @@ import threading
from zope.interface import implementer
from zope.interface.registry import Components
-from pyramid.compat import text_
from pyramid.decorator import reify
from pyramid.interfaces import IIntrospector, IIntrospectable, ISettings
from pyramid.path import CALLER_PACKAGE, caller_package
-empty = text_('')
-
class Registry(Components, dict):
""" A registry object is an :term:`application registry`.
@@ -77,7 +74,7 @@ class Registry(Components, dict):
return result
def registerSelfAdapter(
- self, required=None, provided=None, name=empty, info=empty, event=True
+ self, required=None, provided=None, name='', info='', event=True
):
# registerAdapter analogue which always returns the object itself
# when required is matched
diff --git a/src/pyramid/renderers.py b/src/pyramid/renderers.py
index a8e3ec16f..832369fd4 100644
--- a/src/pyramid/renderers.py
+++ b/src/pyramid/renderers.py
@@ -8,8 +8,6 @@ from zope.interface.registry import Components
from pyramid.interfaces import IJSONAdapter, IRendererFactory, IRendererInfo
-from pyramid.compat import string_types, text_type
-
from pyramid.csrf import get_csrf_token
from pyramid.decorator import reify
@@ -169,7 +167,7 @@ def get_renderer(renderer_name, package=None, registry=None):
def string_renderer_factory(info):
def _render(value, system):
- if not isinstance(value, string_types):
+ if not isinstance(value, str):
value = str(value)
request = system.get('request')
if request is not None:
@@ -485,7 +483,7 @@ class RendererHelper(object):
response = response_factory(request)
if result is not None:
- if isinstance(result, text_type):
+ if isinstance(result, str):
response.text = result
elif isinstance(result, bytes):
response.body = result
diff --git a/src/pyramid/request.py b/src/pyramid/request.py
index 907b4477f..23c00468d 100644
--- a/src/pyramid/request.py
+++ b/src/pyramid/request.py
@@ -13,14 +13,17 @@ from pyramid.interfaces import (
ISessionFactory,
)
-from pyramid.compat import text_, bytes_, native_, iteritems_
-
from pyramid.decorator import reify
from pyramid.i18n import LocalizerRequestMixin
from pyramid.response import Response, _get_response_factory
from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin
from pyramid.url import URLMethodsMixin
-from pyramid.util import InstancePropertyHelper, InstancePropertyMixin
+from pyramid.util import (
+ InstancePropertyHelper,
+ InstancePropertyMixin,
+ text_,
+ bytes_,
+)
from pyramid.view import ViewMethodsMixin
@@ -281,7 +284,7 @@ def call_app_with_subpath_as_path_info(request, app):
# compute new_path_info
new_path_info = '/' + '/'.join(
- [native_(x.encode('utf-8'), 'latin-1') for x in subpath]
+ [text_(x.encode('utf-8'), 'latin-1') for x in subpath]
)
if new_path_info != '/': # don't want a sole double-slash
@@ -328,7 +331,7 @@ def apply_request_extensions(request, extensions=None):
if extensions is None:
extensions = request.registry.queryUtility(IRequestExtensions)
if extensions is not None:
- for name, fn in iteritems_(extensions.methods):
+ for name, fn in extensions.methods.items():
method = fn.__get__(request, request.__class__)
setattr(request, name, method)
diff --git a/src/pyramid/response.py b/src/pyramid/response.py
index 38f9fa1ce..ea4677226 100644
--- a/src/pyramid/response.py
+++ b/src/pyramid/response.py
@@ -100,14 +100,12 @@ class FileIter(object):
def __iter__(self):
return self
- def next(self):
+ def __next__(self):
val = self.file.read(self.block_size)
if not val:
raise StopIteration
return val
- __next__ = next # py3
-
def close(self):
self.file.close()
@@ -214,8 +212,4 @@ def _guess_type(path):
content_type, content_encoding = mimetypes.guess_type(path, strict=False)
if content_type is None:
content_type = 'application/octet-stream'
- # str-ifying content_type is a workaround for a bug in Python 2.7.7
- # on Windows where mimetypes.guess_type returns unicode for the
- # content_type.
- content_type = str(content_type)
return content_type, content_encoding
diff --git a/src/pyramid/scripts/prequest.py b/src/pyramid/scripts/prequest.py
index e8f5ff8b3..eb2032419 100644
--- a/src/pyramid/scripts/prequest.py
+++ b/src/pyramid/scripts/prequest.py
@@ -2,8 +2,8 @@ import base64
import argparse
import sys
import textwrap
+from urllib.parse import unquote
-from pyramid.compat import url_unquote
from pyramid.request import Request
from pyramid.scripts.common import get_config_loader
from pyramid.scripts.common import parse_vars
@@ -152,7 +152,7 @@ class PRequestCommand(object):
except ValueError:
qs = ''
- path = url_unquote(path)
+ path = unquote(path)
headers = {}
if self.args.login:
diff --git a/src/pyramid/scripts/proutes.py b/src/pyramid/scripts/proutes.py
index 2bce7d1de..78c2295d5 100644
--- a/src/pyramid/scripts/proutes.py
+++ b/src/pyramid/scripts/proutes.py
@@ -7,7 +7,6 @@ import re
from zope.interface import Interface
from pyramid.paster import bootstrap
-from pyramid.compat import string_types
from pyramid.interfaces import IRouteRequest
from pyramid.config import not_
@@ -188,7 +187,7 @@ def get_route_data(route, registry):
view_request_methods[view_module] = []
view_request_methods_order.append(view_module)
- if isinstance(request_method, string_types):
+ if isinstance(request_method, str):
request_method = (request_method,)
elif isinstance(request_method, not_):
request_method = ('!%s' % request_method.value,)
diff --git a/src/pyramid/scripts/pserve.py b/src/pyramid/scripts/pserve.py
index 581479d65..7d68521a4 100644
--- a/src/pyramid/scripts/pserve.py
+++ b/src/pyramid/scripts/pserve.py
@@ -19,8 +19,6 @@ import webbrowser
import hupper
-from pyramid.compat import PY2
-
from pyramid.scripts.common import get_config_loader
from pyramid.scripts.common import parse_vars
from pyramid.path import AssetResolver
@@ -380,18 +378,15 @@ def cherrypy_server_runner(
server = WSGIServer(bind_addr, app, server_name=server_name, **kwargs)
if ssl_pem is not None:
- if PY2:
- server.ssl_certificate = server.ssl_private_key = ssl_pem
- else:
- # creates wsgiserver.ssl_builtin as side-effect
- try:
- from cheroot.server import get_ssl_adapter_class
- from cheroot.ssl.builtin import BuiltinSSLAdapter
- except ImportError:
- from cherrypy.wsgiserver import get_ssl_adapter_class
- from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter
- get_ssl_adapter_class()
- server.ssl_adapter = BuiltinSSLAdapter(ssl_pem, ssl_pem)
+ # creates wsgiserver.ssl_builtin as side-effect
+ try:
+ from cheroot.server import get_ssl_adapter_class
+ from cheroot.ssl.builtin import BuiltinSSLAdapter
+ except ImportError:
+ from cherrypy.wsgiserver import get_ssl_adapter_class
+ from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter
+ get_ssl_adapter_class()
+ server.ssl_adapter = BuiltinSSLAdapter(ssl_pem, ssl_pem)
if protocol_version:
server.protocol = protocol_version
diff --git a/src/pyramid/scripts/pshell.py b/src/pyramid/scripts/pshell.py
index e63114d18..a9f02e408 100644
--- a/src/pyramid/scripts/pshell.py
+++ b/src/pyramid/scripts/pshell.py
@@ -6,7 +6,6 @@ import sys
import textwrap
import pkg_resources
-from pyramid.compat import exec_
from pyramid.util import DottedNameResolver
from pyramid.util import make_contextmanager
from pyramid.paster import bootstrap
@@ -214,7 +213,7 @@ class PShellCommand(object):
if self.pystartup and os.path.isfile(self.pystartup):
with open(self.pystartup, 'rb') as fp:
- exec_(fp.read().decode('utf-8'), env)
+ exec(fp.read().decode('utf-8'), env)
if '__builtins__' in env:
del env['__builtins__']
diff --git a/src/pyramid/scripts/pviews.py b/src/pyramid/scripts/pviews.py
index 891dc4709..d2a4bfa40 100644
--- a/src/pyramid/scripts/pviews.py
+++ b/src/pyramid/scripts/pviews.py
@@ -70,7 +70,7 @@ class PViewsCommand(object):
def _find_multi_routes(self, mapper, request):
infos = []
- path = request.environ['PATH_INFO']
+ path = request.path_info
# find all routes that match path, regardless of predicates
for route in mapper.get_routes():
match = route.match(path)
diff --git a/src/pyramid/security.py b/src/pyramid/security.py
index 08ae295d8..61819588b 100644
--- a/src/pyramid/security.py
+++ b/src/pyramid/security.py
@@ -8,7 +8,6 @@ from pyramid.interfaces import (
IViewClassifier,
)
-from pyramid.compat import map_
from pyramid.threadlocal import get_current_registry
Everyone = 'system.Everyone'
@@ -113,7 +112,7 @@ def forget(request):
def principals_allowed_by_permission(context, permission):
""" Provided a ``context`` (a resource object), and a ``permission``
- (a string or unicode object), if an :term:`authorization policy` is
+ string, if an :term:`authorization policy` is
in effect, return a sequence of :term:`principal` ids that possess
the permission in the ``context``. If no authorization policy is
in effect, this will return a sequence with the single value
@@ -149,7 +148,7 @@ def view_execution_permitted(context, request, name=''):
"""
reg = _get_registry(request)
- provides = [IViewClassifier] + map_(providedBy, (request, context))
+ provides = [IViewClassifier] + [providedBy(x) for x in (request, context)]
# XXX not sure what to do here about using _find_views or analogue;
# for now let's just keep it as-is
view = reg.adapters.lookup(provides, ISecuredView, name=name)
@@ -341,7 +340,7 @@ class AuthorizationAPIMixin(object):
``request.context`` attribute.
:param permission: Does this request have the given permission?
- :type permission: unicode, str
+ :type permission: str
:param context: A resource object or ``None``
:type context: object
:returns: Either :class:`pyramid.security.Allowed` or
diff --git a/src/pyramid/session.py b/src/pyramid/session.py
index 68e0c506c..70ac4f55f 100644
--- a/src/pyramid/session.py
+++ b/src/pyramid/session.py
@@ -1,5 +1,6 @@
import binascii
import os
+import pickle
import time
from zope.deprecation import deprecated
@@ -7,11 +8,12 @@ from zope.interface import implementer
from webob.cookies import JSONSerializer, SignedSerializer
-from pyramid.compat import pickle, PY2, text_, bytes_, native_
from pyramid.csrf import check_csrf_origin, check_csrf_token
from pyramid.interfaces import ISession
+from pyramid.util import text_, bytes_
+
def manage_accessed(wrapped):
""" Decorator which causes a cookie to be renewed when an accessor
@@ -255,12 +257,6 @@ def BaseCookieSessionFactory(
__len__ = manage_accessed(dict.__len__)
__iter__ = manage_accessed(dict.__iter__)
- if PY2:
- iteritems = manage_accessed(dict.iteritems)
- itervalues = manage_accessed(dict.itervalues)
- iterkeys = manage_accessed(dict.iterkeys)
- has_key = manage_accessed(dict.has_key)
-
# modifying dictionary methods
clear = manage_changed(dict.clear)
update = manage_changed(dict.update)
@@ -309,7 +305,7 @@ def BaseCookieSessionFactory(
exception is not None
): # dont set a cookie during exceptions
return False
- cookieval = native_(
+ cookieval = text_(
serializer.dumps((self.accessed, self.created, dict(self)))
)
if len(cookieval) > 4064:
diff --git a/src/pyramid/settings.py b/src/pyramid/settings.py
index af9433840..d1eb4ff14 100644
--- a/src/pyramid/settings.py
+++ b/src/pyramid/settings.py
@@ -1,5 +1,3 @@
-from pyramid.compat import string_types
-
truthy = frozenset(('t', 'true', 'y', 'yes', 'on', '1'))
falsey = frozenset(('f', 'false', 'n', 'no', 'off', '0'))
@@ -17,7 +15,7 @@ def asbool(s):
def aslist_cronly(value):
- if isinstance(value, string_types):
+ if isinstance(value, str):
value = filter(None, [x.strip() for x in value.splitlines()])
return list(value)
diff --git a/src/pyramid/static.py b/src/pyramid/static.py
index 58ad97a46..e3561e93e 100644
--- a/src/pyramid/static.py
+++ b/src/pyramid/static.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+from functools import lru_cache
import json
import os
@@ -8,8 +9,6 @@ from pkg_resources import resource_exists, resource_filename, resource_isdir
from pyramid.asset import abspath_from_asset_spec, resolve_asset_spec
-from pyramid.compat import lru_cache, text_
-
from pyramid.httpexceptions import HTTPNotFound, HTTPMovedPermanently
from pyramid.path import caller_package
@@ -18,8 +17,6 @@ from pyramid.response import _guess_type, FileResponse
from pyramid.traversal import traversal_path_info
-slash = text_('/')
-
class static_view(object):
""" An instance of this class is a callable which can act as a
@@ -91,7 +88,7 @@ class static_view(object):
if self.use_subpath:
path_tuple = request.subpath
else:
- path_tuple = traversal_path_info(request.environ['PATH_INFO'])
+ path_tuple = traversal_path_info(request.path_info)
path = _secure_path(path_tuple)
if path is None:
@@ -160,7 +157,7 @@ def _secure_path(path_tuple):
return None
if any([_contains_slash(item) for item in path_tuple]):
return None
- encoded = slash.join(path_tuple) # will be unicode
+ encoded = '/'.join(path_tuple) # will be unicode
return encoded
diff --git a/src/pyramid/testing.py b/src/pyramid/testing.py
index f700b5a4e..6831ea4e2 100644
--- a/src/pyramid/testing.py
+++ b/src/pyramid/testing.py
@@ -8,8 +8,6 @@ from zope.interface import implementer, alsoProvides
from pyramid.interfaces import IRequest, ISession
-from pyramid.compat import PY3, PYPY, class_types, text_
-
from pyramid.config import Configurator
from pyramid.decorator import reify
from pyramid.path import caller_package
@@ -28,7 +26,7 @@ from pyramid.threadlocal import get_current_registry, manager
from pyramid.i18n import LocalizerRequestMixin
from pyramid.request import CallbackMethodsMixin
from pyramid.url import URLMethodsMixin
-from pyramid.util import InstancePropertyMixin
+from pyramid.util import InstancePropertyMixin, PYPY, text_
from pyramid.view import ViewMethodsMixin
@@ -640,11 +638,9 @@ def skip_on(*platforms): # pragma: no cover
skip = True
if platform == 'pypy' and PYPY:
skip = True
- if platform == 'py3' and PY3:
- skip = True
def decorator(func):
- if isinstance(func, class_types):
+ if isinstance(func, type):
if skip:
return None
else:
diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py
index 338b49083..9ed5754b7 100644
--- a/src/pyramid/traversal.py
+++ b/src/pyramid/traversal.py
@@ -1,3 +1,6 @@
+from functools import lru_cache
+from urllib.parse import unquote_to_bytes
+
from zope.interface import implementer
from zope.interface.interfaces import IInterface
@@ -8,29 +11,15 @@ from pyramid.interfaces import (
VH_ROOT_KEY,
)
-from pyramid.compat import (
- PY2,
- native_,
- text_,
- ascii_native_,
- text_type,
- binary_type,
- is_nonstr_iter,
- decode_path_info,
- unquote_bytes_to_wsgi,
- lru_cache,
-)
-
from pyramid.encode import url_quote
from pyramid.exceptions import URLDecodeError
from pyramid.location import lineage
from pyramid.threadlocal import get_current_registry
+from pyramid.util import ascii_, is_nonstr_iter, text_
PATH_SEGMENT_SAFE = "~!$&'()*+,;=:@" # from webob
PATH_SAFE = PATH_SEGMENT_SAFE + "/"
-empty = text_('')
-
def find_root(resource):
""" Find the root node in the resource tree to which ``resource``
@@ -68,16 +57,12 @@ def find_resource(resource, path):
object supplied to the function as the ``resource`` argument. If an
empty string is passed as ``path``, the ``resource`` passed in will
be returned. Resource path strings must be escaped in the following
- manner: each Unicode path segment must be encoded as UTF-8 and as
- each path segment must escaped via Python's :mod:`urllib.quote`.
- For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or
- ``to%20the/La%20Pe%C3%B1a`` (relative). The
+ manner: each path segment must be UTF-8 encoded and escaped via Python's
+ :mod:`urllib.quote`. For example, ``/path/to%20the/La%20Pe%C3%B1a``
+ (absolute) or ``to%20the/La%20Pe%C3%B1a`` (relative). The
:func:`pyramid.traversal.resource_path` function generates strings
- which follow these rules (albeit only absolute ones).
-
- Rules for passing *text* (Unicode) as the ``path`` argument are the same
- as those for a string. In particular, the text may not have any nonascii
- characters in it.
+ which follow these rules (albeit only absolute ones). The text may not
+ have any non-ASCII characters in it.
Rules for passing a *tuple* as the ``path`` argument: if the first
element in the path tuple is the empty string (for example ``('',
@@ -88,14 +73,13 @@ def find_resource(resource, path):
traversal will begin at the resource object supplied to the function
as the ``resource`` argument. If an empty sequence is passed as
``path``, the ``resource`` passed in itself will be returned. No
- URL-quoting or UTF-8-encoding of individual path segments within
- the tuple is required (each segment may be any string or unicode
- object representing a resource name). Resource path tuples generated by
- :func:`pyramid.traversal.resource_path_tuple` can always be
- resolved by ``find_resource``.
+ URL-quoting of individual path segments within the tuple is required
+ (each segment may be any string representing a resource name). Resource
+ path tuples generated by :func:`pyramid.traversal.resource_path_tuple` can
+ always be resolved by ``find_resource``.
"""
- if isinstance(path, text_type):
- path = ascii_native_(path)
+ if isinstance(path, str):
+ path = ascii_(path)
D = traverse(resource, path)
view_name = D['view_name']
context = D['context']
@@ -137,8 +121,8 @@ def resource_path(resource, *elements):
``/`` character in a path string represents that the path is absolute).
Resource path strings returned will be escaped in the following
- manner: each unicode path segment will be encoded as UTF-8 and
- each path segment will be escaped via Python's :mod:`urllib.quote`.
+ manner: each path segment will be encoded as UTF-8 and escaped via
+ Python's :mod:`urllib.quote`.
For example, ``/path/to%20the/La%20Pe%C3%B1a``.
This function is a logical inverse of
@@ -151,7 +135,7 @@ def resource_path(resource, *elements):
Each segment in the path string returned will use the ``__name__``
attribute of the resource it represents within the resource tree. Each
- of these segments *should* be a unicode or string object (as per the
+ of these segments *should* be a string (as per the
contract of :term:`location`-awareness). However, no conversion or
safety checking of resource names is performed. For instance, if one of
the resources in your tree has a ``__name__`` which (by error) is a
@@ -187,47 +171,49 @@ def traverse(resource, path):
A definition of each value in the returned dictionary:
- ``context``: The :term:`context` (a :term:`resource` object) found
- via traversal or url dispatch. If the ``path`` passed in is the
+ via traversal or URL dispatch. If the ``path`` passed in is the
empty string, the value of the ``resource`` argument passed to this
function is returned.
- ``root``: The resource object at which :term:`traversal` begins.
- If the ``resource`` passed in was found via url dispatch or if the
+ If the ``resource`` passed in was found via URL dispatch or if the
``path`` passed in was relative (non-absolute), the value of the
``resource`` argument passed to this function is returned.
- ``view_name``: The :term:`view name` found during
- :term:`traversal` or :term:`url dispatch`; if the ``resource`` was
+ :term:`traversal` or :term:`URL dispatch`; if the ``resource`` was
found via traversal, this is usually a representation of the
path segment which directly follows the path to the ``context``
- in the ``path``. The ``view_name`` will be a Unicode object or
- the empty string. The ``view_name`` will be the empty string if
+ in the ``path``. The ``view_name`` will be a string. The
+ ``view_name`` will be the empty string if
there is no element which follows the ``context`` path. An
example: if the path passed is ``/foo/bar``, and a resource
object is found at ``/foo`` (but not at ``/foo/bar``), the 'view
- name' will be ``u'bar'``. If the ``resource`` was found via
- urldispatch, the view_name will be the name the route found was
- registered with.
+ name' will be ``'bar'``. If the ``resource`` was found via
+ URL dispatch, the ``view_name`` will be the empty string unless
+ the ``traverse`` predicate was specified or the ``*traverse`` route
+ pattern was used, at which point normal traversal rules dictate the
+ result.
- ``subpath``: For a ``resource`` found via :term:`traversal`, this
is a sequence of path segments found in the ``path`` that follow
- the ``view_name`` (if any). Each of these items is a Unicode
- object. If no path segments follow the ``view_name``, the
+ the ``view_name`` (if any). Each of these items is a string.
+ If no path segments follow the ``view_name``, the
subpath will be the empty sequence. An example: if the path
passed is ``/foo/bar/baz/buz``, and a resource object is found at
``/foo`` (but not ``/foo/bar``), the 'view name' will be
- ``u'bar'`` and the :term:`subpath` will be ``[u'baz', u'buz']``.
- For a ``resource`` found via url dispatch, the subpath will be a
+ ``'bar'`` and the :term:`subpath` will be ``['baz', 'buz']``.
+ For a ``resource`` found via URL dispatch, the subpath will be a
sequence of values discerned from ``*subpath`` in the route
pattern matched or the empty sequence.
- ``traversed``: The sequence of path elements traversed from the
root to find the ``context`` object during :term:`traversal`.
- Each of these items is a Unicode object. If no path segments
+ Each of these items is a string. If no path segments
were traversed to find the ``context`` object (e.g. if the
``path`` provided is the empty string), the ``traversed`` value
will be the empty sequence. If the ``resource`` is a resource found
- via :term:`url dispatch`, traversed will be None.
+ via :term:`URL dispatch`, traversed will be None.
- ``virtual_root``: A resource object representing the 'virtual' root
of the resource tree being traversed during :term:`traversal`.
@@ -243,10 +229,10 @@ def traverse(resource, path):
- ``virtual_root_path`` -- If :term:`traversal` was used to find
the ``resource``, this will be the sequence of path elements
traversed to find the ``virtual_root`` resource. Each of these
- items is a Unicode object. If no path segments were traversed
+ items is a string. If no path segments were traversed
to find the ``virtual_root`` resource (e.g. if virtual hosting is
not in effect), the ``traversed`` value will be the empty list.
- If url dispatch was used to find the ``resource``, this will be
+ If URL dispatch was used to find the ``resource``, this will be
``None``.
If the path cannot be resolved, a :exc:`KeyError` will be raised.
@@ -260,9 +246,9 @@ def traverse(resource, path):
object supplied to the function as the ``resource`` argument. If an
empty string is passed as ``path``, the ``resource`` passed in will
be returned. Resource path strings must be escaped in the following
- manner: each Unicode path segment must be encoded as UTF-8 and
- each path segment must escaped via Python's :mod:`urllib.quote`.
- For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or
+ manner: each path segment must be encoded as UTF-8 and escaped via
+ Python's :mod:`urllib.quote`. For example,
+ ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or
``to%20the/La%20Pe%C3%B1a`` (relative). The
:func:`pyramid.traversal.resource_path` function generates strings
which follow these rules (albeit only absolute ones).
@@ -277,26 +263,24 @@ def traverse(resource, path):
as the ``resource`` argument. If an empty sequence is passed as
``path``, the ``resource`` passed in itself will be returned. No
URL-quoting or UTF-8-encoding of individual path segments within
- the tuple is required (each segment may be any string or unicode
- object representing a resource name).
+ the tuple is required (each segment may be any string representing
+ a resource name).
- Explanation of the conversion of ``path`` segment values to
- Unicode during traversal: Each segment is URL-unquoted, and
- decoded into Unicode. Each segment is assumed to be encoded using
- the UTF-8 encoding (or a subset, such as ASCII); a
+ Explanation of the decoding of ``path`` segment values during traversal:
+ Each segment is URL-unquoted, and UTF-8 decoded. Each segment is assumed
+ to be encoded using the UTF-8 encoding (or a subset, such as ASCII); a
:exc:`pyramid.exceptions.URLDecodeError` is raised if a segment
cannot be decoded. If a segment name is empty or if it is ``.``,
it is ignored. If a segment name is ``..``, the previous segment
is deleted, and the ``..`` is ignored. As a result of this
process, the return values ``view_name``, each element in the
``subpath``, each element in ``traversed``, and each element in
- the ``virtual_root_path`` will be Unicode as opposed to a string,
- and will be URL-decoded.
+ the ``virtual_root_path`` will be decoded strings.
"""
if is_nonstr_iter(path):
- # the traverser factory expects PATH_INFO to be a string, not
- # unicode and it expects path segments to be utf-8 and
+ # the traverser factory expects PATH_INFO to be a string and it
+ # expects path segments to be utf-8 and
# urlencoded (it's the same traverser which accepts PATH_INFO
# from user agents; user agents always send strings).
if path:
@@ -312,7 +296,7 @@ def traverse(resource, path):
# step rather than later down the line as the result of calling
# ``traversal_path``).
- path = ascii_native_(path)
+ path = ascii_(path)
if path and path[0] == '/':
resource = find_root(resource)
@@ -357,7 +341,7 @@ def resource_path_tuple(resource, *elements):
Each segment in the path tuple returned will equal the ``__name__``
attribute of the resource it represents within the resource tree. Each
- of these segments *should* be a unicode or string object (as per the
+ of these segments *should* be a string (as per the
contract of :term:`location`-awareness). However, no conversion or
safety checking of resource names is performed. For instance, if one of
the resources in your tree has a ``__name__`` which (by error) is a
@@ -439,14 +423,13 @@ def traversal_path(path):
""" Variant of :func:`pyramid.traversal.traversal_path_info` suitable for
decoding paths that are URL-encoded.
- If this function is passed a Unicode object instead of a sequence of
- bytes as ``path``, that Unicode object *must* directly encodeable to
- ASCII. For example, u'/foo' will work but u'/<unprintable unicode>' (a
- Unicode object with characters that cannot be encoded to ascii) will
- not. A :exc:`UnicodeEncodeError` will be raised if the Unicode cannot be
+ If this function is passed a string, it *must* be directly encodeable to
+ ASCII. For example, '/foo' will work but '/<unprintable unicode>' (a
+ string object with characters that cannot be encoded to ASCII) will
+ not. A :exc:`UnicodeEncodeError` will be raised if the string cannot be
encoded directly to ASCII.
"""
- if isinstance(path, text_type):
+ if isinstance(path, str):
# must not possess characters outside ascii
path = path.encode('ascii')
# we unquote this path exactly like a PEP 3333 server would
@@ -461,18 +444,11 @@ def traversal_path_info(path):
already-URL-decoded ``str`` type as if it had come to us from an upstream
WSGI server as the ``PATH_INFO`` environ variable.
- The ``path`` is first decoded to from its WSGI representation to Unicode;
- it is decoded differently depending on platform:
-
- - On Python 2, ``path`` is decoded to Unicode from bytes using the UTF-8
- decoding directly; a :exc:`pyramid.exc.URLDecodeError` is raised if a the
- URL cannot be decoded.
-
- - On Python 3, as per the PEP 3333 spec, ``path`` is first encoded to
- bytes using the Latin-1 encoding; the resulting set of bytes is
- subsequently decoded to text using the UTF-8 encoding; a
- :exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be
- decoded.
+ The ``path`` is first decoded from its WSGI representation to text.
+ Per the :pep:`3333` spec, ``path`` is first encoded to bytes using the
+ Latin-1 encoding; the resulting set of bytes is subsequently decoded to
+ text using the UTF-8 encoding; a :exc:`pyramid.exc.URLDecodeError` is
+ raised if the URL cannot be decoded.
The ``path`` is split on slashes, creating a list of segments. If a
segment name is empty or if it is ``.``, it is ignored. If a segment
@@ -487,31 +463,31 @@ def traversal_path_info(path):
``/foo/bar/baz``
- (u'foo', u'bar', u'baz')
+ ('foo', 'bar', 'baz')
``foo/bar/baz``
- (u'foo', u'bar', u'baz')
+ ('foo', 'bar', 'baz')
``/foo/bar/baz/``
- (u'foo', u'bar', u'baz')
+ ('foo', 'bar', 'baz')
``/foo//bar//baz/``
- (u'foo', u'bar', u'baz')
+ ('foo', 'bar', 'baz')
``/foo/bar/baz/..``
- (u'foo', u'bar')
+ ('foo', 'bar')
``/my%20archives/hello``
- (u'my archives', u'hello')
+ ('my archives', 'hello')
``/archives/La%20Pe%C3%B1a``
- (u'archives', u'<unprintable unicode>')
+ ('archives', '<unprintable unicode>')
.. note::
@@ -549,83 +525,60 @@ def split_path_info(path):
return tuple(clean)
+# see PEP 3333 for why we encode to latin-1 then decode to utf-8
+def decode_path_info(path):
+ return path.encode('latin-1').decode('utf-8')
+
+
+# see PEP 3333 for why we decode the path to latin-1
+def unquote_bytes_to_wsgi(bytestring):
+ return unquote_to_bytes(bytestring).decode('latin-1')
+
+
_segment_cache = {}
-quote_path_segment_doc = """ \
-Return a quoted representation of a 'path segment' (such as
-the string ``__name__`` attribute of a resource) as a string. If the
-``segment`` passed in is a unicode object, it is converted to a
-UTF-8 string, then it is URL-quoted using Python's
-``urllib.quote``. If the ``segment`` passed in is a string, it is
-URL-quoted using Python's :mod:`urllib.quote`. If the segment
-passed in is not a string or unicode object, an error will be
-raised. The return value of ``quote_path_segment`` is always a
-string, never Unicode.
-
-You may pass a string of characters that need not be encoded as
-the ``safe`` argument to this function. This corresponds to the
-``safe`` argument to :mod:`urllib.quote`.
-
-.. note::
-
- The return value for each segment passed to this
- function is cached in a module-scope dictionary for
- speed: the cached version is returned when possible
- rather than recomputing the quoted version. No cache
- emptying is ever done for the lifetime of an
- application, however. If you pass arbitrary
- user-supplied strings to this function (as opposed to
- some bounded set of values from a 'working set' known to
- your application), it may become a memory leak.
-"""
-
-
-if PY2:
- # special-case on Python 2 for speed? unchecked
- def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE):
- """ %s """ % quote_path_segment_doc
- # The bit of this code that deals with ``_segment_cache`` is an
- # optimization: we cache all the computation of URL path segments
- # in this module-scope dictionary with the original string (or
- # unicode value) as the key, so we can look it up later without
- # needing to reencode or re-url-quote it
- try:
- return _segment_cache[(segment, safe)]
- except KeyError:
- if (
- segment.__class__ is text_type
- ): # isinstance slighly slower (~15%)
- result = url_quote(segment.encode('utf-8'), safe)
- else:
- result = url_quote(str(segment), safe)
- # we don't need a lock to mutate _segment_cache, as the below
- # will generate exactly one Python bytecode (STORE_SUBSCR)
- _segment_cache[(segment, safe)] = result
- return result
-
-
-else:
-
- def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE):
- """ %s """ % quote_path_segment_doc
- # The bit of this code that deals with ``_segment_cache`` is an
- # optimization: we cache all the computation of URL path segments
- # in this module-scope dictionary with the original string (or
- # unicode value) as the key, so we can look it up later without
- # needing to reencode or re-url-quote it
- try:
- return _segment_cache[(segment, safe)]
- except KeyError:
- if segment.__class__ not in (text_type, binary_type):
- segment = str(segment)
- result = url_quote(native_(segment, 'utf-8'), safe)
- # we don't need a lock to mutate _segment_cache, as the below
- # will generate exactly one Python bytecode (STORE_SUBSCR)
- _segment_cache[(segment, safe)] = result
- return result
-
-
-slash = text_('/')
+
+def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE):
+ """
+ Return a quoted representation of a 'path segment' (such as
+ the string ``__name__`` attribute of a resource) as a string. If the
+ ``segment`` passed in is a bytes object, it is decoded as a UTF-8 string.
+ The result is then URL-quoted using Python's ``urllib.quote``.
+ If the segment passed in is not bytes nor a string, an error will be
+ raised. The return value of ``quote_path_segment`` is always a string.
+
+ You may pass a string of characters that need not be encoded as
+ the ``safe`` argument to this function. This corresponds to the
+ ``safe`` argument to :mod:`urllib.quote`.
+
+ .. note::
+
+ The return value for each segment passed to this
+ function is cached in a module-scope dictionary for
+ speed: the cached version is returned when possible
+ rather than recomputing the quoted version. No cache
+ emptying is ever done for the lifetime of an
+ application, however. If you pass arbitrary
+ user-supplied strings to this function (as opposed to
+ some bounded set of values from a 'working set' known to
+ your application), it may become a memory leak.
+
+ """
+ # The bit of this code that deals with ``_segment_cache`` is an
+ # optimization: we cache all the computation of URL path segments
+ # in this module-scope dictionary with the original string as the
+ # key, so we can look it up later without needing to reencode
+ # or re-url-quote it
+ try:
+ if segment.__class__ not in (str, bytes):
+ segment = str(segment)
+ return _segment_cache[(segment, safe)]
+ except KeyError:
+ result = url_quote(text_(segment, 'utf-8'), safe)
+ # we don't need a lock to mutate _segment_cache, as the below
+ # will generate exactly one Python bytecode (STORE_SUBSCR)
+ _segment_cache[(segment, safe)] = result
+ return result
@implementer(ITraverser)
@@ -647,12 +600,12 @@ class ResourceTreeTraverser(object):
if matchdict is not None:
- path = matchdict.get('traverse', slash) or slash
+ path = matchdict.get('traverse', '/') or '/'
if is_nonstr_iter(path):
# this is a *traverse stararg (not a {traverse})
# routing has already decoded these elements, so we just
# need to join them
- path = '/' + slash.join(path) or slash
+ path = '/' + '/'.join(path) or '/'
subpath = matchdict.get('subpath', ())
if not is_nonstr_iter(subpath):
@@ -666,10 +619,10 @@ class ResourceTreeTraverser(object):
subpath = ()
try:
# empty if mounted under a path in mod_wsgi, for example
- path = request.path_info or slash
+ path = request.path_info or '/'
except KeyError:
# if environ['PATH_INFO'] is just not there
- path = slash
+ path = '/'
except UnicodeDecodeError as e:
raise URLDecodeError(
e.encoding, e.object, e.start, e.end, e.reason
@@ -691,7 +644,7 @@ class ResourceTreeTraverser(object):
root = self.root
ob = vroot = root
- if vpath == slash: # invariant: vpath must not be empty
+ if vpath == '/': # invariant: vpath must not be empty
# prevent a call to traversal_path if we know it's going
# to return the empty tuple
vpath_tuple = ()
@@ -745,7 +698,7 @@ class ResourceTreeTraverser(object):
return {
'context': ob,
- 'view_name': empty,
+ 'view_name': '',
'subpath': subpath,
'traversed': vpath_tuple,
'virtual_root': vroot,
diff --git a/src/pyramid/tweens.py b/src/pyramid/tweens.py
index 839c53b8f..b5660b44b 100644
--- a/src/pyramid/tweens.py
+++ b/src/pyramid/tweens.py
@@ -1,7 +1,7 @@
import sys
-from pyramid.compat import reraise
from pyramid.httpexceptions import HTTPNotFound
+from pyramid.util import reraise
def _error_handler(request, exc):
diff --git a/src/pyramid/url.py b/src/pyramid/url.py
index 00dd13bfe..22551a349 100644
--- a/src/pyramid/url.py
+++ b/src/pyramid/url.py
@@ -1,13 +1,14 @@
""" Utility functions for dealing with URLs in pyramid """
+from functools import lru_cache
import os
from pyramid.interfaces import IResourceURL, IRoutesMapper, IStaticURLInfo
-from pyramid.compat import bytes_, lru_cache, string_types
from pyramid.encode import url_quote, urlencode
from pyramid.path import caller_package
from pyramid.threadlocal import get_current_registry
+from pyramid.util import bytes_
from pyramid.traversal import (
ResourceURL,
@@ -45,7 +46,7 @@ def parse_url_overrides(request, kw):
qs = ''
if query:
- if isinstance(query, string_types):
+ if isinstance(query, str):
qs = '?' + url_quote(query, QUERY_SAFE)
else:
qs = '?' + urlencode(query, doseq=True)
diff --git a/src/pyramid/urldispatch.py b/src/pyramid/urldispatch.py
index de8a69d2a..73b7be9f3 100644
--- a/src/pyramid/urldispatch.py
+++ b/src/pyramid/urldispatch.py
@@ -3,21 +3,12 @@ from zope.interface import implementer
from pyramid.interfaces import IRoutesMapper, IRoute
-from pyramid.compat import (
- PY2,
- native_,
- text_,
- text_type,
- string_types,
- binary_type,
- is_nonstr_iter,
- decode_path_info,
-)
-
from pyramid.exceptions import URLDecodeError
from pyramid.traversal import quote_path_segment, split_path_info, PATH_SAFE
+from pyramid.util import is_nonstr_iter, text_
+
_marker = object()
@@ -82,10 +73,9 @@ class RoutesMapper(object):
return self.routes[name].generate(kw)
def __call__(self, request):
- environ = request.environ
try:
# empty if mounted under a path in mod_wsgi, for example
- path = decode_path_info(environ['PATH_INFO'] or '/')
+ path = request.path_info or '/'
except KeyError:
path = '/'
except UnicodeDecodeError as e:
@@ -127,7 +117,7 @@ def _compile_route(route):
# using the ASCII decoding. We decode it using ASCII because we don't
# want to accept bytestrings with high-order characters in them here as
# we have no idea what the encoding represents.
- if route.__class__ is not text_type:
+ if route.__class__ is not str:
try:
route = text_(route, 'ascii')
except UnicodeDecodeError:
@@ -174,7 +164,7 @@ def _compile_route(route):
name, reg = name.split(':', 1)
else:
reg = '[^/]+'
- gen.append('%%(%s)s' % native_(name)) # native
+ gen.append('%%(%s)s' % name) # native
name = '(?P<%s>%s)' % (name, reg) # unicode
rpat.append(name)
s = pat.pop() # unicode
@@ -189,34 +179,22 @@ def _compile_route(route):
if remainder:
rpat.append('(?P<%s>.*?)' % remainder) # unicode
- gen.append('%%(%s)s' % native_(remainder)) # native
+ gen.append('%%(%s)s' % remainder) # native
pattern = ''.join(rpat) + '$' # unicode
match = re.compile(pattern).match
def matcher(path):
- # This function really wants to consume Unicode patterns natively,
- # but if someone passes us a bytestring, we allow it by converting it
- # to Unicode using the ASCII decoding. We decode it using ASCII
- # because we don't want to accept bytestrings with high-order
- # characters in them here as we have no idea what the encoding
- # represents.
- if path.__class__ is not text_type:
- path = text_(path, 'ascii')
m = match(path)
if m is None:
return None
d = {}
for k, v in m.groupdict().items():
- # k and v will be Unicode 2.6.4 and lower doesnt accept unicode
- # kwargs as **kw, so we explicitly cast the keys to native
- # strings in case someone wants to pass the result as **kw
- nk = native_(k, 'ascii')
if k == remainder:
- d[nk] = split_path_info(v)
+ d[k] = split_path_info(v)
else:
- d[nk] = v
+ d[k] = v
return d
gen = ''.join(gen)
@@ -227,27 +205,21 @@ def _compile_route(route):
def generator(dict):
newdict = {}
for k, v in dict.items():
- if PY2:
- if v.__class__ is text_type:
- # url_quote below needs bytes, not unicode on Py2
- v = v.encode('utf-8')
- else:
- if v.__class__ is binary_type:
- # url_quote below needs a native string, not bytes on Py3
- v = v.decode('utf-8')
+ if v.__class__ is bytes:
+ # url_quote below needs a native string
+ v = v.decode('utf-8')
if k == remainder:
# a stararg argument
if is_nonstr_iter(v):
v = '/'.join([q(x) for x in v]) # native
else:
- if v.__class__ not in string_types:
+ if v.__class__ is not str:
v = str(v)
v = q(v)
else:
- if v.__class__ not in string_types:
+ if v.__class__ is not str:
v = str(v)
- # v may be bytes (py2) or native string (py3)
v = q(v)
# at this point, the value will be a native string
diff --git a/src/pyramid/util.py b/src/pyramid/util.py
index bebf9e7d3..e552b37de 100644
--- a/src/pyramid/util.py
+++ b/src/pyramid/util.py
@@ -1,32 +1,24 @@
from contextlib import contextmanager
import functools
-
-try:
- # py2.7.7+ and py3.3+ have native comparison support
- from hmac import compare_digest
-except ImportError: # pragma: no cover
- compare_digest = None
+from hmac import compare_digest
import inspect
+import platform
import weakref
-from pyramid.exceptions import ConfigurationError, CyclicDependencyError
-
-from pyramid.compat import (
- getargspec,
- im_func,
- is_nonstr_iter,
- integer_types,
- string_types,
- bytes_,
- text_,
- PY2,
- native_,
-)
-
from pyramid.path import DottedNameResolver as _DottedNameResolver
_marker = object()
+WIN = platform.system() == 'Windows'
+
+try: # pragma: no cover
+ import __pypy__
+
+ PYPY = True
+except BaseException: # pragma: no cover
+ __pypy__ = None
+ PYPY = False
+
class DottedNameResolver(_DottedNameResolver):
def __init__(
@@ -35,8 +27,40 @@ class DottedNameResolver(_DottedNameResolver):
_DottedNameResolver.__init__(self, package)
+def text_(s, encoding='latin-1', errors='strict'):
+ """ If ``s`` is an instance of ``bytes``, return
+ ``s.decode(encoding, errors)``, otherwise return ``s``"""
+ if isinstance(s, bytes):
+ return s.decode(encoding, errors)
+ return s
+
+
+def bytes_(s, encoding='latin-1', errors='strict'):
+ """ If ``s`` is an instance of ``str``, return
+ ``s.encode(encoding, errors)``, otherwise return ``s``"""
+ if isinstance(s, str):
+ return s.encode(encoding, errors)
+ return s
+
+
+def ascii_(s):
+ """
+ If ``s`` is an instance of ``str``, return
+ ``s.encode('ascii')``, otherwise return ``str(s, 'ascii', 'strict')``
+ """
+ if isinstance(s, str):
+ s = s.encode('ascii')
+ return str(s, 'ascii', 'strict')
+
+
+def is_nonstr_iter(v):
+ if isinstance(v, str):
+ return False
+ return hasattr(v, '__iter__')
+
+
def is_string_or_iterable(v):
- if isinstance(v, string_types):
+ if isinstance(v, str):
return True
if hasattr(v, '__iter__'):
return True
@@ -277,7 +301,7 @@ class WeakOrderedSet(object):
return self._items[oid]()
-def strings_differ(string1, string2, compare_digest=compare_digest):
+def strings_differ(string1, string2):
"""Check whether two strings differ while avoiding timing attacks.
This function returns True if the given strings differ and False
@@ -301,11 +325,7 @@ def strings_differ(string1, string2, compare_digest=compare_digest):
left = string2
right = string2
- if compare_digest is not None:
- invalid_bits += not compare_digest(left, right)
- else:
- for a, b in zip(left, right):
- invalid_bits += a != b
+ invalid_bits += not compare_digest(left, right)
return invalid_bits != 0
@@ -314,14 +334,14 @@ def object_description(object):
usually involving a Python dotted name. For example:
>>> object_description(None)
- u'None'
+ 'None'
>>> from xml.dom import minidom
>>> object_description(minidom)
- u'module xml.dom.minidom'
+ 'module xml.dom.minidom'
>>> object_description(minidom.Attr)
- u'class xml.dom.minidom.Attr'
+ 'class xml.dom.minidom.Attr'
>>> object_description(minidom.Attr.appendChild)
- u'method appendChild of class xml.dom.minidom.Attr'
+ 'method appendChild of class xml.dom.minidom.Attr'
If this method cannot identify the type of the object, a generic
description ala ``object <object.__name__>`` will be returned.
@@ -330,17 +350,14 @@ def object_description(object):
is a boolean, an integer, a list, a tuple, a set, or ``None``, a
(possibly shortened) string representation is returned.
"""
- if isinstance(object, string_types):
- return text_(object)
- if isinstance(object, integer_types):
- return text_(str(object))
+ if isinstance(object, str):
+ return object
+ if isinstance(object, int):
+ return str(object)
if isinstance(object, (bool, float, type(None))):
- return text_(str(object))
+ return str(object)
if isinstance(object, set):
- if PY2:
- return shortrepr(object, ')')
- else:
- return shortrepr(object, '}')
+ return shortrepr(object, '}')
if isinstance(object, tuple):
return shortrepr(object, ')')
if isinstance(object, list):
@@ -349,26 +366,25 @@ def object_description(object):
return shortrepr(object, '}')
module = inspect.getmodule(object)
if module is None:
- return text_('object %s' % str(object))
+ return 'object %s' % str(object)
modulename = module.__name__
if inspect.ismodule(object):
- return text_('module %s' % modulename)
+ return 'module %s' % modulename
if inspect.ismethod(object):
oself = getattr(object, '__self__', None)
- if oself is None: # pragma: no cover
- oself = getattr(object, 'im_self', None)
- return text_(
- 'method %s of class %s.%s'
- % (object.__name__, modulename, oself.__class__.__name__)
+ return 'method %s of class %s.%s' % (
+ object.__name__,
+ modulename,
+ oself.__class__.__name__,
)
if inspect.isclass(object):
dottedname = '%s.%s' % (modulename, object.__name__)
- return text_('class %s' % dottedname)
+ return 'class %s' % dottedname
if inspect.isfunction(object):
dottedname = '%s.%s' % (modulename, object.__name__)
- return text_('function %s' % dottedname)
- return text_('object %s' % str(object))
+ return 'function %s' % dottedname
+ return 'object %s' % str(object)
def shortrepr(object, closer):
@@ -499,11 +515,17 @@ class TopologicalSorter(object):
has_after.add(b)
if not self.req_before.issubset(has_before):
+ # avoid circular dependency
+ from pyramid.exceptions import ConfigurationError
+
raise ConfigurationError(
'Unsatisfied before dependencies: %s'
% (', '.join(sorted(self.req_before - has_before)))
)
if not self.req_after.issubset(has_after):
+ # avoid circular dependency
+ from pyramid.exceptions import ConfigurationError
+
raise ConfigurationError(
'Unsatisfied after dependencies: %s'
% (', '.join(sorted(self.req_after - has_after)))
@@ -524,6 +546,9 @@ class TopologicalSorter(object):
del graph[root]
if graph:
+ # avoid circular dependency
+ from pyramid.exceptions import CyclicDependencyError
+
# loop in input
cycledeps = {}
for k, v in graph.items():
@@ -545,8 +570,11 @@ def get_callable_name(name):
if it is not.
"""
try:
- return native_(name, 'ascii')
+ return ascii_(name)
except (UnicodeEncodeError, UnicodeDecodeError):
+ # avoid circular dependency
+ from pyramid.exceptions import ConfigurationError
+
msg = (
'`name="%s"` is invalid. `name` must be ascii because it is '
'used on __name__ of the method'
@@ -615,10 +643,7 @@ def takes_one_arg(callee, attr=None, argname=None):
if inspect.isroutine(callee):
fn = callee
elif inspect.isclass(callee):
- try:
- fn = callee.__init__
- except AttributeError:
- return False
+ fn = callee.__init__
ismethod = hasattr(fn, '__call__')
else:
try:
@@ -626,15 +651,11 @@ def takes_one_arg(callee, attr=None, argname=None):
except AttributeError:
return False
- try:
- argspec = getargspec(fn)
- except TypeError:
- return False
-
+ argspec = inspect.getfullargspec(fn)
args = argspec[0]
- if hasattr(fn, im_func) or ismethod:
- # it's an instance method (or unbound method on py2)
+ if hasattr(fn, '__func__') or ismethod:
+ # it's an instance method
if not args:
return False
args = args[1:]
@@ -660,7 +681,40 @@ def takes_one_arg(callee, attr=None, argname=None):
class SimpleSerializer(object):
def loads(self, bstruct):
- return native_(bstruct)
+ return text_(bstruct)
def dumps(self, appstruct):
return bytes_(appstruct)
+
+
+def is_bound_method(ob):
+ return inspect.ismethod(ob) and getattr(ob, '__self__', None) is not None
+
+
+def is_unbound_method(fn):
+ """
+ This consistently verifies that the callable is bound to a
+ class.
+ """
+ is_bound = is_bound_method(fn)
+
+ if not is_bound and inspect.isroutine(fn):
+ spec = inspect.getfullargspec(fn)
+ has_self = len(spec.args) > 0 and spec.args[0] == 'self'
+
+ if inspect.isfunction(fn) and has_self:
+ return True
+
+ return False
+
+
+def reraise(tp, value, tb=None):
+ try:
+ if value is None:
+ value = tp()
+ if value.__traceback__ is not tb:
+ raise value.with_traceback(tb)
+ raise value
+ finally:
+ value = None
+ tb = None
diff --git a/src/pyramid/view.py b/src/pyramid/view.py
index 9f58e72ae..944ad93ea 100644
--- a/src/pyramid/view.py
+++ b/src/pyramid/view.py
@@ -15,9 +15,6 @@ from pyramid.interfaces import (
IExceptionViewClassifier,
)
-from pyramid.compat import decode_path_info
-from pyramid.compat import reraise as reraise_
-
from pyramid.exceptions import ConfigurationError, PredicateMismatch
from pyramid.httpexceptions import (
@@ -29,6 +26,7 @@ from pyramid.httpexceptions import (
from pyramid.threadlocal import get_current_registry, manager
from pyramid.util import hide_attrs
+from pyramid.util import reraise as reraise_
_marker = object()
@@ -305,7 +303,7 @@ class AppendSlashNotFoundViewFactory(object):
self.redirect_class = redirect_class
def __call__(self, context, request):
- path = decode_path_info(request.environ['PATH_INFO'] or '/')
+ path = request.path_info
registry = request.registry
mapper = registry.queryUtility(IRoutesMapper)
if mapper is not None and not path.endswith('/'):
diff --git a/src/pyramid/viewderivers.py b/src/pyramid/viewderivers.py
index fbe0c252c..181cc9e5c 100644
--- a/src/pyramid/viewderivers.py
+++ b/src/pyramid/viewderivers.py
@@ -17,11 +17,14 @@ from pyramid.interfaces import (
IViewMapperFactory,
)
-from pyramid.compat import is_bound_method, is_unbound_method
-
from pyramid.exceptions import ConfigurationError
from pyramid.httpexceptions import HTTPForbidden
-from pyramid.util import object_description, takes_one_arg
+from pyramid.util import (
+ object_description,
+ takes_one_arg,
+ is_bound_method,
+ is_unbound_method,
+)
from pyramid.view import render_view_to_response
from pyramid import renderers
diff --git a/tests/pkgs/forbiddenapp/__init__.py b/tests/pkgs/forbiddenapp/__init__.py
index 9ebf62a9d..31ea4dd52 100644
--- a/tests/pkgs/forbiddenapp/__init__.py
+++ b/tests/pkgs/forbiddenapp/__init__.py
@@ -1,6 +1,6 @@
from webob import Response
from pyramid.httpexceptions import HTTPForbidden
-from pyramid.compat import bytes_
+from pyramid.util import bytes_
def x_view(request): # pragma: no cover
diff --git a/tests/pkgs/permbugapp/__init__.py b/tests/pkgs/permbugapp/__init__.py
index aedd405f8..72b5d9c17 100644
--- a/tests/pkgs/permbugapp/__init__.py
+++ b/tests/pkgs/permbugapp/__init__.py
@@ -1,4 +1,4 @@
-from pyramid.compat import escape
+from html import escape
from pyramid.security import view_execution_permitted
from pyramid.response import Response
diff --git a/tests/test_authentication.py b/tests/test_authentication.py
index fc3e60587..8671eba05 100644
--- a/tests/test_authentication.py
+++ b/tests/test_authentication.py
@@ -1,7 +1,8 @@
+from http.cookies import SimpleCookie
import unittest
import warnings
from pyramid import testing
-from pyramid.compat import text_, bytes_
+from pyramid.util import text_, bytes_
class TestCallbackAuthenticationPolicyDebugging(unittest.TestCase):
@@ -706,8 +707,6 @@ class TestAuthTktCookieHelper(unittest.TestCase):
return cookie
def _parseCookie(self, cookie):
- from pyramid.compat import SimpleCookie
-
cookies = SimpleCookie()
cookies.load(cookie)
return cookies.get('auth_tkt')
@@ -1272,18 +1271,6 @@ class TestAuthTktCookieHelper(unittest.TestCase):
self.assertEqual(val['userid'], '1')
self.assertEqual(val['user_data'], 'userid_type:int')
- def test_remember_long_userid(self):
- from pyramid.compat import long
-
- helper = self._makeOne('secret')
- request = self._makeRequest()
- result = helper.remember(request, long(1))
- values = self._parseHeaders(result)
- self.assertEqual(len(result), 3)
- val = self._cookieValue(values[0])
- self.assertEqual(val['userid'], '1')
- self.assertEqual(val['user_data'], 'userid_type:int')
-
def test_remember_unicode_userid(self):
import base64
diff --git a/tests/test_compat.py b/tests/test_compat.py
deleted file mode 100644
index 4a14caedf..000000000
--- a/tests/test_compat.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import unittest
-from pyramid.compat import is_unbound_method
-
-
-class TestUnboundMethods(unittest.TestCase):
- def test_old_style_bound(self):
- self.assertFalse(is_unbound_method(OldStyle().run))
-
- def test_new_style_bound(self):
- self.assertFalse(is_unbound_method(NewStyle().run))
-
- def test_old_style_unbound(self):
- self.assertTrue(is_unbound_method(OldStyle.run))
-
- def test_new_style_unbound(self):
- self.assertTrue(is_unbound_method(NewStyle.run))
-
- def test_normal_func_unbound(self):
- def func(): # pragma: no cover
- return 'OK'
-
- self.assertFalse(is_unbound_method(func))
-
-
-class OldStyle:
- def run(self): # pragma: no cover
- return 'OK'
-
-
-class NewStyle(object):
- def run(self): # pragma: no cover
- return 'OK'
diff --git a/tests/test_config/test_adapters.py b/tests/test_config/test_adapters.py
index d871e8825..60a4f3090 100644
--- a/tests/test_config/test_adapters.py
+++ b/tests/test_config/test_adapters.py
@@ -1,6 +1,5 @@
import unittest
-from pyramid.compat import PY2
from . import IDummy
@@ -270,10 +269,7 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase):
from pyramid.interfaces import IResponse
config = self._makeOne(autocommit=True)
- if PY2:
- str_name = '__builtin__.str'
- else:
- str_name = 'builtins.str'
+ str_name = 'builtins.str'
config.add_response_adapter('pyramid.response.Response', str_name)
result = config.registry.queryAdapter('foo', IResponse)
self.assertTrue(result.body, b'foo')
diff --git a/tests/test_config/test_factories.py b/tests/test_config/test_factories.py
index c03d3f68b..bbc38b6cd 100644
--- a/tests/test_config/test_factories.py
+++ b/tests/test_config/test_factories.py
@@ -160,8 +160,7 @@ class TestFactoriesMixin(unittest.TestCase):
config = self._makeOne(autocommit=True)
self.assertRaises(AttributeError, config.add_request_method)
- def test_add_request_method_with_text_type_name(self):
- from pyramid.compat import text_, PY2
+ def test_add_request_method_with_text_name(self):
from pyramid.exceptions import ConfigurationError
config = self._makeOne(autocommit=True)
@@ -170,11 +169,7 @@ class TestFactoriesMixin(unittest.TestCase):
pass
def get_bad_name():
- if PY2:
- name = text_(b'La Pe\xc3\xb1a', 'utf-8')
- else:
- name = b'La Pe\xc3\xb1a'
-
+ name = b'La Pe\xc3\xb1a'
config.add_request_method(boomshaka, name=name)
self.assertRaises(ConfigurationError, get_bad_name)
diff --git a/tests/test_config/test_init.py b/tests/test_config/test_init.py
index 811672fb3..ce2b042ec 100644
--- a/tests/test_config/test_init.py
+++ b/tests/test_config/test_init.py
@@ -1,9 +1,6 @@
import os
import unittest
-from pyramid.compat import im_func
-from pyramid.testing import skip_on
-
from . import dummy_tween_factory
from . import dummy_include
from . import dummy_extend
@@ -1157,7 +1154,6 @@ test_config.dummy_include2"""
"@view_config(name='two', renderer='string')" in which
)
- @skip_on('py3')
def test_hook_zca(self):
from zope.component import getSiteManager
@@ -1173,7 +1169,6 @@ test_config.dummy_include2"""
finally:
getSiteManager.reset()
- @skip_on('py3')
def test_unhook_zca(self):
from zope.component import getSiteManager
@@ -1208,7 +1203,7 @@ test_config.dummy_include2"""
directives = {'foo': (foo, True)}
config.registry._directives = directives
foo_meth = config.foo
- self.assertTrue(getattr(foo_meth, im_func).__docobj__ is foo)
+ self.assertTrue(getattr(foo_meth, '__func__').__docobj__ is foo)
def test___getattr__matches_no_action_wrap(self):
config = self._makeOne()
@@ -1219,7 +1214,7 @@ test_config.dummy_include2"""
directives = {'foo': (foo, False)}
config.registry._directives = directives
foo_meth = config.foo
- self.assertTrue(getattr(foo_meth, im_func) is foo)
+ self.assertTrue(getattr(foo_meth, '__func__') is foo)
class TestConfigurator_add_directive(unittest.TestCase):
diff --git a/tests/test_config/test_predicates.py b/tests/test_config/test_predicates.py
index 079652b39..c27b41639 100644
--- a/tests/test_config/test_predicates.py
+++ b/tests/test_config/test_predicates.py
@@ -1,6 +1,6 @@
import unittest
-from pyramid.compat import text_
+from pyramid.util import text_
class TestPredicateList(unittest.TestCase):
diff --git a/tests/test_config/test_routes.py b/tests/test_config/test_routes.py
index e6540c343..4ff67cf66 100644
--- a/tests/test_config/test_routes.py
+++ b/tests/test_config/test_routes.py
@@ -2,7 +2,7 @@ import unittest
from . import dummyfactory
from . import DummyContext
-from pyramid.compat import text_
+from pyramid.util import text_
class RoutesConfiguratorMixinTests(unittest.TestCase):
diff --git a/tests/test_config/test_testing.py b/tests/test_config/test_testing.py
index ede31e1b6..0fb73d268 100644
--- a/tests/test_config/test_testing.py
+++ b/tests/test_config/test_testing.py
@@ -1,8 +1,8 @@
import unittest
from zope.interface import implementer
-from pyramid.compat import text_
from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin
+from pyramid.util import text_
from . import IDummy
@@ -69,23 +69,27 @@ class TestingConfiguratorMixinTests(unittest.TestCase):
config = self._makeOne(autocommit=True)
config.testing_resources(resources)
adapter = config.registry.getAdapter(None, ITraverser)
- result = adapter(DummyRequest({'PATH_INFO': '/ob1'}))
+ request = DummyRequest()
+ request.path_info = '/ob1'
+ result = adapter(request)
self.assertEqual(result['context'], ob1)
self.assertEqual(result['view_name'], '')
self.assertEqual(result['subpath'], ())
self.assertEqual(result['traversed'], (text_('ob1'),))
self.assertEqual(result['virtual_root'], ob1)
self.assertEqual(result['virtual_root_path'], ())
- result = adapter(DummyRequest({'PATH_INFO': '/ob2'}))
+ request = DummyRequest()
+ request.path_info = '/ob2'
+ result = adapter(request)
self.assertEqual(result['context'], ob2)
self.assertEqual(result['view_name'], '')
self.assertEqual(result['subpath'], ())
self.assertEqual(result['traversed'], (text_('ob2'),))
self.assertEqual(result['virtual_root'], ob2)
self.assertEqual(result['virtual_root_path'], ())
- self.assertRaises(
- KeyError, adapter, DummyRequest({'PATH_INFO': '/ob3'})
- )
+ request = DummyRequest()
+ request.path_info = '/ob3'
+ self.assertRaises(KeyError, adapter, request)
try:
config.begin()
self.assertEqual(find_resource(None, '/ob1'), ob1)
diff --git a/tests/test_config/test_views.py b/tests/test_config/test_views.py
index b72b9b36a..685b81a0f 100644
--- a/tests/test_config/test_views.py
+++ b/tests/test_config/test_views.py
@@ -3,11 +3,11 @@ import unittest
from zope.interface import implementer
from pyramid import testing
-from pyramid.compat import im_func, text_
from pyramid.exceptions import ConfigurationError
from pyramid.exceptions import ConfigurationExecutionError
from pyramid.exceptions import ConfigurationConflictError
from pyramid.interfaces import IResponse, IRequest, IMultiView
+from pyramid.util import text_
from . import IDummy
from . import dummy_view
@@ -1357,6 +1357,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
request_method='POST',
)
request = self._makeRequest(config)
+ request.path_info = '/'
request.method = 'POST'
request.params = {}
router = Router(config.registry)
@@ -1412,6 +1413,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
request_method='POST',
)
request = self._makeRequest(config)
+ request.path_info = '/'
request.method = 'POST'
request.params = {}
router = Router(config.registry)
@@ -2722,7 +2724,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
view, renderer=null_renderer, append_slash=True
)
request = self._makeRequest(config)
- request.environ['PATH_INFO'] = '/foo'
+ request.path_info = '/foo'
request.query_string = 'a=1&b=2'
request.path = '/scriptname/foo'
view = self._getViewCallable(
@@ -2751,7 +2753,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
view, renderer=null_renderer, append_slash=HTTPMovedPermanently
)
request = self._makeRequest(config)
- request.environ['PATH_INFO'] = '/foo'
+ request.path_info = '/foo'
request.query_string = 'a=1&b=2'
request.path = '/scriptname/foo'
view = self._getViewCallable(
@@ -2795,15 +2797,6 @@ class TestViewsConfigurationMixin(unittest.TestCase):
request = self._makeRequest(config)
self.assertRaises(PredicateMismatch, wrapper, context, request)
- # 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: no cover
- self.assertEqual(response.text, value)
- else: # pragma: no cover
- self.assertEqual(response.body, value)
-
def test_add_notfound_view_with_renderer(self):
from zope.interface import implementedBy
from pyramid.interfaces import IRequest
@@ -2820,7 +2813,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
request_iface=IRequest,
)
result = view(None, request)
- self._assertBody(result, '{}')
+ self.assertEqual(result.text, '{}')
def test_add_forbidden_view_with_renderer(self):
from zope.interface import implementedBy
@@ -2838,7 +2831,7 @@ class TestViewsConfigurationMixin(unittest.TestCase):
request_iface=IRequest,
)
result = view(None, request)
- self._assertBody(result, '{}')
+ self.assertEqual(result.text, '{}')
def test_set_view_mapper(self):
from pyramid.interfaces import IViewMapperFactory
@@ -3732,16 +3725,16 @@ class Test_preserve_view_attrs(unittest.TestCase):
self.assertTrue(view1.__module__ is view2.__module__)
self.assertTrue(view1.__name__ is view2.__name__)
self.assertTrue(
- getattr(view1.__call_permissive__, im_func)
- is getattr(view2.__call_permissive__, im_func)
+ getattr(view1.__call_permissive__, '__func__')
+ is getattr(view2.__call_permissive__, '__func__')
)
self.assertTrue(
- getattr(view1.__permitted__, im_func)
- is getattr(view2.__permitted__, im_func)
+ getattr(view1.__permitted__, '__func__')
+ is getattr(view2.__permitted__, '__func__')
)
self.assertTrue(
- getattr(view1.__predicated__, im_func)
- is getattr(view2.__predicated__, im_func)
+ getattr(view1.__predicated__, '__func__')
+ is getattr(view2.__predicated__, '__func__')
)
diff --git a/tests/test_encode.py b/tests/test_encode.py
index f70050cac..4df08d509 100644
--- a/tests/test_encode.py
+++ b/tests/test_encode.py
@@ -1,5 +1,5 @@
import unittest
-from pyramid.compat import text_, native_
+from pyramid.util import text_
class UrlEncodeTests(unittest.TestCase):
@@ -74,7 +74,7 @@ class URLQuoteTests(unittest.TestCase):
self.assertEqual(result, 'La%2FPe%C3%B1a')
def test_it_native(self):
- la = native_(b'La/Pe\xc3\xb1a', 'utf-8')
+ la = text_(b'La/Pe\xc3\xb1a', 'utf-8')
result = self._callFUT(la)
self.assertEqual(result, 'La%2FPe%C3%B1a')
diff --git a/tests/test_httpexceptions.py b/tests/test_httpexceptions.py
index 4c13e096d..5decfc39c 100644
--- a/tests/test_httpexceptions.py
+++ b/tests/test_httpexceptions.py
@@ -1,6 +1,6 @@
import unittest
-from pyramid.compat import bytes_, string_types, text_
+from pyramid.util import bytes_, text_
class Test_exception_response(unittest.TestCase):
@@ -67,6 +67,12 @@ class Test__no_escape(unittest.TestCase):
def test_not_basestring(self):
self.assertEqual(self._callFUT(42), '42')
+ def test_bytes(self):
+ self.assertEqual(
+ self._callFUT(b'/La Pe\xc3\xb1a/{x}'),
+ b'/La Pe\xc3\xb1a/{x}'.decode('utf-8'),
+ )
+
def test_unicode(self):
class DummyUnicodeObject(object):
def __unicode__(self):
@@ -406,7 +412,7 @@ class TestHTTPException(unittest.TestCase):
def test_allow_detail_non_str(self):
exc = self._makeOne(detail={'error': 'This is a test'})
- self.assertIsInstance(exc.__str__(), string_types)
+ self.assertIsInstance(exc.__str__(), str)
class TestRenderAllExceptionsWithoutArguments(unittest.TestCase):
diff --git a/tests/test_integration.py b/tests/test_integration.py
index d57a7cf6e..e6dccbb5b 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -4,14 +4,15 @@ import gc
import locale
import os
import unittest
+from urllib.parse import quote
from webtest import TestApp
from zope.interface import Interface
-from pyramid.wsgi import wsgiapp
-from pyramid.view import view_config
from pyramid.static import static_view
from pyramid.testing import skip_on
-from pyramid.compat import text_, url_quote
+from pyramid.util import text_
+from pyramid.view import view_config
+from pyramid.wsgi import wsgiapp
from .pkgs.exceptionviewapp.models import AnException, NotAnException
@@ -108,7 +109,7 @@ class StaticAppBase(IntegrationBase):
os.makedirs(pathdir)
with open(path, 'wb') as fp:
fp.write(body)
- url = url_quote('/static/héhé/index.html')
+ url = quote('/static/héhé/index.html')
res = self.testapp.get(url, status=200)
self.assertEqual(res.body, body)
finally:
@@ -123,7 +124,7 @@ class StaticAppBase(IntegrationBase):
with open(path, 'wb') as fp:
fp.write(body)
try:
- url = url_quote('/static/héhé.html')
+ url = quote('/static/héhé.html')
res = self.testapp.get(url, status=200)
self.assertEqual(res.body, body)
finally:
@@ -725,8 +726,8 @@ class UnicodeInURLTest(unittest.TestCase):
res = testapp.get(request_path, status=404)
# Pyramid default 404 handler outputs:
- # u'404 Not Found\n\nThe resource could not be found.\n\n\n'
- # u'/avalia\xe7\xe3o_participante\n\n'
+ # '404 Not Found\n\nThe resource could not be found.\n\n\n'
+ # '/avalia\xe7\xe3o_participante\n\n'
self.assertTrue(request_path_unicode in res.text)
def test_unicode_in_url_200(self):
diff --git a/tests/test_path.py b/tests/test_path.py
index 626bb1139..da7cd64e1 100644
--- a/tests/test_path.py
+++ b/tests/test_path.py
@@ -1,6 +1,5 @@
import unittest
import os
-from pyramid.compat import PY2
here = os.path.abspath(os.path.dirname(__file__))
@@ -429,10 +428,7 @@ class TestDottedNameResolver(unittest.TestCase):
def test_zope_dottedname_style_resolve_builtin(self):
typ = self._makeOne()
- if PY2:
- result = typ._zope_dottedname_style('__builtin__.str', None)
- else:
- result = typ._zope_dottedname_style('builtins.str', None)
+ result = typ._zope_dottedname_style('builtins.str', None)
self.assertEqual(result, str)
def test_zope_dottedname_style_resolve_absolute(self):
diff --git a/tests/test_predicates.py b/tests/test_predicates.py
index c072b4229..a99651a8f 100644
--- a/tests/test_predicates.py
+++ b/tests/test_predicates.py
@@ -2,7 +2,7 @@ import unittest
from pyramid import testing
-from pyramid.compat import text_
+from pyramid.util import text_
class TestXHRPredicate(unittest.TestCase):
diff --git a/tests/test_renderers.py b/tests/test_renderers.py
index 0eacfa996..db8b3b4f2 100644
--- a/tests/test_renderers.py
+++ b/tests/test_renderers.py
@@ -1,8 +1,8 @@
import unittest
-from pyramid.testing import cleanUp
from pyramid import testing
-from pyramid.compat import text_
+from pyramid.testing import cleanUp
+from pyramid.util import text_
class TestJSON(unittest.TestCase):
@@ -774,7 +774,7 @@ class DummyResponse:
body = b''
# compat for renderer that will set unicode on py3
- def _set_text(self, val): # pragma: no cover
+ def _set_text(self, val):
self.body = val.encode('utf8')
text = property(fset=_set_text)
diff --git a/tests/test_request.py b/tests/test_request.py
index dcac501aa..484d86e01 100644
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -1,8 +1,8 @@
import unittest
from pyramid import testing
-from pyramid.compat import PY2, text_, bytes_, native_
from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin
+from pyramid.util import text_, bytes_
class TestRequest(unittest.TestCase):
@@ -352,10 +352,7 @@ class TestRequest(unittest.TestCase):
inp = text_(
b'/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', 'utf-8'
)
- if PY2:
- body = json.dumps({'a': inp}).decode('utf-8').encode('utf-16')
- else:
- body = bytes(json.dumps({'a': inp}), 'utf-16')
+ body = bytes(json.dumps({'a': inp}), 'utf-16')
request.body = body
request.content_type = 'application/json; charset=utf-16'
self.assertEqual(request.json_body, {'a': inp})
@@ -481,7 +478,7 @@ class Test_call_app_with_subpath_as_path_info(unittest.TestCase):
self.assertEqual(request.environ['PATH_INFO'], '/hello/')
def test_subpath_path_info_and_script_name_have_utf8(self):
- encoded = native_(text_(b'La Pe\xc3\xb1a'))
+ encoded = text_(b'La Pe\xc3\xb1a')
decoded = text_(bytes_(encoded), 'utf-8')
request = DummyRequest(
{'PATH_INFO': '/' + encoded, 'SCRIPT_NAME': '/' + encoded}
diff --git a/tests/test_response.py b/tests/test_response.py
index 5231e47f0..18d4335ad 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -73,9 +73,9 @@ class TestFileResponse(unittest.TestCase):
# function returns Unicode for the content_type, unlike any previous
# version of Python. See https://github.com/Pylons/pyramid/issues/1360
# for more information.
- from pyramid.compat import text_
import mimetypes as old_mimetypes
from pyramid import response
+ from pyramid.util import text_
class FakeMimetypesModule(object):
def guess_type(self, *arg, **kw):
diff --git a/tests/test_scripts/dummy.py b/tests/test_scripts/dummy.py
index 8e340f645..bb3475d39 100644
--- a/tests/test_scripts/dummy.py
+++ b/tests/test_scripts/dummy.py
@@ -81,8 +81,11 @@ class DummyRoute(object):
class DummyRequest:
application_url = 'http://example.com:5432'
script_name = ''
+ path_info = '/'
- def __init__(self, environ):
+ def __init__(self, environ=None):
+ if environ is None:
+ environ = {}
self.environ = environ
self.matchdict = {}
diff --git a/tests/test_scripts/test_prequest.py b/tests/test_scripts/test_prequest.py
index 1521172bc..aadde719a 100644
--- a/tests/test_scripts/test_prequest.py
+++ b/tests/test_scripts/test_prequest.py
@@ -1,3 +1,4 @@
+from io import StringIO
import unittest
from . import dummy
@@ -134,13 +135,11 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._out, ['abc'])
def test_command_method_post(self):
- from pyramid.compat import NativeIO
-
command = self._makeOne(
['', '--method=POST', 'development.ini', '/'],
[('Content-Type', 'text/html; charset=UTF-8')],
)
- stdin = NativeIO()
+ stdin = StringIO()
command.stdin = stdin
command.run()
self.assertEqual(self._environ['REQUEST_METHOD'], 'POST')
@@ -150,13 +149,11 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._out, ['abc'])
def test_command_method_put(self):
- from pyramid.compat import NativeIO
-
command = self._makeOne(
['', '--method=PUT', 'development.ini', '/'],
[('Content-Type', 'text/html; charset=UTF-8')],
)
- stdin = NativeIO()
+ stdin = StringIO()
command.stdin = stdin
command.run()
self.assertEqual(self._environ['REQUEST_METHOD'], 'PUT')
@@ -166,13 +163,11 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._out, ['abc'])
def test_command_method_patch(self):
- from pyramid.compat import NativeIO
-
command = self._makeOne(
['', '--method=PATCH', 'development.ini', '/'],
[('Content-Type', 'text/html; charset=UTF-8')],
)
- stdin = NativeIO()
+ stdin = StringIO()
command.stdin = stdin
command.run()
self.assertEqual(self._environ['REQUEST_METHOD'], 'PATCH')
@@ -182,13 +177,11 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._out, ['abc'])
def test_command_method_propfind(self):
- from pyramid.compat import NativeIO
-
command = self._makeOne(
['', '--method=PROPFIND', 'development.ini', '/'],
[('Content-Type', 'text/html; charset=UTF-8')],
)
- stdin = NativeIO()
+ stdin = StringIO()
command.stdin = stdin
command.run()
self.assertEqual(self._environ['REQUEST_METHOD'], 'PROPFIND')
@@ -196,13 +189,11 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._out, ['abc'])
def test_command_method_options(self):
- from pyramid.compat import NativeIO
-
command = self._makeOne(
['', '--method=OPTIONS', 'development.ini', '/'],
[('Content-Type', 'text/html; charset=UTF-8')],
)
- stdin = NativeIO()
+ stdin = StringIO()
command.stdin = stdin
command.run()
self.assertEqual(self._environ['REQUEST_METHOD'], 'OPTIONS')
diff --git a/tests/test_scripts/test_pserve.py b/tests/test_scripts/test_pserve.py
index b85f4ddb7..a573f2e5b 100644
--- a/tests/test_scripts/test_pserve.py
+++ b/tests/test_scripts/test_pserve.py
@@ -1,3 +1,4 @@
+from io import StringIO
import os
import unittest
from . import dummy
@@ -8,9 +9,7 @@ here = os.path.abspath(os.path.dirname(__file__))
class TestPServeCommand(unittest.TestCase):
def setUp(self):
- from pyramid.compat import NativeIO
-
- self.out_ = NativeIO()
+ self.out_ = StringIO()
def out(self, msg):
self.out_.write(msg)
diff --git a/tests/test_scripts/test_pviews.py b/tests/test_scripts/test_pviews.py
index 0b26a9cf3..c8d29113f 100644
--- a/tests/test_scripts/test_pviews.py
+++ b/tests/test_scripts/test_pviews.py
@@ -53,7 +53,8 @@ class TestPViewsCommand(unittest.TestCase):
class View1(object):
pass
- request = dummy.DummyRequest({'PATH_INFO': '/a'})
+ request = dummy.DummyRequest()
+ request.path_info = '/a'
root = DefaultRootFactory(request)
root_iface = providedBy(root)
registry.registerAdapter(
@@ -78,7 +79,8 @@ class TestPViewsCommand(unittest.TestCase):
def view1(): # pragma: no cover
pass
- request = dummy.DummyRequest({'PATH_INFO': '/a'})
+ request = dummy.DummyRequest()
+ request.path_info = '/a'
root = DefaultRootFactory(request)
root_iface = providedBy(root)
registry.registerAdapter(
@@ -105,7 +107,8 @@ class TestPViewsCommand(unittest.TestCase):
class View1(object):
pass
- request = dummy.DummyRequest({'PATH_INFO': '/a'})
+ request = dummy.DummyRequest()
+ request.path_info = '/a'
root = DefaultRootFactory(request)
root_iface = providedBy(root)
view = View1()
@@ -267,7 +270,8 @@ class TestPViewsCommand(unittest.TestCase):
dummy.DummyRoute('b', '/a', factory=factory, matchdict={}),
]
mapper = dummy.DummyMapper(*routes)
- request = dummy.DummyRequest({'PATH_INFO': '/a'})
+ request = dummy.DummyRequest()
+ request.path_info = '/a'
result = command._find_multi_routes(mapper, request)
self.assertEqual(
result,
@@ -288,7 +292,8 @@ class TestPViewsCommand(unittest.TestCase):
dummy.DummyRoute('b', '/a', factory=factory, matchdict={}),
]
mapper = dummy.DummyMapper(*routes)
- request = dummy.DummyRequest({'PATH_INFO': '/a'})
+ request = dummy.DummyRequest()
+ request.path_info = '/a'
result = command._find_multi_routes(mapper, request)
self.assertEqual(result, [{'match': {}, 'route': routes[1]}])
@@ -303,7 +308,8 @@ class TestPViewsCommand(unittest.TestCase):
dummy.DummyRoute('b', '/a', factory=factory),
]
mapper = dummy.DummyMapper(*routes)
- request = dummy.DummyRequest({'PATH_INFO': '/a'})
+ request = dummy.DummyRequest()
+ request.path_info = '/a'
result = command._find_multi_routes(mapper, request)
self.assertEqual(result, [])
diff --git a/tests/test_session.py b/tests/test_session.py
index 5e2a1ff55..8e5e82bb2 100644
--- a/tests/test_session.py
+++ b/tests/test_session.py
@@ -1,8 +1,8 @@
import base64
import json
+import pickle
import unittest
from pyramid import testing
-from pyramid.compat import pickle
class SharedCookieSessionTests(object):
@@ -607,13 +607,7 @@ class DummySerializer(object):
return base64.b64encode(json.dumps(value).encode('utf-8'))
def loads(self, value):
- try:
- return json.loads(base64.b64decode(value).decode('utf-8'))
-
- # base64.b64decode raises a TypeError on py2 instead of a ValueError
- # and a ValueError is required for the session to handle it properly
- except TypeError:
- raise ValueError
+ return json.loads(base64.b64decode(value).decode('utf-8'))
class DummySessionFactory(dict):
diff --git a/tests/test_traversal.py b/tests/test_traversal.py
index 61e480cbc..188ee803c 100644
--- a/tests/test_traversal.py
+++ b/tests/test_traversal.py
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
import unittest
+from urllib.parse import quote
from pyramid.testing import cleanUp
-from pyramid.compat import text_, native_, text_type, url_quote, PY2
+from pyramid.util import text_
class TraversalPathTests(unittest.TestCase):
@@ -14,7 +15,7 @@ class TraversalPathTests(unittest.TestCase):
def test_utf8(self):
la = b'La Pe\xc3\xb1a'
- encoded = url_quote(la)
+ encoded = quote(la)
decoded = text_(la, 'utf-8')
path = '/'.join([encoded, encoded])
result = self._callFUT(path)
@@ -24,7 +25,7 @@ class TraversalPathTests(unittest.TestCase):
from pyramid.exceptions import URLDecodeError
la = text_(b'La Pe\xc3\xb1a', 'utf-8').encode('utf-16')
- encoded = url_quote(la)
+ encoded = quote(la)
path = '/'.join([encoded, encoded])
self.assertRaises(URLDecodeError, self._callFUT, path)
@@ -71,8 +72,8 @@ class TraversalPathInfoTests(unittest.TestCase):
def test_segments_are_unicode(self):
result = self._callFUT('/foo/bar')
- self.assertEqual(type(result[0]), text_type)
- self.assertEqual(type(result[1]), text_type)
+ self.assertEqual(type(result[0]), str)
+ self.assertEqual(type(result[1]), str)
def test_same_value_returned_if_cached(self):
result1 = self._callFUT('/foo/bar')
@@ -86,15 +87,14 @@ class TraversalPathInfoTests(unittest.TestCase):
def test_highorder(self):
la = b'La Pe\xc3\xb1a'
- latin1 = native_(la)
+ latin1 = text_(la)
result = self._callFUT(latin1)
self.assertEqual(result, (text_(la, 'utf-8'),))
def test_highorder_undecodeable(self):
from pyramid.exceptions import URLDecodeError
- la = text_(b'La Pe\xc3\xb1a', 'utf-8')
- notlatin1 = native_(la)
+ notlatin1 = text_(b'La Pe\xc3\xb1a', 'utf-8')
self.assertRaises(URLDecodeError, self._callFUT, notlatin1)
@@ -346,10 +346,7 @@ class ResourceTreeTraverserTests(unittest.TestCase):
foo = DummyContext(bar, path)
root = DummyContext(foo, 'root')
policy = self._makeOne(root)
- if PY2:
- vhm_root = b'/Qu\xc3\xa9bec'
- else:
- vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1')
+ vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1')
environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root)
request = DummyRequest(environ, path_info=text_('/bar'))
result = policy(request)
@@ -680,7 +677,7 @@ class FindResourceTests(unittest.TestCase):
def test_absolute_unicode_found(self):
# test for bug wiggy found in wild, traceback stack:
- # root = u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF'
+ # root = '/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF'
# wiggy's code: section=find_resource(page, root)
# find_resource L76: D = traverse(resource, path)
# traverse L291: return traverser(request)
@@ -873,15 +870,6 @@ class QuotePathSegmentTests(unittest.TestCase):
result = self._callFUT(s)
self.assertEqual(result, '12345')
- def test_long(self):
- from pyramid.compat import long
- import sys
-
- s = long(sys.maxsize + 1)
- result = self._callFUT(s)
- expected = str(s)
- self.assertEqual(result, expected)
-
def test_other(self):
class Foo(object):
def __str__(self):
@@ -1226,18 +1214,18 @@ class Test__join_path_tuple(unittest.TestCase):
def test_segments_with_unsafes(self):
safe_segments = tuple(
- u"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
- u"-._~!$&'()*+,;=:@"
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "-._~!$&'()*+,;=:@"
)
result = self._callFUT(safe_segments)
- self.assertEqual(result, u'/'.join(safe_segments))
+ self.assertEqual(result, '/'.join(safe_segments))
unsafe_segments = tuple(
chr(i) for i in range(0x20, 0x80) if not chr(i) in safe_segments
- ) + (u'あ',)
+ ) + ('あ',)
result = self._callFUT(unsafe_segments)
self.assertEqual(
result,
- u'/'.join(
+ '/'.join(
''.join(
'%%%02X' % (ord(c) if isinstance(c, str) else c)
for c in unsafe_segment.encode('utf-8')
diff --git a/tests/test_url.py b/tests/test_url.py
index 94a0a61c9..648f48d53 100644
--- a/tests/test_url.py
+++ b/tests/test_url.py
@@ -3,7 +3,7 @@ import unittest
from pyramid import testing
-from pyramid.compat import text_, WIN
+from pyramid.util import WIN, text_
class TestURLMethodsMixin(unittest.TestCase):
@@ -25,6 +25,7 @@ class TestURLMethodsMixin(unittest.TestCase):
def __init__(self, environ):
self.environ = environ
+ self.scheme = environ.get('wsgi.url_scheme', 'http')
request = Request(environ)
request.registry = self.config.registry
diff --git a/tests/test_urldispatch.py b/tests/test_urldispatch.py
index 772250e89..5d77042ae 100644
--- a/tests/test_urldispatch.py
+++ b/tests/test_urldispatch.py
@@ -1,6 +1,6 @@
import unittest
from pyramid import testing
-from pyramid.compat import text_, PY2
+from pyramid.util import text_
class TestRoute(unittest.TestCase):
@@ -59,9 +59,7 @@ class RoutesMapperTests(unittest.TestCase):
def _getRequest(self, **kw):
from pyramid.threadlocal import get_current_registry
- environ = {'SERVER_NAME': 'localhost', 'wsgi.url_scheme': 'http'}
- environ.update(kw)
- request = DummyRequest(environ)
+ request = DummyRequest(**kw)
reg = get_current_registry()
request.registry = reg
return request
@@ -83,7 +81,7 @@ class RoutesMapperTests(unittest.TestCase):
def test_no_route_matches(self):
mapper = self._makeOne()
- request = self._getRequest(PATH_INFO='/')
+ request = self._getRequest(path_info='/')
result = mapper(request)
self.assertEqual(result['match'], None)
self.assertEqual(result['route'], None)
@@ -130,19 +128,39 @@ class RoutesMapperTests(unittest.TestCase):
def test___call__pathinfo_cant_be_decoded(self):
from pyramid.exceptions import URLDecodeError
+ from pyramid.threadlocal import get_current_registry
+
+ class DummyRequest:
+ @property
+ def path_info(self):
+ return b'\xff\xfe\xe6\x00'.decode('utf-8')
mapper = self._makeOne()
- if PY2:
- path_info = b'\xff\xfe\xe6\x00'
- else:
- path_info = b'\xff\xfe\xe6\x00'.decode('latin-1')
- request = self._getRequest(PATH_INFO=path_info)
+ request = DummyRequest()
+ request.registry = get_current_registry()
self.assertRaises(URLDecodeError, mapper, request)
+ def test___call__pathinfo_KeyError(self):
+ from pyramid.threadlocal import get_current_registry
+
+ class DummyRequest:
+ @property
+ def path_info(self):
+ # if the PATH_INFO is missing from the environ
+ raise KeyError
+
+ mapper = self._makeOne()
+ mapper.connect('root', '')
+ request = DummyRequest()
+ request.registry = get_current_registry()
+ result = mapper(request)
+ self.assertEqual(result['route'], mapper.routes['root'])
+ self.assertEqual(result['match'], {})
+
def test___call__route_matches(self):
mapper = self._makeOne()
mapper.connect('foo', 'archives/:action/:article')
- request = self._getRequest(PATH_INFO='/archives/action1/article1')
+ request = self._getRequest(path_info='/archives/action1/article1')
result = mapper(request)
self.assertEqual(result['route'], mapper.routes['foo'])
self.assertEqual(result['match']['action'], 'action1')
@@ -153,7 +171,7 @@ class RoutesMapperTests(unittest.TestCase):
mapper.connect(
'foo', 'archives/:action/:article', predicates=[lambda *arg: True]
)
- request = self._getRequest(PATH_INFO='/archives/action1/article1')
+ request = self._getRequest(path_info='/archives/action1/article1')
result = mapper(request)
self.assertEqual(result['route'], mapper.routes['foo'])
self.assertEqual(result['match']['action'], 'action1')
@@ -167,7 +185,7 @@ class RoutesMapperTests(unittest.TestCase):
predicates=[lambda *arg: True, lambda *arg: False],
)
mapper.connect('bar', 'archives/:action/:article')
- request = self._getRequest(PATH_INFO='/archives/action1/article1')
+ request = self._getRequest(path_info='/archives/action1/article1')
result = mapper(request)
self.assertEqual(result['route'], mapper.routes['bar'])
self.assertEqual(result['match']['action'], 'action1')
@@ -182,7 +200,7 @@ class RoutesMapperTests(unittest.TestCase):
return True
mapper.connect('foo', 'archives/:action/article1', predicates=[pred])
- request = self._getRequest(PATH_INFO='/archives/action1/article1')
+ request = self._getRequest(path_info='/archives/action1/article1')
mapper(request)
def test_cc_bug(self):
@@ -194,13 +212,13 @@ class RoutesMapperTests(unittest.TestCase):
'juri', 'licenses/:license_code/:license_version/:jurisdiction'
)
- request = self._getRequest(PATH_INFO='/licenses/1/v2/rdf')
+ request = self._getRequest(path_info='/licenses/1/v2/rdf')
result = mapper(request)
self.assertEqual(result['route'], mapper.routes['rdf'])
self.assertEqual(result['match']['license_code'], '1')
self.assertEqual(result['match']['license_version'], 'v2')
- request = self._getRequest(PATH_INFO='/licenses/1/v2/usa')
+ request = self._getRequest(path_info='/licenses/1/v2/usa')
result = mapper(request)
self.assertEqual(result['route'], mapper.routes['juri'])
self.assertEqual(result['match']['license_code'], '1')
@@ -210,7 +228,7 @@ class RoutesMapperTests(unittest.TestCase):
def test___call__root_route_matches(self):
mapper = self._makeOne()
mapper.connect('root', '')
- request = self._getRequest(PATH_INFO='/')
+ request = self._getRequest(path_info='/')
result = mapper(request)
self.assertEqual(result['route'], mapper.routes['root'])
self.assertEqual(result['match'], {})
@@ -218,7 +236,7 @@ class RoutesMapperTests(unittest.TestCase):
def test___call__root_route_matches2(self):
mapper = self._makeOne()
mapper.connect('root', '/')
- request = self._getRequest(PATH_INFO='/')
+ request = self._getRequest(path_info='/')
result = mapper(request)
self.assertEqual(result['route'], mapper.routes['root'])
self.assertEqual(result['match'], {})
@@ -226,7 +244,7 @@ class RoutesMapperTests(unittest.TestCase):
def test___call__root_route_when_path_info_empty(self):
mapper = self._makeOne()
mapper.connect('root', '/')
- request = self._getRequest(PATH_INFO='')
+ request = self._getRequest(path_info='')
result = mapper(request)
self.assertEqual(result['route'], mapper.routes['root'])
self.assertEqual(result['match'], {})
@@ -234,7 +252,7 @@ class RoutesMapperTests(unittest.TestCase):
def test___call__root_route_when_path_info_notempty(self):
mapper = self._makeOne()
mapper.connect('root', '/')
- request = self._getRequest(PATH_INFO='/')
+ request = self._getRequest(path_info='/')
result = mapper(request)
self.assertEqual(result['route'], mapper.routes['root'])
self.assertEqual(result['match'], {})
@@ -242,7 +260,7 @@ class RoutesMapperTests(unittest.TestCase):
def test___call__no_path_info(self):
mapper = self._makeOne()
mapper.connect('root', '/')
- request = self._getRequest()
+ request = self._getRequest(path_info='')
result = mapper(request)
self.assertEqual(result['route'], mapper.routes['root'])
self.assertEqual(result['match'], {})
@@ -646,8 +664,10 @@ class DummyContext(object):
class DummyRequest(object):
- def __init__(self, environ):
- self.environ = environ
+ scheme = 'http'
+
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
class DummyRoute(object):
diff --git a/tests/test_util.py b/tests/test_util.py
index a36655f6f..84bc9379f 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -1,5 +1,6 @@
+import sys
import unittest
-from pyramid.compat import PY2, text_, bytes_
+from pyramid.util import text_, bytes_
class Test_InstancePropertyHelper(unittest.TestCase):
@@ -170,14 +171,10 @@ class Test_InstancePropertyHelper(unittest.TestCase):
self.assertEqual(2, foo.y)
def test_make_property_unicode(self):
- from pyramid.compat import text_
from pyramid.exceptions import ConfigurationError
cls = self._getTargetClass()
- if PY2:
- name = text_(b'La Pe\xc3\xb1a', 'utf-8')
- else:
- name = b'La Pe\xc3\xb1a'
+ name = b'La Pe\xc3\xb1a'
def make_bad_name():
cls.make_property(lambda x: 1, name=name, reify=True)
@@ -439,37 +436,11 @@ class Test_strings_differ(unittest.TestCase):
self.assertFalse(self._callFUT('123', '123'))
self.assertTrue(self._callFUT('123', '1234'))
- def test_it_with_internal_comparator(self):
- result = self._callFUT(b'foo', b'foo', compare_digest=None)
- self.assertFalse(result)
-
- result = self._callFUT(b'123', b'abc', compare_digest=None)
- self.assertTrue(result)
-
- def test_it_with_external_comparator(self):
- class DummyComparator(object):
- called = False
-
- def __init__(self, ret_val):
- self.ret_val = ret_val
-
- def __call__(self, a, b):
- self.called = True
- return self.ret_val
-
- dummy_compare = DummyComparator(True)
- result = self._callFUT(b'foo', b'foo', compare_digest=dummy_compare)
- self.assertTrue(dummy_compare.called)
+ def test_it(self):
+ result = self._callFUT(b'foo', b'foo')
self.assertFalse(result)
- dummy_compare = DummyComparator(False)
- result = self._callFUT(b'123', b'345', compare_digest=dummy_compare)
- self.assertTrue(dummy_compare.called)
- self.assertTrue(result)
-
- dummy_compare = DummyComparator(False)
- result = self._callFUT(b'abc', b'abc', compare_digest=dummy_compare)
- self.assertTrue(dummy_compare.called)
+ result = self._callFUT(b'123', b'abc')
self.assertTrue(result)
@@ -498,10 +469,7 @@ class Test_object_description(unittest.TestCase):
self.assertEqual(self._callFUT(('a', 'b')), "('a', 'b')")
def test_set(self):
- if PY2:
- self.assertEqual(self._callFUT(set(['a'])), "set(['a'])")
- else:
- self.assertEqual(self._callFUT(set(['a'])), "{'a'}")
+ self.assertEqual(self._callFUT(set(['a'])), "{'a'}")
def test_list(self):
self.assertEqual(self._callFUT(['a']), "['a']")
@@ -839,31 +807,26 @@ class TestSentinel(unittest.TestCase):
class TestCallableName(unittest.TestCase):
- def test_valid_ascii(self):
+ def _callFUT(self, val):
from pyramid.util import get_callable_name
- from pyramid.compat import text_
- if PY2:
- name = text_(b'hello world', 'utf-8')
- else:
- name = b'hello world'
+ return get_callable_name(val)
- self.assertEqual(get_callable_name(name), 'hello world')
+ def test_valid_ascii_bytes(self):
+ name = b'hello world'
+ self.assertEqual(self._callFUT(name), 'hello world')
- def test_invalid_ascii(self):
- from pyramid.util import get_callable_name
- from pyramid.compat import text_
+ def test_valid_ascii_string(self):
from pyramid.exceptions import ConfigurationError
- def get_bad_name():
- if PY2:
- name = text_(b'La Pe\xc3\xb1a', 'utf-8')
- else:
- name = b'La Pe\xc3\xb1a'
+ name = b'La Pe\xc3\xb1a'.decode('utf-8')
+ self.assertRaises(ConfigurationError, self._callFUT, name)
- get_callable_name(name)
+ def test_invalid_ascii(self):
+ from pyramid.exceptions import ConfigurationError
- self.assertRaises(ConfigurationError, get_bad_name)
+ name = b'La Pe\xc3\xb1a'
+ self.assertRaises(ConfigurationError, self._callFUT, name)
class Test_hide_attrs(unittest.TestCase):
@@ -1240,3 +1203,77 @@ class TestSimpleSerializer(unittest.TestCase):
def test_dumps(self):
inst = self._makeOne()
self.assertEqual(inst.dumps('abc'), bytes_('abc'))
+
+
+class TestUnboundMethods(unittest.TestCase):
+ class Dummy(object):
+ def run(self): # pragma: no cover
+ return 'OK'
+
+ def _callFUT(self, val):
+ from pyramid.util import is_unbound_method
+
+ return is_unbound_method(val)
+
+ def test_bound_method(self):
+ self.assertFalse(self._callFUT(self.Dummy().run))
+
+ def test_unbound_method(self):
+ self.assertTrue(self._callFUT(self.Dummy.run))
+
+ def test_normal_func_unbound(self):
+ def func(): # pragma: no cover
+ return 'OK'
+
+ self.assertFalse(self._callFUT(func))
+
+
+class TestReraise(unittest.TestCase):
+ def _callFUT(self, *args):
+ from pyramid.util import reraise
+
+ return reraise(*args)
+
+ def test_it(self):
+ # tests cribbed from six.py
+ def get_next(tb):
+ return tb.tb_next.tb_next.tb_next
+
+ e = Exception('blah')
+ try:
+ raise e
+ except Exception:
+ tp, val, tb = sys.exc_info()
+
+ try:
+ self._callFUT(tp, val, tb)
+ except Exception:
+ tp2, val2, tb2 = sys.exc_info()
+ self.assertIs(tp2, Exception)
+ self.assertIs(val2, e)
+ self.assertIs(get_next(tb2), tb)
+
+ try:
+ self._callFUT(tp, val)
+ except Exception:
+ tp2, val2, tb2 = sys.exc_info()
+ self.assertIs(tp2, Exception)
+ self.assertIs(val2, e)
+ self.assertIsNot(get_next(tb2), tb)
+
+ try:
+ self._callFUT(tp, val, tb2)
+ except Exception:
+ tp2, val2, tb3 = sys.exc_info()
+ self.assertIs(tp2, Exception)
+ self.assertIs(val2, e)
+ self.assertIs(get_next(tb3), tb2)
+
+ try:
+ self._callFUT(tp, None, tb)
+ except Exception:
+ tp2, val2, tb2 = sys.exc_info()
+ self.assertIs(tp2, Exception)
+ self.assertIsNot(val2, val)
+ self.assertIsInstance(val2, Exception)
+ self.assertIs(get_next(tb2), tb)
diff --git a/tests/test_view.py b/tests/test_view.py
index f82480169..de40df1d5 100644
--- a/tests/test_view.py
+++ b/tests/test_view.py
@@ -417,12 +417,11 @@ class RenderViewToIterableTests(BaseTest, unittest.TestCase):
from pyramid.request import Request
from pyramid.config import Configurator
from pyramid.view import render_view
- from webob.compat import text_type
config = Configurator(settings={})
def view(request):
- request.response.text = text_type('<body></body>')
+ request.response.text = '<body></body>'
return request.response
config.add_view(name='test', view=view)
diff --git a/tox.ini b/tox.ini
index 5bf19d2a7..33e3c2c81 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,29 +1,19 @@
[tox]
envlist =
lint,
- py27,py34,py35,py36,py37,py38,pypy,pypy3,
- docs,{py2,py3}-cover,coverage,
+ py34,py35,py36,py37,pypy3,
+ docs,py36-cover,coverage,
[testenv]
-# Most of these are defaults but if you specify any you can't fall back
-# to defaults for others.
-basepython =
- py27: python2.7
- py34: python3.4
- py35: python3.5
- py36: python3.6
- py37: python3.7
- py38: python3.8
- pypy: pypy
- pypy3: pypy3
- py2: python2.7
- py3: python3.6
-
commands =
- nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:}
-
+ cover: coverage run \
+ {envbindir}/nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:}
extras =
testing
+deps =
+ cover: coverage
+setenv =
+ COVERAGE_FILE=.coverage.{envname}
[testenv:lint]
skip_install = true
@@ -56,32 +46,10 @@ commands =
extras =
docs
-# we separate coverage into its own testenv because a) "last run wins" wrt
-# cobertura jenkins reporting and b) pypy and jython can't handle any
-# combination of versions of coverage and nosexcover that i can find.
-[testenv:py2-cover]
-commands =
- coverage run {envbindir}/nosetests
- coverage xml -o coverage-py2.xml
-setenv =
- COVERAGE_FILE=.coverage.py2
-extras =
- testing
-
-[testenv:py3-cover]
-commands =
- coverage run {envbindir}/nosetests
- coverage xml -o coverage-py3.xml
-setenv =
- COVERAGE_FILE=.coverage.py3
-extras =
- testing
-
[testenv:coverage]
skip_install = true
basepython = python3.6
commands =
- coverage erase
coverage combine
coverage xml
coverage report --fail-under=100
@@ -109,7 +77,6 @@ commands =
python setup.py sdist --dist-dir {toxinidir}/dist
# build wheel from sdist
pip wheel -v --no-deps --no-index --no-build-isolation --wheel-dir {toxinidir}/dist --find-links {toxinidir}/dist pyramid
-
deps =
setuptools
wheel