summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2020-01-10 00:50:03 -0600
committerMichael Merickel <michael@merickel.org>2020-01-10 01:07:04 -0600
commit095eb560dc17dc591d43144758adaf2e4c780e72 (patch)
tree400867fe71d121e65e40469231616d1386d6200d /docs
parent7adc44fa2b4bfa5b4230d8646e734ba262ec1ce2 (diff)
downloadpyramid-095eb560dc17dc591d43144758adaf2e4c780e72.tar.gz
pyramid-095eb560dc17dc591d43144758adaf2e4c780e72.tar.bz2
pyramid-095eb560dc17dc591d43144758adaf2e4c780e72.zip
sync wiki installation, basiclayout, models and views chapters with new cookiecutter
Diffstat (limited to 'docs')
-rw-r--r--docs/tutorials/wiki/basiclayout.rst35
-rw-r--r--docs/tutorials/wiki/definingviews.rst30
-rw-r--r--docs/tutorials/wiki/installation.rst4
-rw-r--r--docs/tutorials/wiki/src/basiclayout/.gitignore1
-rw-r--r--docs/tutorials/wiki/src/basiclayout/testing.ini60
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tests/conftest.py69
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tests/test_functional.py7
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tests/test_it.py24
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tests/test_views.py13
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py5
-rw-r--r--docs/tutorials/wiki/src/installation/.gitignore1
-rw-r--r--docs/tutorials/wiki/src/installation/testing.ini60
-rw-r--r--docs/tutorials/wiki/src/installation/tests/conftest.py69
-rw-r--r--docs/tutorials/wiki/src/installation/tests/test_functional.py7
-rw-r--r--docs/tutorials/wiki/src/installation/tests/test_it.py24
-rw-r--r--docs/tutorials/wiki/src/installation/tests/test_views.py13
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/__init__.py5
-rw-r--r--docs/tutorials/wiki/src/models/.gitignore1
-rw-r--r--docs/tutorials/wiki/src/models/testing.ini60
-rw-r--r--docs/tutorials/wiki/src/models/tests/conftest.py69
-rw-r--r--docs/tutorials/wiki/src/models/tests/test_functional.py7
-rw-r--r--docs/tutorials/wiki/src/models/tests/test_it.py24
-rw-r--r--docs/tutorials/wiki/src/models/tests/test_views.py13
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/__init__.py5
-rw-r--r--docs/tutorials/wiki/src/views/.gitignore1
-rw-r--r--docs/tutorials/wiki/src/views/setup.py2
-rw-r--r--docs/tutorials/wiki/src/views/testing.ini60
-rw-r--r--docs/tutorials/wiki/src/views/tests/conftest.py69
-rw-r--r--docs/tutorials/wiki/src/views/tests/test_functional.py7
-rw-r--r--docs/tutorials/wiki/src/views/tests/test_it.py24
-rw-r--r--docs/tutorials/wiki/src/views/tests/test_views.py13
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/__init__.py5
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/layout.pt12
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views/default.py13
-rw-r--r--docs/tutorials/wiki2/installation.rst4
35 files changed, 669 insertions, 147 deletions
diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst
index 4eb5c4283..c1c762ae4 100644
--- a/docs/tutorials/wiki/basiclayout.rst
+++ b/docs/tutorials/wiki/basiclayout.rst
@@ -57,7 +57,7 @@ Next in ``main``, construct a :term:`Configurator` object using a context manage
See also :term:`Deployment settings`.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 14
+ :lines: 15
:lineno-match:
:language: py
@@ -65,35 +65,28 @@ See also :term:`Deployment settings`.
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.
+Next include support for the :term:`Chameleon` template rendering bindings, allowing us to use the ``.pt`` templates.
.. 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.
+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: 17
:lineno-match:
:language: py
-Next set a root factory using our function named ``root_factory``.
+Next include support for ``pyramid_retry`` to retry a request when transient exceptions occur.
.. 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.
+Next include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:lines: 19
@@ -107,6 +100,13 @@ Next include routes from the ``.routes`` module.
:lineno-match:
:language: py
+Next set a root factory using our function named ``root_factory``.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 21
+ :lineno-match:
+ :language: py
+
The included module contains the following function.
.. literalinclude:: src/basiclayout/tutorial/routes.py
@@ -130,7 +130,7 @@ The third argument is an optional ``cache_max_age`` which specifies the number o
Back into our ``__init__.py``, next perform a :term:`scan`.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 21
+ :lines: 22
:lineno-match:
:language: py
@@ -142,7 +142,7 @@ The cookiecutter could have equivalently said ``config.scan('tutorial')``, but i
Finally use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 22
+ :lines: 23
:lineno-match:
:language: py
@@ -262,3 +262,10 @@ The ``development.ini`` (in the ``tutorial`` :term:`project` directory, as oppos
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``.
+
+
+Tests
+-----
+
+The project contains a basic structure for a test suite using ``pytest``.
+The structure is covered later in :ref:`wiki_adding_tests`.
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index 2e4d009a1..5aafd68d6 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -39,8 +39,9 @@ We need to add a dependency on the ``docutils`` package to our ``tutorial`` pack
Open ``setup.py`` and edit it to look like the following:
.. literalinclude:: src/views/setup.py
- :linenos:
- :emphasize-lines: 22
+ :lines: 11-29
+ :lineno-match:
+ :emphasize-lines: 2
:language: python
Only the highlighted line needs to be added.
@@ -91,7 +92,7 @@ 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 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 ``default.py`` module:
* ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL.
* ``view_page()`` - Displays an individual page.
@@ -102,7 +103,7 @@ We will describe each one briefly in the following sections.
.. note::
- There is nothing special about the filename ``views.py``.
+ There is nothing special about the filename ``default.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.
@@ -113,7 +114,7 @@ The ``view_wiki`` view function
Following is the code for the ``view_wiki`` view function and its decorator:
.. literalinclude:: src/views/tutorial/views/default.py
- :lines: 13-15
+ :lines: 12-14
:lineno-match:
:language: python
@@ -133,9 +134,9 @@ The view configuration associated with ``view_wiki`` does not use a ``renderer``
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.
+To do so, it returns an instance of the :class:`pyramid.httpexceptions.HTTPSeeOther` 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.
+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 ``HTTPSeeOther`` response, forming an HTTP redirect.
The ``view_page`` view function
@@ -144,7 +145,7 @@ The ``view_page`` view function
Here is the code for the ``view_page`` view function and its decorator:
.. literalinclude:: src/views/tutorial/views/default.py
- :lines: 18-35
+ :lines: 17-34
:lineno-match:
:language: python
@@ -183,7 +184,7 @@ The ``add_page`` view function
Here is the code for the ``add_page`` view function and its decorator:
.. literalinclude:: src/views/tutorial/views/default.py
- :lines: 38-53
+ :lines: 37-52
:lineno-match:
:language: python
@@ -231,7 +232,7 @@ The ``edit_page`` view function
Here is the code for the ``edit_page`` view function and its decorator:
.. literalinclude:: src/views/tutorial/views/default.py
- :lines: 56-64
+ :lines: 55-
:lineno-match:
:language: python
@@ -260,7 +261,7 @@ Open ``tutorial/views/notfound.py`` and make the changes shown by the emphasized
.. literalinclude:: src/views/tutorial/views/notfound.py
:linenos:
:language: python
- :emphasize-lines: 3-4, 9-12
+ :emphasize-lines: 3, 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.
@@ -282,7 +283,7 @@ Update ``tutorial/templates/layout.pt`` with the following content, as indicated
.. literalinclude:: src/views/tutorial/templates/layout.pt
:linenos:
- :emphasize-lines: 11-12, 37-41
+ :emphasize-lines: 11, 36-40
:language: html
Since we are using a templating engine, we can factor common boilerplate out of our page templates into reusable components.
@@ -291,11 +292,10 @@ 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).
+- The cookiecutter defined a macro customization point or `slot` (line 35).
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.
+- We added a ``div`` element with a link to allow the user to return to the front page (lines 36-40).
.. seealso::
diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst
index 6088f577d..392441eae 100644
--- a/docs/tutorials/wiki/installation.rst
+++ b/docs/tutorials/wiki/installation.rst
@@ -243,8 +243,8 @@ For a successful test run, you should see output that ends like this:
.. code-block:: bash
- ..
- 2 passed in 0.49 seconds
+ ....
+ 4 passed in 0.49 seconds
Expose test coverage information
diff --git a/docs/tutorials/wiki/src/basiclayout/.gitignore b/docs/tutorials/wiki/src/basiclayout/.gitignore
index 1853d983c..c612e59f2 100644
--- a/docs/tutorials/wiki/src/basiclayout/.gitignore
+++ b/docs/tutorials/wiki/src/basiclayout/.gitignore
@@ -19,3 +19,4 @@ Data.fs*
.DS_Store
coverage
test
+*.sqlite
diff --git a/docs/tutorials/wiki/src/basiclayout/testing.ini b/docs/tutorials/wiki/src/basiclayout/testing.ini
new file mode 100644
index 000000000..9298354ac
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/testing.ini
@@ -0,0 +1,60 @@
+###
+# app configuration
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+
+zodbconn.uri = file://%(here)s/Data.testing.fs?connection_cache_size=20000
+
+retry.attempts = 3
+
+[pshell]
+setup = tutorial.pshell.setup
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+listen = localhost:6543
+
+###
+# logging configuration
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/basiclayout/tests/conftest.py b/docs/tutorials/wiki/src/basiclayout/tests/conftest.py
new file mode 100644
index 000000000..12e75d8e9
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tests/conftest.py
@@ -0,0 +1,69 @@
+import os
+from pyramid.paster import get_appsettings
+from pyramid.scripting import prepare
+from pyramid.testing import DummyRequest
+import pytest
+import webtest
+
+from tutorial import main
+
+
+def pytest_addoption(parser):
+ parser.addoption('--ini', action='store', metavar='INI_FILE')
+
+@pytest.fixture(scope='session')
+def ini_file(request):
+ # potentially grab this path from a pytest option
+ return os.path.abspath(request.config.option.ini or 'testing.ini')
+
+@pytest.fixture(scope='session')
+def app_settings(ini_file):
+ return get_appsettings(ini_file)
+
+@pytest.fixture(scope='session')
+def app(app_settings):
+ return main({}, **app_settings)
+
+@pytest.fixture
+def testapp(app):
+ testapp = webtest.TestApp(app, extra_environ={
+ 'HTTP_HOST': 'example.com',
+ })
+
+ return testapp
+
+@pytest.fixture
+def app_request(app):
+ """
+ A real request.
+
+ This request is almost identical to a real request but it has some
+ drawbacks in tests as it's harder to mock data and is heavier.
+
+ """
+ env = prepare(registry=app.registry)
+ request = env['request']
+ request.host = 'example.com'
+
+ yield request
+ env['closer']()
+
+@pytest.fixture
+def dummy_request(app):
+ """
+ A lightweight dummy request.
+
+ This request is ultra-lightweight and should be used only when the
+ request itself is not a large focus in the call-stack.
+
+ It is way easier to mock and control side-effects using this object.
+
+ - It does not have request extensions applied.
+ - Threadlocals are not properly pushed.
+
+ """
+ request = DummyRequest()
+ request.registry = app.registry
+ request.host = 'example.com'
+
+ return request
diff --git a/docs/tutorials/wiki/src/basiclayout/tests/test_functional.py b/docs/tutorials/wiki/src/basiclayout/tests/test_functional.py
new file mode 100644
index 000000000..bac5d63f4
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tests/test_functional.py
@@ -0,0 +1,7 @@
+def test_root(testapp):
+ res = testapp.get('/', status=200)
+ assert b'Pyramid' in res.body
+
+def test_notfound(testapp):
+ res = testapp.get('/badurl', status=404)
+ assert res.status_code == 404
diff --git a/docs/tutorials/wiki/src/basiclayout/tests/test_it.py b/docs/tutorials/wiki/src/basiclayout/tests/test_it.py
deleted file mode 100644
index 6c72bcc62..000000000
--- a/docs/tutorials/wiki/src/basiclayout/tests/test_it.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import unittest
-
-from pyramid import testing
-
-
-class ViewTests(unittest.TestCase):
- def setUp(self):
- self.config = testing.setUp()
-
- def tearDown(self):
- testing.tearDown()
-
- def test_my_view(self):
- from tutorial.views.default import my_view
- request = testing.DummyRequest()
- info = my_view(request)
- self.assertEqual(info['project'], 'myproj')
-
- def test_notfound_view(self):
- from tutorial.views.notfound import notfound_view
- request = testing.DummyRequest()
- info = notfound_view(request)
- self.assertEqual(info, {})
-
diff --git a/docs/tutorials/wiki/src/basiclayout/tests/test_views.py b/docs/tutorials/wiki/src/basiclayout/tests/test_views.py
new file mode 100644
index 000000000..2b4201955
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tests/test_views.py
@@ -0,0 +1,13 @@
+from tutorial.views.default import my_view
+from tutorial.views.notfound import notfound_view
+
+
+def test_my_view(app_request):
+ info = my_view(app_request)
+ assert app_request.response.status_int == 200
+ assert info['project'] == 'myproj'
+
+def test_notfound_view(app_request):
+ info = notfound_view(app_request)
+ assert app_request.response.status_int == 404
+ assert info == {}
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
index 830a607f3..e40451339 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
@@ -1,5 +1,6 @@
from pyramid.config import Configurator
from pyramid_zodbconn import get_connection
+
from .models import appmaker
@@ -12,11 +13,11 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
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.include('pyramid_chameleon')
config.include('.routes')
+ config.set_root_factory(root_factory)
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/installation/.gitignore b/docs/tutorials/wiki/src/installation/.gitignore
index 1853d983c..c612e59f2 100644
--- a/docs/tutorials/wiki/src/installation/.gitignore
+++ b/docs/tutorials/wiki/src/installation/.gitignore
@@ -19,3 +19,4 @@ Data.fs*
.DS_Store
coverage
test
+*.sqlite
diff --git a/docs/tutorials/wiki/src/installation/testing.ini b/docs/tutorials/wiki/src/installation/testing.ini
new file mode 100644
index 000000000..9298354ac
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/testing.ini
@@ -0,0 +1,60 @@
+###
+# app configuration
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+
+zodbconn.uri = file://%(here)s/Data.testing.fs?connection_cache_size=20000
+
+retry.attempts = 3
+
+[pshell]
+setup = tutorial.pshell.setup
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+listen = localhost:6543
+
+###
+# logging configuration
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/installation/tests/conftest.py b/docs/tutorials/wiki/src/installation/tests/conftest.py
new file mode 100644
index 000000000..12e75d8e9
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tests/conftest.py
@@ -0,0 +1,69 @@
+import os
+from pyramid.paster import get_appsettings
+from pyramid.scripting import prepare
+from pyramid.testing import DummyRequest
+import pytest
+import webtest
+
+from tutorial import main
+
+
+def pytest_addoption(parser):
+ parser.addoption('--ini', action='store', metavar='INI_FILE')
+
+@pytest.fixture(scope='session')
+def ini_file(request):
+ # potentially grab this path from a pytest option
+ return os.path.abspath(request.config.option.ini or 'testing.ini')
+
+@pytest.fixture(scope='session')
+def app_settings(ini_file):
+ return get_appsettings(ini_file)
+
+@pytest.fixture(scope='session')
+def app(app_settings):
+ return main({}, **app_settings)
+
+@pytest.fixture
+def testapp(app):
+ testapp = webtest.TestApp(app, extra_environ={
+ 'HTTP_HOST': 'example.com',
+ })
+
+ return testapp
+
+@pytest.fixture
+def app_request(app):
+ """
+ A real request.
+
+ This request is almost identical to a real request but it has some
+ drawbacks in tests as it's harder to mock data and is heavier.
+
+ """
+ env = prepare(registry=app.registry)
+ request = env['request']
+ request.host = 'example.com'
+
+ yield request
+ env['closer']()
+
+@pytest.fixture
+def dummy_request(app):
+ """
+ A lightweight dummy request.
+
+ This request is ultra-lightweight and should be used only when the
+ request itself is not a large focus in the call-stack.
+
+ It is way easier to mock and control side-effects using this object.
+
+ - It does not have request extensions applied.
+ - Threadlocals are not properly pushed.
+
+ """
+ request = DummyRequest()
+ request.registry = app.registry
+ request.host = 'example.com'
+
+ return request
diff --git a/docs/tutorials/wiki/src/installation/tests/test_functional.py b/docs/tutorials/wiki/src/installation/tests/test_functional.py
new file mode 100644
index 000000000..bac5d63f4
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tests/test_functional.py
@@ -0,0 +1,7 @@
+def test_root(testapp):
+ res = testapp.get('/', status=200)
+ assert b'Pyramid' in res.body
+
+def test_notfound(testapp):
+ res = testapp.get('/badurl', status=404)
+ assert res.status_code == 404
diff --git a/docs/tutorials/wiki/src/installation/tests/test_it.py b/docs/tutorials/wiki/src/installation/tests/test_it.py
deleted file mode 100644
index 6c72bcc62..000000000
--- a/docs/tutorials/wiki/src/installation/tests/test_it.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import unittest
-
-from pyramid import testing
-
-
-class ViewTests(unittest.TestCase):
- def setUp(self):
- self.config = testing.setUp()
-
- def tearDown(self):
- testing.tearDown()
-
- def test_my_view(self):
- from tutorial.views.default import my_view
- request = testing.DummyRequest()
- info = my_view(request)
- self.assertEqual(info['project'], 'myproj')
-
- def test_notfound_view(self):
- from tutorial.views.notfound import notfound_view
- request = testing.DummyRequest()
- info = notfound_view(request)
- self.assertEqual(info, {})
-
diff --git a/docs/tutorials/wiki/src/installation/tests/test_views.py b/docs/tutorials/wiki/src/installation/tests/test_views.py
new file mode 100644
index 000000000..2b4201955
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tests/test_views.py
@@ -0,0 +1,13 @@
+from tutorial.views.default import my_view
+from tutorial.views.notfound import notfound_view
+
+
+def test_my_view(app_request):
+ info = my_view(app_request)
+ assert app_request.response.status_int == 200
+ assert info['project'] == 'myproj'
+
+def test_notfound_view(app_request):
+ info = notfound_view(app_request)
+ assert app_request.response.status_int == 404
+ assert info == {}
diff --git a/docs/tutorials/wiki/src/installation/tutorial/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
index 830a607f3..e40451339 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
@@ -1,5 +1,6 @@
from pyramid.config import Configurator
from pyramid_zodbconn import get_connection
+
from .models import appmaker
@@ -12,11 +13,11 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
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.include('pyramid_chameleon')
config.include('.routes')
+ config.set_root_factory(root_factory)
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/models/.gitignore b/docs/tutorials/wiki/src/models/.gitignore
index 1853d983c..c612e59f2 100644
--- a/docs/tutorials/wiki/src/models/.gitignore
+++ b/docs/tutorials/wiki/src/models/.gitignore
@@ -19,3 +19,4 @@ Data.fs*
.DS_Store
coverage
test
+*.sqlite
diff --git a/docs/tutorials/wiki/src/models/testing.ini b/docs/tutorials/wiki/src/models/testing.ini
new file mode 100644
index 000000000..9298354ac
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/testing.ini
@@ -0,0 +1,60 @@
+###
+# app configuration
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+
+zodbconn.uri = file://%(here)s/Data.testing.fs?connection_cache_size=20000
+
+retry.attempts = 3
+
+[pshell]
+setup = tutorial.pshell.setup
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+listen = localhost:6543
+
+###
+# logging configuration
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/models/tests/conftest.py b/docs/tutorials/wiki/src/models/tests/conftest.py
new file mode 100644
index 000000000..12e75d8e9
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tests/conftest.py
@@ -0,0 +1,69 @@
+import os
+from pyramid.paster import get_appsettings
+from pyramid.scripting import prepare
+from pyramid.testing import DummyRequest
+import pytest
+import webtest
+
+from tutorial import main
+
+
+def pytest_addoption(parser):
+ parser.addoption('--ini', action='store', metavar='INI_FILE')
+
+@pytest.fixture(scope='session')
+def ini_file(request):
+ # potentially grab this path from a pytest option
+ return os.path.abspath(request.config.option.ini or 'testing.ini')
+
+@pytest.fixture(scope='session')
+def app_settings(ini_file):
+ return get_appsettings(ini_file)
+
+@pytest.fixture(scope='session')
+def app(app_settings):
+ return main({}, **app_settings)
+
+@pytest.fixture
+def testapp(app):
+ testapp = webtest.TestApp(app, extra_environ={
+ 'HTTP_HOST': 'example.com',
+ })
+
+ return testapp
+
+@pytest.fixture
+def app_request(app):
+ """
+ A real request.
+
+ This request is almost identical to a real request but it has some
+ drawbacks in tests as it's harder to mock data and is heavier.
+
+ """
+ env = prepare(registry=app.registry)
+ request = env['request']
+ request.host = 'example.com'
+
+ yield request
+ env['closer']()
+
+@pytest.fixture
+def dummy_request(app):
+ """
+ A lightweight dummy request.
+
+ This request is ultra-lightweight and should be used only when the
+ request itself is not a large focus in the call-stack.
+
+ It is way easier to mock and control side-effects using this object.
+
+ - It does not have request extensions applied.
+ - Threadlocals are not properly pushed.
+
+ """
+ request = DummyRequest()
+ request.registry = app.registry
+ request.host = 'example.com'
+
+ return request
diff --git a/docs/tutorials/wiki/src/models/tests/test_functional.py b/docs/tutorials/wiki/src/models/tests/test_functional.py
new file mode 100644
index 000000000..bac5d63f4
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tests/test_functional.py
@@ -0,0 +1,7 @@
+def test_root(testapp):
+ res = testapp.get('/', status=200)
+ assert b'Pyramid' in res.body
+
+def test_notfound(testapp):
+ res = testapp.get('/badurl', status=404)
+ assert res.status_code == 404
diff --git a/docs/tutorials/wiki/src/models/tests/test_it.py b/docs/tutorials/wiki/src/models/tests/test_it.py
deleted file mode 100644
index 6c72bcc62..000000000
--- a/docs/tutorials/wiki/src/models/tests/test_it.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import unittest
-
-from pyramid import testing
-
-
-class ViewTests(unittest.TestCase):
- def setUp(self):
- self.config = testing.setUp()
-
- def tearDown(self):
- testing.tearDown()
-
- def test_my_view(self):
- from tutorial.views.default import my_view
- request = testing.DummyRequest()
- info = my_view(request)
- self.assertEqual(info['project'], 'myproj')
-
- def test_notfound_view(self):
- from tutorial.views.notfound import notfound_view
- request = testing.DummyRequest()
- info = notfound_view(request)
- self.assertEqual(info, {})
-
diff --git a/docs/tutorials/wiki/src/models/tests/test_views.py b/docs/tutorials/wiki/src/models/tests/test_views.py
new file mode 100644
index 000000000..2b4201955
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tests/test_views.py
@@ -0,0 +1,13 @@
+from tutorial.views.default import my_view
+from tutorial.views.notfound import notfound_view
+
+
+def test_my_view(app_request):
+ info = my_view(app_request)
+ assert app_request.response.status_int == 200
+ assert info['project'] == 'myproj'
+
+def test_notfound_view(app_request):
+ info = notfound_view(app_request)
+ assert app_request.response.status_int == 404
+ assert info == {}
diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py
index 830a607f3..e40451339 100644
--- a/docs/tutorials/wiki/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py
@@ -1,5 +1,6 @@
from pyramid.config import Configurator
from pyramid_zodbconn import get_connection
+
from .models import appmaker
@@ -12,11 +13,11 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
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.include('pyramid_chameleon')
config.include('.routes')
+ config.set_root_factory(root_factory)
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/views/.gitignore b/docs/tutorials/wiki/src/views/.gitignore
index 1853d983c..c612e59f2 100644
--- a/docs/tutorials/wiki/src/views/.gitignore
+++ b/docs/tutorials/wiki/src/views/.gitignore
@@ -19,3 +19,4 @@ Data.fs*
.DS_Store
coverage
test
+*.sqlite
diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py
index 439bb7759..86c778bf2 100644
--- a/docs/tutorials/wiki/src/views/setup.py
+++ b/docs/tutorials/wiki/src/views/setup.py
@@ -9,6 +9,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
+ 'docutils',
'plaster_pastedeploy',
'pyramid',
'pyramid_chameleon',
@@ -19,7 +20,6 @@ requires = [
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'docutils',
]
tests_require = [
diff --git a/docs/tutorials/wiki/src/views/testing.ini b/docs/tutorials/wiki/src/views/testing.ini
new file mode 100644
index 000000000..9298354ac
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/testing.ini
@@ -0,0 +1,60 @@
+###
+# app configuration
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+
+zodbconn.uri = file://%(here)s/Data.testing.fs?connection_cache_size=20000
+
+retry.attempts = 3
+
+[pshell]
+setup = tutorial.pshell.setup
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+listen = localhost:6543
+
+###
+# logging configuration
+# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/views/tests/conftest.py b/docs/tutorials/wiki/src/views/tests/conftest.py
new file mode 100644
index 000000000..12e75d8e9
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tests/conftest.py
@@ -0,0 +1,69 @@
+import os
+from pyramid.paster import get_appsettings
+from pyramid.scripting import prepare
+from pyramid.testing import DummyRequest
+import pytest
+import webtest
+
+from tutorial import main
+
+
+def pytest_addoption(parser):
+ parser.addoption('--ini', action='store', metavar='INI_FILE')
+
+@pytest.fixture(scope='session')
+def ini_file(request):
+ # potentially grab this path from a pytest option
+ return os.path.abspath(request.config.option.ini or 'testing.ini')
+
+@pytest.fixture(scope='session')
+def app_settings(ini_file):
+ return get_appsettings(ini_file)
+
+@pytest.fixture(scope='session')
+def app(app_settings):
+ return main({}, **app_settings)
+
+@pytest.fixture
+def testapp(app):
+ testapp = webtest.TestApp(app, extra_environ={
+ 'HTTP_HOST': 'example.com',
+ })
+
+ return testapp
+
+@pytest.fixture
+def app_request(app):
+ """
+ A real request.
+
+ This request is almost identical to a real request but it has some
+ drawbacks in tests as it's harder to mock data and is heavier.
+
+ """
+ env = prepare(registry=app.registry)
+ request = env['request']
+ request.host = 'example.com'
+
+ yield request
+ env['closer']()
+
+@pytest.fixture
+def dummy_request(app):
+ """
+ A lightweight dummy request.
+
+ This request is ultra-lightweight and should be used only when the
+ request itself is not a large focus in the call-stack.
+
+ It is way easier to mock and control side-effects using this object.
+
+ - It does not have request extensions applied.
+ - Threadlocals are not properly pushed.
+
+ """
+ request = DummyRequest()
+ request.registry = app.registry
+ request.host = 'example.com'
+
+ return request
diff --git a/docs/tutorials/wiki/src/views/tests/test_functional.py b/docs/tutorials/wiki/src/views/tests/test_functional.py
new file mode 100644
index 000000000..bac5d63f4
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tests/test_functional.py
@@ -0,0 +1,7 @@
+def test_root(testapp):
+ res = testapp.get('/', status=200)
+ assert b'Pyramid' in res.body
+
+def test_notfound(testapp):
+ res = testapp.get('/badurl', status=404)
+ assert res.status_code == 404
diff --git a/docs/tutorials/wiki/src/views/tests/test_it.py b/docs/tutorials/wiki/src/views/tests/test_it.py
deleted file mode 100644
index 6c72bcc62..000000000
--- a/docs/tutorials/wiki/src/views/tests/test_it.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import unittest
-
-from pyramid import testing
-
-
-class ViewTests(unittest.TestCase):
- def setUp(self):
- self.config = testing.setUp()
-
- def tearDown(self):
- testing.tearDown()
-
- def test_my_view(self):
- from tutorial.views.default import my_view
- request = testing.DummyRequest()
- info = my_view(request)
- self.assertEqual(info['project'], 'myproj')
-
- def test_notfound_view(self):
- from tutorial.views.notfound import notfound_view
- request = testing.DummyRequest()
- info = notfound_view(request)
- self.assertEqual(info, {})
-
diff --git a/docs/tutorials/wiki/src/views/tests/test_views.py b/docs/tutorials/wiki/src/views/tests/test_views.py
new file mode 100644
index 000000000..2b4201955
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tests/test_views.py
@@ -0,0 +1,13 @@
+from tutorial.views.default import my_view
+from tutorial.views.notfound import notfound_view
+
+
+def test_my_view(app_request):
+ info = my_view(app_request)
+ assert app_request.response.status_int == 200
+ assert info['project'] == 'myproj'
+
+def test_notfound_view(app_request):
+ info = notfound_view(app_request)
+ assert app_request.response.status_int == 404
+ assert info == {}
diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py
index 830a607f3..e40451339 100644
--- a/docs/tutorials/wiki/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py
@@ -1,5 +1,6 @@
from pyramid.config import Configurator
from pyramid_zodbconn import get_connection
+
from .models import appmaker
@@ -12,11 +13,11 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
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.include('pyramid_chameleon')
config.include('.routes')
+ config.set_root_factory(root_factory)
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt
index 06a3c8157..1e8b808d4 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt
@@ -8,8 +8,7 @@
<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>
+ <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">
@@ -42,6 +41,15 @@
</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>
diff --git a/docs/tutorials/wiki/src/views/tutorial/views/default.py b/docs/tutorials/wiki/src/views/tutorial/views/default.py
index e7921cf2f..7ea54bf51 100644
--- a/docs/tutorials/wiki/src/views/tutorial/views/default.py
+++ b/docs/tutorials/wiki/src/views/tutorial/views/default.py
@@ -1,18 +1,17 @@
from docutils.core import publish_parts
-import re
-
-from pyramid.httpexceptions import HTTPFound
+from pyramid.httpexceptions import HTTPSeeOther
from pyramid.view import view_config
+import re
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')
def view_wiki(context, request):
- return HTTPFound(location=request.resource_url(context, 'FrontPage'))
+ return HTTPSeeOther(location=request.resource_url(context, 'FrontPage'))
@view_config(context='..models.Page', renderer='tutorial:templates/view.pt')
@@ -45,7 +44,7 @@ def add_page(context, request):
page.__name__ = pagename
page.__parent__ = context
context[pagename] = page
- return HTTPFound(location=request.resource_url(page))
+ return HTTPSeeOther(location=request.resource_url(page))
save_url = request.resource_url(context, 'add_page', pagename)
page = Page('')
page.__name__ = pagename
@@ -58,7 +57,7 @@ def add_page(context, request):
def edit_page(context, request):
if 'form.submitted' in request.params:
context.data = request.params['body']
- return HTTPFound(location=request.resource_url(context))
+ return HTTPSeeOther(location=request.resource_url(context))
return dict(page=context,
save_url=request.resource_url(context, 'edit_page'))
diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst
index b144fc4e0..9defef31a 100644
--- a/docs/tutorials/wiki2/installation.rst
+++ b/docs/tutorials/wiki2/installation.rst
@@ -315,8 +315,8 @@ For a successful test run, you should see output that ends like this:
.. code-block:: bash
- ..
- 2 passed in 0.44 seconds
+ .....
+ 5 passed in 0.44 seconds
Expose test coverage information