summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2015-07-29 14:37:49 -0400
committerRyan Petrello <lists@ryanpetrello.com>2015-07-29 14:40:05 -0400
commitb49e8c7be413512866390e0a03ee18802ad3d046 (patch)
tree79b6c5c545c49e86dffa41e381e148bb06ce2de0
parentcbcc8b3e511f8366aabb04b50ce5c482cfcbb6f3 (diff)
downloadpecan-b49e8c7be413512866390e0a03ee18802ad3d046.tar.gz
Pecan has moved to https://github.com/pecan/pecan
Change-Id: I1ee039e0e84cd4c32d49c1a0b858fbc247e75054
-rw-r--r--.coveragerc2
-rw-r--r--.gitignore22
-rw-r--r--.gitreview4
-rw-r--r--AUTHORS16
-rw-r--r--CONTRIBUTING.rst43
-rw-r--r--LICENSE24
-rw-r--r--MANIFEST.in7
-rw-r--r--README.rst57
-rw-r--r--bin/pecan5
-rw-r--r--docs/Makefile130
-rw-r--r--docs/source/changes.rst228
-rw-r--r--docs/source/commands.rst253
-rw-r--r--docs/source/conf.py228
-rw-r--r--docs/source/configuration.rst211
-rw-r--r--docs/source/contextlocals.rst55
-rw-r--r--docs/source/databases.rst191
-rw-r--r--docs/source/deployment.rst272
-rw-r--r--docs/source/development.rst60
-rw-r--r--docs/source/errors.rst119
-rw-r--r--docs/source/forms.rst92
-rw-r--r--docs/source/hooks.rst409
-rw-r--r--docs/source/index.rst127
-rw-r--r--docs/source/installation.rst50
-rw-r--r--docs/source/jsonify.rst70
-rw-r--r--docs/source/logging.rst157
-rw-r--r--docs/source/pecan_commands.rst26
-rw-r--r--docs/source/pecan_configuration.rst11
-rw-r--r--docs/source/pecan_core.rst12
-rw-r--r--docs/source/pecan_decorators.rst11
-rw-r--r--docs/source/pecan_deploy.rst11
-rw-r--r--docs/source/pecan_hooks.rst12
-rw-r--r--docs/source/pecan_jsonify.rst11
-rw-r--r--docs/source/pecan_middleware_debug.rst9
-rw-r--r--docs/source/pecan_rest.rst11
-rw-r--r--docs/source/pecan_routing.rst11
-rw-r--r--docs/source/pecan_secure.rst19
-rw-r--r--docs/source/pecan_templating.rst12
-rw-r--r--docs/source/pecan_testing.rst11
-rw-r--r--docs/source/pecan_util.rst10
-rw-r--r--docs/source/quick_start.rst297
-rw-r--r--docs/source/reload.rst28
-rw-r--r--docs/source/rest.rst222
-rw-r--r--docs/source/routing.rst597
-rw-r--r--docs/source/secure_controller.rst263
-rw-r--r--docs/source/sessions.rst42
-rw-r--r--docs/source/simple_ajax.rst286
-rw-r--r--docs/source/simple_forms_processing.rst195
-rw-r--r--docs/source/static0
-rw-r--r--docs/source/templates.rst143
-rw-r--r--docs/source/testing.rst137
-rw-r--r--pecan/__init__.py134
-rw-r--r--pecan/commands/__init__.py4
-rw-r--r--pecan/commands/base.py166
-rw-r--r--pecan/commands/create.py60
-rw-r--r--pecan/commands/serve.py223
-rw-r--r--pecan/commands/shell.py177
-rw-r--r--pecan/compat/__init__.py20
-rw-r--r--pecan/configuration.py254
-rw-r--r--pecan/core.py854
-rw-r--r--pecan/decorators.py175
-rw-r--r--pecan/deploy.py9
-rw-r--r--pecan/ext/__init__.py6
-rw-r--r--pecan/extensions.py83
-rw-r--r--pecan/hooks.py375
-rw-r--r--pecan/jsonify.py137
-rw-r--r--pecan/log.py54
-rw-r--r--pecan/middleware/__init__.py3
-rw-r--r--pecan/middleware/debug.py96
-rw-r--r--pecan/middleware/errordocument.py76
-rw-r--r--pecan/middleware/recursive.py184
-rw-r--r--pecan/middleware/static.py166
-rw-r--r--pecan/rest.py412
-rw-r--r--pecan/routing.py325
-rw-r--r--pecan/scaffolds/__init__.py142
-rw-r--r--pecan/scaffolds/base/+package+/__init__.py0
-rw-r--r--pecan/scaffolds/base/+package+/app.py_tmpl14
-rw-r--r--pecan/scaffolds/base/+package+/controllers/__init__.py0
-rw-r--r--pecan/scaffolds/base/+package+/controllers/root.py22
-rw-r--r--pecan/scaffolds/base/+package+/model/__init__.py15
-rw-r--r--pecan/scaffolds/base/+package+/templates/error.html12
-rw-r--r--pecan/scaffolds/base/+package+/templates/index.html34
-rw-r--r--pecan/scaffolds/base/+package+/templates/layout.html22
-rw-r--r--pecan/scaffolds/base/+package+/tests/__init__.py_tmpl22
-rw-r--r--pecan/scaffolds/base/+package+/tests/config.py_tmpl25
-rw-r--r--pecan/scaffolds/base/+package+/tests/test_functional.py_tmpl22
-rw-r--r--pecan/scaffolds/base/+package+/tests/test_units.py7
-rw-r--r--pecan/scaffolds/base/MANIFEST.in1
-rw-r--r--pecan/scaffolds/base/config.py_tmpl54
-rw-r--r--pecan/scaffolds/base/public/css/style.css43
-rw-r--r--pecan/scaffolds/base/public/images/logo.pngbin20596 -> 0 bytes
-rw-r--r--pecan/scaffolds/base/setup.cfg_tmpl6
-rw-r--r--pecan/scaffolds/base/setup.py_tmpl22
-rw-r--r--pecan/scaffolds/rest-api/+package+/__init__.py0
-rw-r--r--pecan/scaffolds/rest-api/+package+/app.py_tmpl16
-rw-r--r--pecan/scaffolds/rest-api/+package+/controllers/__init__.py0
-rw-r--r--pecan/scaffolds/rest-api/+package+/controllers/root.py53
-rw-r--r--pecan/scaffolds/rest-api/+package+/errors.py18
-rw-r--r--pecan/scaffolds/rest-api/+package+/model/__init__.py15
-rw-r--r--pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl22
-rw-r--r--pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl19
-rw-r--r--pecan/scaffolds/rest-api/+package+/tests/test_functional.py_tmpl37
-rw-r--r--pecan/scaffolds/rest-api/+package+/tests/test_units.py7
-rw-r--r--pecan/scaffolds/rest-api/config.py_tmpl48
-rw-r--r--pecan/scaffolds/rest-api/setup.cfg_tmpl6
-rw-r--r--pecan/scaffolds/rest-api/setup.py_tmpl22
-rw-r--r--pecan/secure.py232
-rw-r--r--pecan/templating.py286
-rw-r--r--pecan/testing.py35
-rw-r--r--pecan/tests/__init__.py20
-rw-r--r--pecan/tests/config_fixtures/bad/importerror.py1
-rw-r--r--pecan/tests/config_fixtures/bad/module_and_underscore.py4
-rw-r--r--pecan/tests/config_fixtures/config.py22
-rw-r--r--pecan/tests/config_fixtures/empty.py2
-rw-r--r--pecan/tests/config_fixtures/foobar.py1
-rw-r--r--pecan/tests/config_fixtures/forcedict.py14
-rw-r--r--pecan/tests/middleware/__init__.py0
-rw-r--r--pecan/tests/middleware/static_fixtures/self.pngbin6976 -> 0 bytes
-rw-r--r--pecan/tests/middleware/static_fixtures/text.txt9
-rw-r--r--pecan/tests/middleware/test_errordocument.py92
-rw-r--r--pecan/tests/middleware/test_recursive.py142
-rw-r--r--pecan/tests/middleware/test_static.py68
-rw-r--r--pecan/tests/scaffold_builder.py172
-rw-r--r--pecan/tests/scaffold_fixtures/__init__.py0
-rw-r--r--pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl1
-rw-r--r--pecan/tests/scaffold_fixtures/content_sub/foo_tmpl1
-rw-r--r--pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt1
-rw-r--r--pecan/tests/scaffold_fixtures/file_sub/foo_+package+1
-rw-r--r--pecan/tests/scaffold_fixtures/simple/bar/spam.txt1
-rw-r--r--pecan/tests/scaffold_fixtures/simple/foo1
-rw-r--r--pecan/tests/templates/__init__.py0
-rw-r--r--pecan/tests/templates/form_colors.html2
-rw-r--r--pecan/tests/templates/form_colors_invalid.html2
-rw-r--r--pecan/tests/templates/form_colors_valid.html2
-rw-r--r--pecan/tests/templates/form_login_invalid.html2
-rw-r--r--pecan/tests/templates/form_login_valid.html2
-rw-r--r--pecan/tests/templates/form_name.html1
-rw-r--r--pecan/tests/templates/form_name_invalid.html3
-rw-r--r--pecan/tests/templates/form_name_invalid_custom.html3
-rw-r--r--pecan/tests/templates/form_name_valid.html1
-rw-r--r--pecan/tests/templates/genshi.html16
-rw-r--r--pecan/tests/templates/genshi_bad.html18
-rw-r--r--pecan/tests/templates/jinja.html11
-rw-r--r--pecan/tests/templates/jinja_bad.html13
-rw-r--r--pecan/tests/templates/kajiki.html11
-rw-r--r--pecan/tests/templates/mako.html11
-rw-r--r--pecan/tests/templates/mako_bad.html6
-rw-r--r--pecan/tests/test_base.py2227
-rw-r--r--pecan/tests/test_commands.py56
-rw-r--r--pecan/tests/test_conf.py346
-rw-r--r--pecan/tests/test_generic.py111
-rw-r--r--pecan/tests/test_hooks.py1711
-rw-r--r--pecan/tests/test_jsonify.py233
-rw-r--r--pecan/tests/test_no_thread_locals.py1440
-rw-r--r--pecan/tests/test_rest.py1640
-rw-r--r--pecan/tests/test_scaffolds.py160
-rw-r--r--pecan/tests/test_secure.py563
-rw-r--r--pecan/tests/test_templating.py50
-rw-r--r--pecan/tests/test_util.py95
-rw-r--r--pecan/util.py53
-rw-r--r--requirements.txt5
-rw-r--r--setup.cfg9
-rw-r--r--setup.py122
-rw-r--r--tox.ini229
163 files changed, 1 insertions, 20197 deletions
diff --git a/.coveragerc b/.coveragerc
deleted file mode 100644
index d1df824..0000000
--- a/.coveragerc
+++ /dev/null
@@ -1,2 +0,0 @@
-[run]
-source=pecan
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index df8ab61..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,22 +0,0 @@
-# From GitHub's .gitignore template collection
-
-*.py[co]
-
-# Packages
-*.egg
-*.egg-info
-dist
-build
-eggs
-
-develop-eggs
-
-# Installer logs
-pip-log.txt
-
-# Unit test / coverage reports
-.coverage
-.tox
-htmlcov
-
-.DS_Store
diff --git a/.gitreview b/.gitreview
deleted file mode 100644
index 48e5fd8..0000000
--- a/.gitreview
+++ /dev/null
@@ -1,4 +0,0 @@
-[gerrit]
-host=review.openstack.org
-port=29418
-project=stackforge/pecan.git
diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644
index 3e8f667..0000000
--- a/AUTHORS
+++ /dev/null
@@ -1,16 +0,0 @@
-Pecan is written by various contributors (by date of contribution):
-
-Jonathan LaCour
-Alfredo Deza
-Mark McClain
-Ryan Petrello
-Yoann Roman
-John Anderson
-Jeremy Jones
-Benjamin W. Smith
-Pete Chudykowski
-Mike Perez
-Justin Barber
-Wesley Spikes
-Steven Berler
-Chad Lung
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
deleted file mode 100644
index 733d3c8..0000000
--- a/CONTRIBUTING.rst
+++ /dev/null
@@ -1,43 +0,0 @@
-Contributing to Pecan
----------------------
-Pecan uses the Gerrit code review system for bug fixes and feature additions.
-
-**Pull requests submitted through GitHub will be ignored.**
-
-To contribute:
-
- * Visit `review.openstack.org <http://review.openstack.org>`_ and click the
- *Sign In* link at the top-right corner of the page. Log in with your
- Launchpad ID (or register a new account).
- * ``$ git clone`` pecan locally and create a `topic branch
- <http://git-scm.com/book/ch3-4.html#Topic-Branches>`_ to hold your work.
- The general convention when working on bugs is to name the branch
- ``bug/BUG-NUMBER`` (e.g., ``bug/1234567``). Otherwise, give it
- a meaningful name because it will show up as the topic for your change in
- Gerrit.
- * Commit your work and submit a review (``$ git review``)
-
-::
-
- $ git clone https://github.com/stackforge/pecan.git && cd pecan
- $ git checkout -b bug/1234
- $ pip install git-review && git review -s
- # Make changes
- $ pip install tox && tox
- $ git add .
- $ git commit -a
- $ git review
-
-All contributions must:
-
- * Include accompanying tests.
- * Include narrative and API documentation if new features are added.
- * Be (generally) compliant with `PEP8
- <http://www.python.org/dev/peps/pep-0008/>`_.
- * Not break the test or build. Before submitting a review, ``$ pip
- install tox && tox`` from your source to ensure that all tests still pass
- across multiple versions of Python.
-
-Bugs should be filed on Launchpad, not GitHub:
-
-https://bugs.launchpad.net/pecan
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index c47247d..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,24 +0,0 @@
-Copyright (c) <2011>, Jonathan LaCour
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of the <organization> nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index bcffd6c..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,7 +0,0 @@
-recursive-include pecan/scaffolds/base *
-include pecan/scaffolds/base/*
-recursive-include pecan/scaffolds/rest-api *
-include pecan/scaffolds/rest-api/*
-include pecan/middleware/resources/*
-include LICENSE README.rst requirements.txt
-recursive-include pecan/tests *
diff --git a/README.rst b/README.rst
index 60a38ea..0669a67 100644
--- a/README.rst
+++ b/README.rst
@@ -1,59 +1,4 @@
-Pecan
-=====
-
A WSGI object-dispatching web framework, designed to be lean and fast with few
dependencies.
-.. image:: https://badge.fury.io/py/pecan.png
- :target: https://pypi.python.org/pypi/pecan/
- :alt: Latest PyPI version
-
-Installing
-----------
-
-::
-
- $ pip install pecan
-
-...or, for the latest (unstable) tip::
-
- $ git clone https://github.com/stackforge/pecan.git
- $ cd pecan && python setup.py install
-
-Running Tests
--------------
-
-::
-
- $ python setup.py test
-
-...or, to run all tests across all supported environments::
-
- $ pip install tox && tox
-
-Viewing Documentation
----------------------
-`Available online <http://pecan.readthedocs.org>`_, or to build manually::
-
- $ cd docs && make html
- $ open docs/build/html/index.html
-
-...or::
-
- $ cd docs && make man
- $ man docs/build/man/pecan.1
-
-Contributing
-------------
-For information on contributing to Pecan, please read our `Contributing
-Guidelines <https://github.com/stackforge/pecan/blob/master/CONTRIBUTING.rst>`_.
-
-Bugs should be filed on Launchpad, not GitHub:
-
-https://bugs.launchpad.net/pecan
-
-Additional Help/Support
------------------------
-Most Pecan interaction is done via the `pecan-dev Mailing List
-<https://groups.google.com/forum/#!forum/pecan-dev>`_ and the #pecanpy channel
-on `FreeNode <http://freenode.net/>`_ IRC.
+The Pecan project has moved to: https://github.com/pecan/pecan
diff --git a/bin/pecan b/bin/pecan
deleted file mode 100644
index 9715ea0..0000000
--- a/bin/pecan
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env python
-
-if __name__ == '__main__':
- from pecan.commands import CommandRunner
- CommandRunner.handle_command_line()
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index 0d6c079..0000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,130 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-BUILDDIR = build
-
-# Internal variables.
-PAPEROPT_a4 = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
-
-help:
- @echo "Please use \`make <target>' where <target> is one of"
- @echo " html to make standalone HTML files"
- @echo " dirhtml to make HTML files named index.html in directories"
- @echo " singlehtml to make a single large HTML file"
- @echo " pickle to make pickle files"
- @echo " json to make JSON files"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " qthelp to make HTML files and a qthelp project"
- @echo " devhelp to make HTML files and a Devhelp project"
- @echo " epub to make an epub"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " latexpdf to make LaTeX files and run them through pdflatex"
- @echo " text to make text files"
- @echo " man to make manual pages"
- @echo " changes to make an overview of all changed/added/deprecated items"
- @echo " linkcheck to check all external links for integrity"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
-
-clean:
- -rm -rf $(BUILDDIR)/*
-
-html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
- $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
- @echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
- @echo
- @echo "Build finished; now you can process the pickle files."
-
-json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
- @echo
- @echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Pecan.qhcp"
- @echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Pecan.qhc"
-
-devhelp:
- $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
- @echo
- @echo "Build finished."
- @echo "To view the help file:"
- @echo "# mkdir -p $$HOME/.local/share/devhelp/Pecan"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Pecan"
- @echo "# devhelp"
-
-epub:
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
- @echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make' in that directory to run these through (pdf)latex" \
- "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through pdflatex..."
- make -C $(BUILDDIR)/latex all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
- @echo
- @echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
- @echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-changes:
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
- @echo
- @echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/source/changes.rst b/docs/source/changes.rst
deleted file mode 100644
index ce3a3aa..0000000
--- a/docs/source/changes.rst
+++ /dev/null
@@ -1,228 +0,0 @@
-0.9.0
-=====
-* Support for Python 3.2 has been dropped.
-* Added a new feature which allows users to specify custom path segments for
- controllers. This is especially useful for path segments that are not
- valid Python identifiers (such as path segments that include certain
- punctuation characters, like `/some/~path~/`).
-* Added a new configuration option, `app.debugger`, which allows developers to
- specify an alternative debugger to `pdb` (e.g., `ipdb`) when performing
- interactive debugging with pecan's `DebugMiddleware`.
-* Changed new quickstart pecan projects to default the `pecan` log level to
- `DEBUG` for development.
-* Fixed a bug that prevented `staticmethods` from being used as controllers.
-* Fixed a decoding bug in the way pecan handles certain quoted URL path
- segments and query strings.
-* Fixed several bugs in the way pecan handles Unicode path segments (for
- example, now you can define pecan routes that contain emoji characters).
-* Fixed several bugs in RestController that caused it to return `HTTP 404 Not
- Found` rather than `HTTP 405 Method Not Allowed`. Additionally,
- RestController now returns valid `Allow` headers when `HTTP 405 Method Not
- Allowed` is returned.
-* Fixed a bug which allowed special pecan methods (`_route`, `_lookup`,
- `_default`) to be marked as generic REST methods.
-* Added more emphasis in pecan's documentation to the need for `debug=False` in
- production deployments.
-
-0.8.3
-=====
-* Changed pecan to more gracefully handle a few odd request encoding edge
- cases. Now pecan applications respond with an HTTP 400 (rather than an
- uncaught UnicodeDecodeError, resulting in an HTTP 500) when:
- - HTTP POST requests are composed of non-Unicode data
- - Request paths contain invalid percent-encoded characters, e.g.,
- ``/some/path/%aa/``
-* Improved verbosity for import-related errors in pecan configuration files,
- especially those involving relative imports.
-
-0.8.2
-=====
-* Fixes a bug that breaks support for multi-value query string variables (e.g.,
- `?check=a&check=b`).
-
-0.8.1
-=====
-* Improved detection of infinite recursion for PecanHook and pypy. This fixes
- a bug discovered in pecan + pypy that could result in infinite recursion when
- using the PecanHook metaclass.
-* Fixed a bug that prevented @exposed controllers from using @staticmethod.
-* Fixed a minor bug in the controller argument calculation.
-
-0.8.0
-=====
- * For HTTP POSTs, map JSON request bodies to controller keyword arguments.
- * Improved argspec detection and leniency for wrapped controllers.
- * When path arguments are incorrect for RestController, return HTTP 404, not 400.
- * When detecting non-content for HTTP 204, properly catch UnicodeDecodeError.
- * Fixed a routing bug for generic subcontrollers.
- * Fixed a bug in generic function handling when context locals are disabled.
- * Fixed a bug that mixes up argument order for generic functions.
- * Removed `assert` for flow control; it can be optimized away with `python -O`.
-
-0.7.0
-=====
-* Fixed an edge case in RestController routing which should have returned an
- HTTP 400 but was instead raising an exception (and thus, HTTP 500).
-* Fixed an incorrect root logger configuration for quickstarted pecan projects.
-* Added `pecan.state.arguments`, a new feature for inspecting controller call
- arguments.
-* Fixed an infinite recursion error in PecanHook application. Subclassing both
- `rest.RestController` and `hooks.HookController` resulted in an infinite
- recursion error in hook application (which prevented applications from
- starting).
-* Pecan's tests are now included in its source distribution.
-
-0.6.1
-=====
-* Fixed a bug which causes pecan to mistakenly return HTTP 204 for non-empty
- response bodies.
-
-0.6.0
-=====
-* Added support for disabling the `pecan.request` and `pecan.response`
- threadlocals at the WSGI application level in favor of explicit reference
- passing. For more information, see :ref:`contextlocals`.
-* Added better support for hook composition via subclassing and mixins. For
- more information, see :ref:`attaching_hooks`.
-* Added support for specifying custom request and response implementations at
- the WSGI application level for people who want to extend the functionality
- provided by the base classes in `webob`.
-* Pecan controllers may now return an explicit `webob.Response` instance to
- short-circuit Pecan's template rendering and serialization.
-* For generic methods that return HTTP 405, pecan now generates an `Allow`
- header to communicate acceptable methods to the client.
-* Fixed a bug in adherence to RFC2616: if an exposed method returns no response
- body (or namespace), pecan will now enforce an HTTP 204 response (instead of
- HTTP 200).
-* Fixed a bug in adherence to RFC2616: when pecan responds with HTTP 204 or
- HTTP 304, the `Content-Type` header is automatically stripped (because these
- types of HTTP responses do not contain body content).
-* Fixed a bug: now when clients request JSON via an `Accept` header, `webob`
- HTTP exceptions are serialized as JSON, not their native HTML representation.
-* Fixed a bug that broke applications which specified `default_renderer
- = json`.
-
-0.5.0
-=====
-* This release adds formal support for pypy.
-* Added colored request logging to the `pecan serve` command.
-* Added a scaffold for easily generating a basic REST API.
-* Added the ability to pass arbitrary keyword arguments to
- `pecan.testing.load_test_app`.
-* Fixed a recursion-related bug in the error document middleware.
-* Fixed a bug in the `gunicorn_pecan` command that caused `threading.local`
- data to leak between eventlet/gevent green threads.
-* Improved documentation through fixes and narrative tutorials for sample pecan
- applications.
-
-0.4.5
-=====
-* Fixed a trailing slash bug for `RestController`s that have a `_lookup` method.
-* Cleaned up the WSGI app reference from the threadlocal state on every request
- (to avoid potential memory leaks, especially when testing).
-* Improved pecan documentation and corrected intersphinx references.
-* pecan supports Python 3.4.
-
-0.4.4
-=====
-* Removed memoization of certain controller attributes, which can lead to
- a memory leak in dynamic controller lookups.
-
-0.4.3
-=====
-* Fixed several bugs for RestController.
-* Fixed a bug in security handling for generic controllers.
-* Resolved a bug in `_default` handlers used in `RestController`.
-* Persist `pecan.request.context` across internal redirects.
-
-0.4.2
-=====
-* Remove a routing optimization that breaks the WSME pecan plugin.
-
-0.4.1
-=====
-* Moved the project to `StackForge infrastructure
- <http://docs.openstack.org/infra/system-config/stackforge.html>`_, including Gerrit code review,
- Jenkins continuous integration, and GitHub mirroring.
-* Added a pecan plugin for the popular `uwsgi server
- <http://uwsgi-docs.readthedocs.org>`_.
-* Replaced the ``simplegeneric`` dependency with the new
- ``functools.singledispatch`` function in preparation for Python 3.4 support.
-* Optimized pecan's core dispatch routing for notably faster response times.
-
-0.3.2
-=====
-* Made some changes to simplify how ``pecan.conf.app`` is passed to new apps.
-* Fixed a routing bug for certain ``_lookup`` controller configurations.
-* Improved documentation for handling file uploads.
-* Deprecated the ``pecan.conf.requestviewer`` configuration option.
-
-0.3.1
-=====
-* ``on_error`` hooks can now return a Pecan Response objects.
-* Minor documentation and release tooling updates.
-
-0.3.0
-=====
-* Pecan now supports Python 2.6, 2.7, 3.2, and 3.3.
-
-0.2.4
-=====
-* Add support for ``_lookup`` methods as a fallback in RestController.
-* A variety of improvements to project documentation.
-
-0.2.3
-=====
-* Add a variety of optimizations to ``pecan.core`` that improve request
- handling time by approximately 30% for simple object dispatch routing.
-* Store exceptions raised by ``abort`` in the WSGI environ so they can be
- accessed later in the request handling (e.g., by other middleware or pecan
- hooks).
-* Make TransactionHook more robust so that it isn't as susceptible to failure
- when exceptions occur in *other* pecan hooks within a request.
-* Rearrange quickstart verbiage so users don't miss a necessary step.
-
-0.2.2
-=====
-* Unobfuscate syntax highlighting JavaScript for debian packaging.
-* Extract the scaffold-building tests into tox.
-* Add support for specifying a pecan configuration file via the
- ``PECAN_CONFIG``
- environment variable.
-* Fix a bug in ``DELETE`` methods in two (or more) nested ``RestControllers``.
-* Add documentation for returning specific HTTP status codes.
-
-0.2.1
-=====
-
-* Include a license, readme, and ``requirements.txt`` in distributions.
-* Improve inspection with ``dir()`` for ``pecan.request`` and ``pecan.response``
-* Fix a bug which prevented pecan applications from being mounted at WSGI
- virtual paths.
-
-0.2.0
-=====
-
-* Update base project scaffolding tests to be more repeatable.
-* Add an application-level configuration option to disable content-type guessing by URL
-* Fix the wrong test dependency on Jinja, it's Jinja2.
-* Fix a routing-related bug in ``RestController``. Fixes #156
-* Add an explicit ``CONTRIBUTING.rst`` document.
-* Improve visibility of deployment-related docs.
-* Add support for a ``gunicorn_pecan`` console script.
-* Remove and annotate a few unused (and py26 alternative) imports.
-* Bug fix: don't strip a dotted extension from the path unless it has a matching mimetype.
-* Add a test to the scaffold project buildout that ensures pep8 passes.
-* Fix misleading output for ``$ pecan --version``.
-
-0.2.0b
-======
-
-* Fix a bug in ``SecureController``. Resolves #131.
-* Extract debug middleware static file dependencies into physical files.
-* Improve a test that can fail due to a race condition.
-* Improve documentation about configation format and ``app.py``.
-* Add support for content type detection via HTTP Accept headers.
-* Correct source installation instructions in ``README``.
-* Fix an incorrect code example in the Hooks documentation.
-* docs: Fix minor typo in ``*args`` Routing example.
diff --git a/docs/source/commands.rst b/docs/source/commands.rst
deleted file mode 100644
index fd9836f..0000000
--- a/docs/source/commands.rst
+++ /dev/null
@@ -1,253 +0,0 @@
-.. _commands:
-
-Command Line Pecan
-==================
-
-Any Pecan application can be controlled and inspected from the command
-line using the built-in :command:`pecan` command. The usage examples
-of :command:`pecan` in this document are intended to be invoked from
-your project's root directory.
-
-Serving a Pecan App For Development
------------------------------------
-
-Pecan comes bundled with a lightweight WSGI development server based on
-Python's :py:mod:`wsgiref.simple_server` module.
-
-Serving your Pecan app is as simple as invoking the ``pecan serve`` command::
-
- $ pecan serve config.py
- Starting server in PID 000.
- serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
-
-and then visiting it in your browser.
-
-The server ``host`` and ``port`` in your configuration file can be changed as
-described in :ref:`server_configuration`.
-
-.. include:: reload.rst
- :start-after: #reload
-
-The Interactive Shell
----------------------
-
-Pecan applications also come with an interactive Python shell which can be used
-to execute expressions in an environment very similar to the one your
-application runs in. To invoke an interactive shell, use the ``pecan shell``
-command::
-
- $ pecan shell config.py
- Pecan Interactive Shell
- Python 2.7.1 (r271:86832, Jul 31 2011, 19:30:53)
- [GCC 4.2.1 (Based on Apple Inc. build 5658)
-
- The following objects are available:
- wsgiapp - This project's WSGI App instance
- conf - The current configuration
- app - webtest.TestApp wrapped around wsgiapp
-
- >>> conf
- Config({
- 'app': Config({
- 'root': 'myapp.controllers.root.RootController',
- 'modules': ['myapp'],
- 'static_root': '/Users/somebody/myapp/public',
- 'template_path': '/Users/somebody/myapp/project/templates',
- 'errors': {'404': '/error/404'},
- 'debug': True
- }),
- 'server': Config({
- 'host': '0.0.0.0',
- 'port': '8080'
- })
- })
- >>> app
- <webtest.app.TestApp object at 0x101a830>
- >>> app.get('/')
- <200 OK text/html body='<html>\n ...\n\n'/936>
-
-Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows).
-
-Using an Alternative Shell
-++++++++++++++++++++++++++
-
-``pecan shell`` has optional support for the `IPython <http://ipython.org/>`_
-and `bpython <http://bpython-interpreter.org/>`_ alternative shells, each of
-which can be specified with the ``--shell`` flag (or its abbreviated alias,
-``-s``), e.g.,
-
-::
-
- $ pecan shell --shell=ipython config.py
- $ pecan shell -s bpython config.py
-
-
-.. _env_config:
-
-Configuration from an environment variable
-------------------------------------------
-
-In all the examples shown, you will see that the :command:`pecan` commands
-accepted a file path to the configuration file. An alternative to this is to
-specify the configuration file in an environment variable (:envvar:`PECAN_CONFIG`).
-
-This is completely optional; if a file path is passed in explicitly, Pecan will
-honor that before looking for an environment variable.
-
-For example, to serve a Pecan application, a variable could be exported and
-subsequently be re-used when no path is passed in.
-
-::
-
- $ export PECAN_CONFIG=/path/to/app/config.py
- $ pecan serve
- Starting server in PID 000.
- serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
-
-Note that the path needs to reference a valid pecan configuration file,
-otherwise the command will error out with a message indicating that
-the path is invalid (for example, if a directory is passed in).
-
-If :envvar:`PECAN_CONFIG` is not set and no configuration is passed in, the command
-will error out because it will not be able to locate a configuration file.
-
-
-Extending ``pecan`` with Custom Commands
-----------------------------------------
-
-While the commands packaged with Pecan are useful, the real utility of its
-command line toolset lies in its extensibility. It's convenient to be able to
-write a Python script that can work "in a Pecan environment" with access to
-things like your application's parsed configuration file or a simulated
-instance of your application itself (like the one provided in the ``pecan
-shell`` command).
-
-Writing a Custom Pecan Command
-++++++++++++++++++++++++++++++
-
-As an example, let's create a command that can be used to issue a simulated
-HTTP GET to your application and print the result. Its invocation from the
-command line might look something like this::
-
- $ pecan wget config.py /path/to/some/resource
-
-Let's say you have a distribution with a package in it named ``myapp``, and
-that within this package is a ``wget.py`` module::
-
- # myapp/myapp/wget.py
- import pecan
- from webtest import TestApp
-
- class GetCommand(pecan.commands.BaseCommand):
- '''
- Issues a (simulated) HTTP GET and returns the request body.
- '''
-
- arguments = pecan.commands.BaseCommand.arguments + ({
- 'name': 'path',
- 'help': 'the URI path of the resource to request'
- },)
-
- def run(self, args):
- super(GetCommand, self).run(args)
- app = TestApp(self.load_app())
- print app.get(args.path).body
-
-Let's analyze this piece-by-piece.
-
-Overriding the ``run`` Method
-,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
-
-First, we're subclassing :class:`~pecan.commands.base.BaseCommand` and extending
-the :func:`~pecan.commands.base.BaseCommandParent.run` method to:
-
-* Load a Pecan application - :func:`~pecan.core.load_app`
-* Wrap it in a fake WGSI environment - :class:`~webtest.app.TestApp`
-* Issue an HTTP GET request against it - :meth:`~webtest.app.TestApp.get`
-
-Defining Custom Arguments
-,,,,,,,,,,,,,,,,,,,,,,,,,
-
-The :attr:`arguments` class attribute is used to define command line arguments
-specific to your custom command. You'll notice in this example that we're
-*adding* to the arguments list provided by :class:`~pecan.commands.base.BaseCommand`
-(which already provides an argument for the ``config_file``), rather
-than overriding it entirely.
-
-The format of the :attr:`arguments` class attribute is a :class:`tuple` of
-dictionaries, with each dictionary representing an argument definition in the
-same format accepted by Python's :py:mod:`argparse` module (more specifically,
-:meth:`~argparse.ArgumentParser.add_argument`). By providing a list of
-arguments in this format, the :command:`pecan` command can include your custom
-commands in the help and usage output it provides.
-
-::
-
- $ pecan -h
- usage: pecan [-h] command ...
-
- positional arguments:
- command
- wget Issues a (simulated) HTTP GET and returns the request body
- serve Open an interactive shell with the Pecan app loaded
- ...
-
- $ pecan wget -h
- usage: pecan wget [-h] config_file path
- $ pecan wget config.py /path/to/some/resource
-
-Additionally, you'll notice that the first line of the docstring from
-:class:`GetCommand` -- ``Issues a (simulated) HTTP GET and returns the
-request body`` -- is automatically used to describe the :command:`wget`
-command in the output for ``$ pecan -h``. Following this convention
-allows you to easily integrate a summary for your command into the
-Pecan command line tool.
-
-Registering a Custom Command
-++++++++++++++++++++++++++++
-
-Now that you've written your custom command, you’ll need to tell your
-distribution’s ``setup.py`` about its existence and reinstall. Within your
-distribution’s ``setup.py`` file, you'll find a call to :func:`~setuptools.setup`.
-
-::
-
- # myapp/setup.py
- ...
- setup(
- name='myapp',
- version='0.1',
- author='Joe Somebody',
- ...
- )
-
-Assuming it doesn't exist already, we'll add the ``entry_points`` argument
-to the :func:`~setuptools.setup` call, and define a ``[pecan.command]`` definition for your custom
-command::
-
-
- # myapp/setup.py
- ...
- setup(
- name='myapp',
- version='0.1',
- author='Joe Somebody',
- ...
- entry_points="""
- [pecan.command]
- wget = myapp.wget:GetCommand
- """
- )
-
-Once you've done this, reinstall your project in development to register the
-new entry point.
-
-::
-
- $ python setup.py develop
-
-Then give it a try.
-
-::
-
- $ pecan wget config.py /path/to/some/resource
diff --git a/docs/source/conf.py b/docs/source/conf.py
deleted file mode 100644
index 9d71241..0000000
--- a/docs/source/conf.py
+++ /dev/null
@@ -1,228 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Pecan documentation build configuration file, created by
-# sphinx-quickstart on Sat Oct 9 14:41:27 2010.
-#
-# This file is execfile()d w/ the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import pkg_resources
-
-import sys
-import os
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.append(os.path.abspath('.'))
-
-# -- General configuration ----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
-
-intersphinx_mapping = {
- 'python': ('http://docs.python.org', None),
- 'webob': ('http://docs.webob.org/en/latest', None),
- 'webtest': ('http://webtest.readthedocs.org/en/latest/', None),
- 'beaker': ('http://beaker.readthedocs.org/en/latest/', None),
- 'paste': ('http://pythonpaste.org', None),
-}
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'Pecan'
-copyright = u'2010, Jonathan LaCour'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-dist = pkg_resources.get_distribution('pecan')
-version = release = dist.version
-# The full version, including alpha/beta/rc tags.
-#release = '0.3.0'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = []
-
-# The reST default role (used for this markup: `text`) to use for documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-
-# -- Options for HTML output --------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'nature'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = ''
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'Pecandoc'
-
-
-# -- Options for LaTeX output -------------------------------------------------
-
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
-
-# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual])
-latex_documents = [
- ('index', 'Pecan.tex', u'Pecan Documentation',
- u'Jonathan LaCour', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output -------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- ('index', 'pecan', u'Pecan Documentation',
- [u'Jonathan LaCour'], 1)
-]
diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst
deleted file mode 100644
index 47b0a43..0000000
--- a/docs/source/configuration.rst
+++ /dev/null
@@ -1,211 +0,0 @@
-.. _configuration:
-
-Configuring Pecan Applications
-==============================
-
-Pecan is very easy to configure. As long as you follow certain conventions,
-using, setting and dealing with configuration should be very intuitive.
-
-Pecan configuration files are pure Python. Each "section" of the
-configuration is a dictionary assigned to a variable name in the
-configuration module.
-
-Default Values
----------------
-
-Below is the complete list of default values the framework uses::
-
-
- server = {
- 'port' : '8080',
- 'host' : '0.0.0.0'
- }
-
- app = {
- 'root' : None,
- 'modules' : [],
- 'static_root' : 'public',
- 'template_path' : ''
- }
-
-
-
-.. _application_configuration:
-
-Application Configuration
--------------------------
-
-The ``app`` configuration values are used by Pecan to wrap your
-application into a valid `WSGI app
-<http://www.wsgi.org/en/latest/what.html>`_. The ``app`` configuration
-is specific to your application, and includes values like the root
-controller class location.
-
-A typical application configuration might look like this::
-
- app = {
- 'root' : 'project.controllers.root.RootController',
- 'modules' : ['project'],
- 'static_root' : '%(confdir)s/public',
- 'template_path' : '%(confdir)s/project/templates',
- 'debug' : True
- }
-
-Let's look at each value and what it means:
-
-**modules**
- A list of modules where pecan will search for applications.
- Generally this should contain a single item, the name of your
- project's python package. At least one of the listed modules must
- contain an ``app.setup_app`` function which is called to create the
- WSGI app. In other words, this package should be where your
- ``app.py`` file is located, and this file should contain a
- ``setup_app`` function.
-
-**root**
- The root controller of your application. Remember to provide a
- string representing a Python path to some callable (e.g.,
- ``"yourapp.controllers.root.RootController"``).
-
-**static_root**
- The directory where your static files can be found (relative to
- the project root). Pecan comes with middleware that can
- be used to serve static files (like CSS and Javascript files) during
- development.
-
-**template_path**
- Points to the directory where your template files live (relative to
- the project root).
-
-**debug**
- Enables the ability to display tracebacks in the browser and interactively
- debug during development.
-
-.. warning::
-
- ``app`` is a reserved variable name for that section of the
- configuration, so make sure you don't override it.
-
-.. warning::
-
- Make sure **debug** is *always* set to ``False`` in production environments.
-
-.. seealso::
-
- * :ref:`app_template`
-
-
-.. _server_configuration:
-
-Server Configuration
---------------------
-
-Pecan provides some sane defaults. Change these to alter the host and port your
-WSGI app is served on.
-
-::
-
- server = {
- 'port' : '8080',
- 'host' : '0.0.0.0'
- }
-
-Additional Configuration
-------------------------
-
-Your application may need access to other configuration values at
-runtime (like third-party API credentials). Put these settings in
-their own blocks in your configuration file.
-
-::
-
- twitter = {
- 'api_key' : 'FOO',
- 'api_secret' : 'SECRET'
- }
-
-.. _accessibility:
-
-Accessing Configuration at Runtime
-----------------------------------
-
-You can access any configuration value at runtime via :py:mod:`pecan.conf`.
-This includes custom, application, and server-specific values.
-
-For example, if you needed to specify a global administrator, you could
-do so like this within the configuration file.
-
-::
-
- administrator = 'foo_bar_user'
-
-And it would be accessible in :py:mod:`pecan.conf` as::
-
- >>> from pecan import conf
- >>> conf.administrator
- 'foo_bar_user'
-
-
-Dictionary Conversion
----------------------
-
-In certain situations you might want to deal with keys and values, but in strict
-dictionary form. The :class:`~pecan.configuration.Config` object has a helper
-method for this purpose that will return a dictionary representation of the
-configuration, including nested values.
-
-Below is a representation of how you can access the
-:meth:`~pecan.configuration.Config.to_dict` method and what it returns as
-a result (shortened for brevity):
-
-::
-
- >>> from pecan import conf
- >>> conf
- Config({'app': Config({'errors': {}, 'template_path': '', 'static_root': 'public', [...]
- >>> conf.to_dict()
- {'app': {'errors': {}, 'template_path': '', 'static_root': 'public', [...]
-
-
-Prefixing Dictionary Keys
--------------------------
-
-:func:`~pecan.configuration.Config.to_dict` allows you to pass an optional
-string argument if you need to prefix the keys in the returned dictionary.
-
-::
-
- >>> from pecan import conf
- >>> conf
- Config({'app': Config({'errors': {}, 'template_path': '', 'static_root': 'public', [...]
- >>> conf.to_dict('prefixed_')
- {'prefixed_app': {'prefixed_errors': {}, 'prefixed_template_path': '', 'prefixed_static_root': 'prefixed_public', [...]
-
-
-Dotted Keys, Non-Python Idenfitiers, and Native Dictionaries
-------------------------------------------------------------
-
-Sometimes you want to specify a configuration option that includes dotted keys
-or is not a valid Python idenfitier, such as ``()``. These situations are
-especially common when configuring Python logging. By passing a special key,
-``__force_dict__``, individual configuration blocks can be treated as native
-dictionaries.
-
-::
-
- logging = {
- 'root': {'level': 'INFO', 'handlers': ['console']},
- 'loggers': {
- 'sqlalchemy.engine': {'level': 'INFO', 'handlers': ['console']},
- '__force_dict__': True
- },
- 'formatters': {
- 'custom': {
- '()': 'my.package.customFormatter'
- }
- }
- }
-
- from myapp import conf
- assert isinstance(conf.logging.loggers, dict)
- assert isinstance(conf.logging.loggers['sqlalchemy.engine'], dict)
diff --git a/docs/source/contextlocals.rst b/docs/source/contextlocals.rst
deleted file mode 100644
index d97ef7e..0000000
--- a/docs/source/contextlocals.rst
+++ /dev/null
@@ -1,55 +0,0 @@
-.. _contextlocals:
-
-
-Context/Thread-Locals vs. Explicit Argument Passing
-===================================================
-In any pecan application, the module-level ``pecan.request`` and
-``pecan.response`` are proxy objects that always refer to the request and
-response being handled in the current thread.
-
-This `thread locality` ensures that you can safely access a global reference to
-the current request and response in a multi-threaded environment without
-constantly having to pass object references around in your code; it's a feature
-of pecan that makes writing traditional web applications easier and less
-verbose.
-
-Some people feel thread-locals are too implicit or magical, and that explicit
-reference passing is much clearer and more maintainable in the long run.
-Additionally, the default implementation provided by pecan uses
-:func:`threading.local` to associate these context-local proxy objects with the
-`thread identifier` of the current server thread. In asynchronous server
-models - where lots of tasks run for short amounts of time on
-a `single` shared thread - supporting this mechanism involves monkeypatching
-:func:`threading.local` to behave in a greenlet-local manner.
-
-Disabling Thread-Local Proxies
-------------------------------
-
-If you're certain that you `do not` want to utilize context/thread-locals in
-your project, you can do so by passing the argument
-``use_context_locals=False`` in your application's configuration file::
-
- app = {
- 'root': 'project.controllers.root.RootController',
- 'modules': ['project'],
- 'static_root': '%(confdir)s/public',
- 'template_path': '%(confdir)s/project/templates',
- 'debug': True,
- 'use_context_locals': False
- }
-
-Additionally, you'll need to update **all** of your pecan controllers to accept
-positional arguments for the current request and response::
-
- class RootController(object):
-
- @pecan.expose('json')
- def index(self, req, resp):
- return dict(method=req.method) # path: /
-
- @pecan.expose()
- def greet(self, req, resp, name):
- return name # path: /greet/joe
-
-It is *imperative* that the request and response arguments come **after**
-``self`` and before any positional form arguments.
diff --git a/docs/source/databases.rst b/docs/source/databases.rst
deleted file mode 100644
index 4eb5a9b..0000000
--- a/docs/source/databases.rst
+++ /dev/null
@@ -1,191 +0,0 @@
-.. _databases:
-
-Working with Databases, Transactions, and ORM's
-===============================================
-
-Pecan provides no opinionated support for working with databases, but
-it's easy to hook into your ORM of choice. This article details best
-practices for integrating the popular Python ORM, SQLAlchemy_, into
-your Pecan project.
-
-.. _SQLAlchemy: http://sqlalchemy.org
-
-``init_model`` and Preparing Your Model
----------------------------------------
-
-Pecan's default quickstart project includes an empty stub directory
-for implementing your model as you see fit.
-
-::
-
- .
- └── test_project
- ├── app.py
- ├── __init__.py
- ├── controllers
- ├── model
- │   ├── __init__.py
- └── templates
-
-By default, this module contains a special method, :func:`init_model`.
-
-::
-
- from pecan import conf
-
- def init_model():
- """
- This is a stub method which is called at application startup time.
-
- If you need to bind to a parsed database configuration, set up tables
- or ORM classes, or perform any database initialization, this is the
- recommended place to do it.
-
- For more information working with databases, and some common recipes,
- see http://pecan.readthedocs.org/en/latest/databases.html
- """
- pass
-
-The purpose of this method is to determine bindings from your
-configuration file and create necessary engines, pools,
-etc. according to your ORM or database toolkit of choice.
-
-Additionally, your project's :py:mod:`model` module can be used to define
-functions for common binding operations, such as starting
-transactions, committing or rolling back work, and clearing a session.
-This is also the location in your project where object and relation
-definitions should be defined. Here's what a sample Pecan
-configuration file with database bindings might look like.
-
-::
-
- # Server Specific Configurations
- server = {
- ...
- }
-
- # Pecan Application Configurations
- app = {
- ...
- }
-
- # Bindings and options to pass to SQLAlchemy's ``create_engine``
- sqlalchemy = {
- 'url' : 'mysql://root:@localhost/dbname?charset=utf8&use_unicode=0',
- 'echo' : False,
- 'echo_pool' : False,
- 'pool_recycle' : 3600,
- 'encoding' : 'utf-8'
- }
-
-And a basic model implementation that can be used to configure and
-bind using SQLAlchemy.
-
-::
-
- from pecan import conf
- from sqlalchemy import create_engine, MetaData
- from sqlalchemy.orm import scoped_session, sessionmaker
-
- Session = scoped_session(sessionmaker())
- metadata = MetaData()
-
- def _engine_from_config(configuration):
- configuration = dict(configuration)
- url = configuration.pop('url')
- return create_engine(url, **configuration)
-
- def init_model():
- conf.sqlalchemy.engine = _engine_from_config(conf.sqlalchemy)
-
- def start():
- Session.bind = conf.sqlalchemy.engine
- metadata.bind = Session.bind
-
- def commit():
- Session.commit()
-
- def rollback():
- Session.rollback()
-
- def clear():
- Session.remove()
-
-Binding Within the Application
-------------------------------
-
-There are several approaches to wrapping your application's requests
-with calls to appropriate model function calls. One approach is WSGI
-middleware. We also recommend Pecan :ref:`hooks`. Pecan comes with
-:class:`~pecan.hooks.TransactionHook`, a hook which can be used to wrap
-requests in database transactions for you. To use it, simply include it in
-your project's ``app.py`` file and pass it a set of functions related to
-database binding.
-
-::
-
- from pecan import conf, make_app
- from pecan.hooks import TransactionHook
- from test_project import model
-
- app = make_app(
- conf.app.root,
- static_root = conf.app.static_root,
- template_path = conf.app.template_path,
- debug = conf.app.debug,
- hooks = [
- TransactionHook(
- model.start,
- model.start_read_only,
- model.commit,
- model.rollback,
- model.clear
- )
- ]
- )
-
-In the above example, on HTTP ``POST``, ``PUT``, and ``DELETE``
-requests, :class:`~pecan.hooks.TransactionHook` takes care of the transaction
-automatically by following these rules:
-
-#. Before controller routing has been determined, :func:`model.start`
- is called. This function should bind to the appropriate
- SQLAlchemy engine and start a transaction.
-
-#. Controller code is run and returns.
-
-#. If your controller or template rendering fails and raises an
- exception, :func:`model.rollback` is called and the original
- exception is re-raised. This allows you to rollback your database
- transaction to avoid committing work when exceptions occur in your
- application code.
-
-#. If the controller returns successfully, :func:`model.commit` and
- :func:`model.clear` are called.
-
-On idempotent operations (like HTTP ``GET`` and ``HEAD`` requests),
-:class:`~pecan.hooks.TransactionHook` handles transactions following different
-rules.
-
-#. ``model.start_read_only()`` is called. This function should bind
- to your SQLAlchemy engine.
-
-#. Controller code is run and returns.
-
-#. If the controller returns successfully, ``model.clear()`` is
- called.
-
-Also note that there is a useful :func:`~pecan.decorators.after_commit`
-decorator provided in :ref:`pecan_decorators`.
-
-Splitting Reads and Writes
---------------------------
-
-Employing the strategy above with :class:`~pecan.hooks.TransactionHook` makes
-it very simple to split database reads and writes based upon HTTP methods
-(i.e., GET/HEAD requests are read-only and would potentially be routed
-to a read-only database slave, while POST/PUT/DELETE requests require
-writing, and would always bind to a master database with read/write
-privileges). It's also possible to extend
-:class:`~pecan.hooks.TransactionHook` or write your own hook implementation for
-more refined control over where and when database bindings are called.
diff --git a/docs/source/deployment.rst b/docs/source/deployment.rst
deleted file mode 100644
index fa07e8b..0000000
--- a/docs/source/deployment.rst
+++ /dev/null
@@ -1,272 +0,0 @@
-.. _deployment:
-
-Deploying Pecan in Production
-=============================
-
-There are a variety of ways to deploy a Pecan project to a production
-environment. The following examples are meant to provide *direction*,
-not explicit instruction; deployment is usually heavily dependent upon
-the needs and goals of individual applications, so your mileage will
-probably vary.
-
-.. note::
-
- While Pecan comes packaged with a simple server *for development use*
- (:command:`pecan serve`), using a *production-ready* server similar to the ones
- described in this document is **very highly encouraged**.
-
-Installing Pecan
-----------------
-
-A few popular options are available for installing Pecan in production
-environments:
-
-* Using `setuptools <https://pypi.python.org/pypi/setuptools>`_. Manage
- Pecan as a dependency in your project's ``setup.py`` file so that it's
- installed alongside your project (e.g., ``python
- /path/to/project/setup.py install``). The default Pecan project
- described in :ref:`quick_start` facilitates this by including Pecan as
- a dependency for your project.
-
-* Using `pip <http://www.pip-installer.org/>`_.
- Use ``pip freeze`` and ``pip install`` to create and install from
- a ``requirements.txt`` file for your project.
-
-* Via the manual instructions found in :ref:`Installation`.
-
-.. note::
- Regardless of the route you choose, it's highly recommended that all
- deployment installations be done in a Python `virtual environment
- <http://www.virtualenv.org/>`_.
-
-Disabling Debug Mode
---------------------
-
-.. warning::
- One of the most important steps to take before deploying a Pecan app
- into production is to ensure that you have disabled **Debug Mode**, which
- provides a development-oriented debugging environment for tracebacks
- encountered at runtime. Failure to disable this development tool in your
- production environment *will* result in serious security issues. In your
- production configuration file, ensure that ``debug`` is set to ``False``.
-
- ::
-
- # myapp/production_config.py
- app = {
- ...
- 'debug': False
- }
-
-Pecan and WSGI
---------------
-
-WSGI is a Python standard that describes a standard interface between servers
-and an application. Any Pecan application is also known as a "WSGI
-application" because it implements the WSGI interface, so any server that is
-"WSGI compatible" may be used to serve your application. A few popular
-examples are:
-
-* `mod_wsgi <http://code.google.com/p/modwsgi/>`__
-* `uWSGI <http://projects.unbit.it/uwsgi/>`__
-* `Gunicorn <http://gunicorn.org/>`__
-* `waitress <http://docs.pylonsproject.org/projects/waitress/en/latest/>`__
-* `CherryPy <http://cherrypy.org/>`__
-
-Generally speaking, the WSGI entry point to any Pecan application can be
-generated using :func:`~pecan.deploy.deploy`::
-
- from pecan.deploy import deploy
- application = deploy('/path/to/some/app/config.py')
-
-Considerations for Static Files
--------------------------------
-
-Pecan comes with static file serving (e.g., CSS, Javascript, images)
-middleware which is **not** recommended for use in production.
-
-In production, Pecan doesn't serve media files itself; it leaves that job to
-whichever web server you choose.
-
-For serving static files in production, it's best to separate your concerns by
-serving static files separately from your WSGI application (primarily for
-performance reasons). There are several popular ways to accomplish this. Here
-are two:
-
-1. Set up a proxy server (such as `nginx <http://nginx.org/en>`__, `cherokee
- <http://www.cherokee-project.com>`__, :ref:`cherrypy`, or `lighttpd
- <http://www.lighttpd.net/>`__) to serve static files and proxy application
- requests through to your WSGI application:
-
- ::
-
- <HTTP Client> ─── <Production/Proxy Server>, e.g., Apache, nginx, cherokee (0.0.0.0:80) ─── <Static Files>
- │
- ├── <WSGI Server> Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5000 or /tmp/some.sock)
- ├── <WSGI Server> Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5001 or /tmp/some.sock)
- ├── <WSGI Server> Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5002 or /tmp/some.sock)
- └── <WSGI Server> Instance e.g., mod_wsgi, Gunicorn, uWSGI (127.0.0.1:5003 or /tmp/some.sock)
-
-
-2. Serve static files via a separate service, virtual host, or CDN.
-
-Common Recipes
---------------
-
-Apache + mod_wsgi
-+++++++++++++++++
-
-`mod_wsgi <http://code.google.com/p/modwsgi/>`_ is a popular Apache
-module which can be used to host any WSGI-compatible Python
-application (including your Pecan application).
-
-To get started, check out the `installation and configuration
-documentation
-<http://code.google.com/p/modwsgi/wiki/InstallationInstructions>`_ for
-mod_wsgi.
-
-For the sake of example, let's say that our project, ``simpleapp``, lives at
-``/var/www/simpleapp``, and that a `virtualenv <http://www.virtualenv.org>`_
-has been created at ``/var/www/venv`` with any necessary dependencies installed
-(including Pecan). Additionally, for security purposes, we've created a user,
-``user1``, and a group, ``group1`` to execute our application under.
-
-The first step is to create a ``.wsgi`` file which mod_wsgi will use
-as an entry point for your application::
-
- # /var/www/simpleapp/app.wsgi
- from pecan.deploy import deploy
- application = deploy('/var/www/simpleapp/config.py')
-
-Next, add Apache configuration for your application. Here's a simple
-example::
-
- <VirtualHost *>
- ServerName example.com
-
- WSGIDaemonProcess simpleapp user=user1 group=group1 threads=5 python-path=/var/www/venv/lib/python2.7/site-packages
- WSGIScriptAlias / /var/www/simpleapp/app.wsgi
-
- <Directory /var/www/simpleapp/>
- WSGIProcessGroup simpleapp
- WSGIApplicationGroup %{GLOBAL}
- Order deny,allow
- Allow from all
- </Directory>
- </VirtualHost>
-
-For more instructions and examples of mounting WSGI applications using
-mod_wsgi, consult the `mod_wsgi Documentation`_.
-
-.. _mod_wsgi Documentation: http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide#Mounting_The_WSGI_Application
-
-Finally, restart Apache and give it a try.
-
-uWSGI
-+++++
-
-`uWSGI <http://projects.unbit.it/uwsgi/>`_ is a fast, self-healing and
-developer/sysadmin-friendly application container server coded in pure C. It
-uses the `uwsgi <http://projects.unbit.it/uwsgi/wiki/uwsgiProtocol>`__
-protocol, but can speak other protocols as well (http, fastcgi...).
-
-Running Pecan applications with uWSGI is a snap::
-
- $ pip install uwsgi
- $ pecan create simpleapp && cd simpleapp
- $ python setup.py develop
- $ uwsgi --http-socket :8080 --venv /path/to/virtualenv --pecan config.py
-
-or using a Unix socket (that nginx, for example, could be configured to
-`proxy to <http://projects.unbit.it/uwsgi/wiki/RunOnNginx>`_)::
-
- $ uwsgi -s /tmp/uwsgi.sock --venv /path/to/virtualenv --pecan config.py
-
-Gunicorn
-++++++++
-
-`Gunicorn <http://gunicorn.org/>`__, or "Green Unicorn", is a WSGI HTTP Server for
-UNIX. It’s a pre-fork worker model ported from Ruby’s Unicorn project. It
-supports both eventlet and greenlet.
-
-Running a Pecan application on Gunicorn is simple. Let's walk through it with
-Pecan's default project::
-
- $ pip install gunicorn
- $ pecan create simpleapp && cd simpleapp
- $ python setup.py develop
- $ gunicorn_pecan config.py
-
-
-.. _cherrypy:
-
-CherryPy
-++++++++
-
-`CherryPy <http://cherrypy.org/>`__ offers a pure Python HTTP/1.1-compliant WSGI
-thread-pooled web server. It can support Pecan applications easily and even
-serve static files like a production server would do.
-
-The examples that follow are geared towards using CherryPy as the server in
-charge of handling a Pecan app along with serving static files.
-
-::
-
- $ pip install cherrypy
- $ pecan create simpleapp && cd simpleapp
- $ python setup.py develop
-
-To run with CherryPy, the easiest approach is to create a script in the root of
-the project (alongside ``setup.py``), so that we can describe how our example
-application should be served. This is how the script (named ``run.py``) looks::
-
- import os
- import cherrypy
- from cherrypy import wsgiserver
-
- from pecan import deploy
-
- simpleapp_wsgi_app = deploy('/path/to/production_config.py')
-
- public_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'public'))
-
- # A dummy class for our Root object
- # necessary for some CherryPy machinery
- class Root(object):
- pass
-
- def make_static_config(static_dir_name):
- """
- All custom static configurations are set here, since most are common, it
- makes sense to generate them just once.
- """
- static_path = os.path.join('/', static_dir_name)
- path = os.path.join(public_path, static_dir_name)
- configuration = {
- static_path: {
- 'tools.staticdir.on': True,
- 'tools.staticdir.dir': path
- }
- }
- return cherrypy.tree.mount(Root(), '/', config=configuration)
-
- # Assuming your app has media on different paths, like 'css', and 'images'
- application = wsgiserver.WSGIPathInfoDispatcher({
- '/': simpleapp_wsgi_app,
- '/css': make_static_config('css'),
- '/images': make_static_config('images')
- }
- )
-
- server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 8080), application,
- server_name='simpleapp')
-
- try:
- server.start()
- except KeyboardInterrupt:
- print "Terminating server..."
- server.stop()
-
-To start the server, simply call it with the Python executable::
-
- $ python run.py
diff --git a/docs/source/development.rst b/docs/source/development.rst
deleted file mode 100644
index 76b2267..0000000
--- a/docs/source/development.rst
+++ /dev/null
@@ -1,60 +0,0 @@
-.. _development:
-
-Developing Pecan Applications Locally
-=====================================
-
-.. include:: reload.rst
- :start-after: #reload
-
-Debugging Pecan Applications
-----------------------------
-
-Pecan comes with simple debugging middleware for helping diagnose problems
-in your applications. To enable the debugging middleware, simply set the
-``debug`` flag to ``True`` in your configuration file::
-
- app = {
- ...
- 'debug': True,
- ...
- }
-
-Once enabled, the middleware will automatically catch exceptions raised by your
-application and display the Python stack trace and WSGI environment in your
-browser when runtime exceptions are raised.
-
-To improve debugging, including support for an interactive browser-based
-console, Pecan makes use of the Python `backlash
-<https://pypi.python.org/pypi/backlash>` library. You’ll need to install it
-for development use before continuing::
-
- $ pip install backlash
- Downloading/unpacking backlash
- ...
- Successfully installed backlash
-
-
-Serving Static Files
---------------------
-
-Pecan comes with simple file serving middleware for serving CSS, Javascript,
-images, and other static files. You can configure it by ensuring that the
-following options are specified in your configuration file:
-
-::
-
- app = {
- ...
- 'debug': True,
- 'static_root': '%(confdir)/public
- }
-
-where ``static_root`` is an absolute pathname to the directory in which your
-static files live. For convenience, the path may include the ``%(confdir)``
-variable, which Pecan will substitute with the absolute path of your
-configuration file at runtime.
-
-.. note::
-
- In production, ``app.debug`` should *never* be set to ``True``, so you'll
- need to serve your static files via your production web server.
diff --git a/docs/source/errors.rst b/docs/source/errors.rst
deleted file mode 100644
index 6e38a88..0000000
--- a/docs/source/errors.rst
+++ /dev/null
@@ -1,119 +0,0 @@
-.. _errors:
-
-Custom Error Documents
-======================
-In this article we will configure a Pecan application to display a custom
-error page whenever the server returns a ``404 Page Not Found`` status.
-
-This article assumes that you have already created a test application as
-described in :ref:`quick_start`.
-
-.. note::
- While this example focuses on the ``HTTP 404`` message, the same
- technique may be applied to define custom actions for any of the ``HTTP``
- status response codes in the 400 and 500 range. You are well advised to use
- this power judiciously.
-
-.. _overview:
-
-Overview
---------
-
-Pecan makes it simple to customize error documents in two simple steps:
-
- * :ref:`configure` of the HTTP status messages you want to handle
- in your application's ``config.py``
- * :ref:`controllers` to handle the status messages you have configured
-
-.. _configure:
-
-Configure Routing
------------------
-Let's configure our application ``test_project`` to route ``HTTP 404 Page
-Not Found`` messages to a custom controller.
-
-First, let's update ``test_project/config.py`` to specify a new
-error-handler.
-
-::
-
- # Pecan Application Configurations
- app = {
- 'root' : 'test_project.controllers.root.RootController',
- 'modules' : ['test_project'],
- 'static_root' : '%(confdir)s/public',
- 'template_path' : '%(confdir)s/test_project/templates',
- 'reload' : True,
- 'debug' : True,
-
- # modify the 'errors' key to direct HTTP status codes to a custom
- # controller
- 'errors' : {
- #404 : '/error/404',
- 404 : '/notfound',
- '__force_dict__' : True
- }
- }
-
-Instead of the default error page, Pecan will now route 404 messages
-to the controller method ``notfound``.
-
-.. _controllers:
-
-Write Custom Controllers
-------------------------
-
-The easiest way to implement the error handler is to
-add it to :class:`test_project.root.RootController` class
-(typically in ``test_project/controllers/root.py``).
-
-::
-
- from pecan import expose
- from webob.exc import status_map
-
-
- class RootController(object):
-
- @expose(generic=True, template='index.html')
- def index(self):
- return dict()
-
- @index.when(method='POST')
- def index_post(self, q):
- redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % q)
-
-
- ## custom handling of '404 Page Not Found' messages
- @expose('error.html')
- def notfound(self):
- return dict(status=404, message="test_project does not have this page")
-
-
- @expose('error.html')
- def error(self, status):
- try:
- status = int(status)
- except ValueError:
- status = 0
- message = getattr(status_map.get(status), 'explanation', '')
- return dict(status=status, message=message)
-
-
-And that's it!
-
-Notice that the only bit of code we added to our :class:`RootController` was::
-
- ## custom handling of '404 Page Not Found' messages
- @expose('error.html')
- def notfound(self):
- return dict(status=404, message="test_project does not have this page")
-
-We simply :func:`~pecan.decorators.expose` the ``notfound`` controller with the
-``error.html`` template (which was conveniently generated for us and placed
-under ``test_project/templates/`` when we created ``test_project``). As with
-any Pecan controller, we return a dictionary of variables for interpolation by
-the template renderer.
-
-Now we can modify the error template, or write a brand new one to make the 404
-error status page of ``test_project`` as pretty or fancy as we want.
diff --git a/docs/source/forms.rst b/docs/source/forms.rst
deleted file mode 100644
index 7f7e2da..0000000
--- a/docs/source/forms.rst
+++ /dev/null
@@ -1,92 +0,0 @@
-.. _forms:
-
-Generating and Validating Forms
-===============================
-
-Pecan provides no opinionated support for working with
-form generation and validation libraries, but it’s easy to import your
-library of choice with minimal effort.
-
-This article details best practices for integrating the popular forms library,
-`WTForms <http://wtforms.simplecodes.com/>`_, into your Pecan project.
-
-Defining a Form Definition
---------------------------
-
-Let's start by building a basic form with a required ``first_name``
-field and an optional ``last_name`` field.
-
-::
-
- from wtforms import Form, TextField, validators
-
- class MyForm(Form):
- first_name = TextField(u'First Name', validators=[validators.required()])
- last_name = TextField(u'Last Name', validators=[validators.optional()])
-
- class SomeController(object):
- pass
-
-Rendering a Form in a Template
-------------------------------
-
-Next, let's add a controller, and pass a form instance to the template.
-
-::
-
- from pecan import expose
- from wtforms import Form, TextField, validators
-
- class MyForm(Form):
- first_name = TextField(u'First Name', validators=[validators.required()])
- last_name = TextField(u'Last Name', validators=[validators.optional()])
-
- class SomeController(object):
-
- @expose(template='index.html')
- def index(self):
- return dict(form=MyForm())
-
-Here's the Mako_ template file:
-
-.. _Mako: http://www.makeotemplates.org/
-
-.. code-block:: html
-
- <form method="post" action="/">
- <div>
- ${form.first_name.label}:
- ${form.first_name}
- </div>
- <div>
- ${form.last_name.label}:
- ${form.last_name}
- </div>
- <input type="submit" value="submit">
- </form>
-
-Validating POST Values
-----------------------
-
-Using the same :class:`MyForm` definition, let's redirect the user if the form is
-validated, otherwise, render the form again.
-
-.. code-block:: python
-
- from pecan import expose, request
- from wtforms import Form, TextField, validators
-
- class MyForm(Form):
- first_name = TextField(u'First Name', validators=[validators.required()])
- last_name = TextField(u'Last Name', validators=[validators.optional()])
-
- class SomeController(object):
-
- @expose(template='index.html')
- def index(self):
- my_form = MyForm(request.POST)
- if request.method == 'POST' and my_form.validate():
- # save_values()
- redirect('/success')
- else:
- return dict(form=my_form)
diff --git a/docs/source/hooks.rst b/docs/source/hooks.rst
deleted file mode 100644
index 69426db..0000000
--- a/docs/source/hooks.rst
+++ /dev/null
@@ -1,409 +0,0 @@
-.. _hooks:
-
-Pecan Hooks
-===========
-
-Although it is easy to use WSGI middleware with Pecan, it can be hard
-(sometimes impossible) to have access to Pecan's internals from within
-middleware. Pecan Hooks are a way to interact with the framework,
-without having to write separate middleware.
-
-Hooks allow you to execute code at key points throughout the life cycle of your request:
-
-* :func:`~pecan.hooks.PecanHook.on_route`: called before Pecan attempts to
- route a request to a controller
-
-* :func:`~pecan.hooks.PecanHook.before`: called after routing, but before
- controller code is run
-
-* :func:`~pecan.hooks.PecanHook.after`: called after controller code has been
- run
-
-* :func:`~pecan.hooks.PecanHook.on_error`: called when a request generates an
- exception
-
-Implementating a Pecan Hook
----------------------------
-
-In the below example, a simple hook will gather some information about
-the request and print it to ``stdout``.
-
-Your hook implementation needs to import :class:`~pecan.hooks.PecanHook` so it
-can be used as a base class. From there, you'll want to override the
-:func:`~pecan.hooks.PecanHook.on_route`, :func:`~pecan.hooks.PecanHook.before`,
-:func:`~pecan.hooks.PecanHook.after`, or
-:func:`~pecan.hooks.PecanHook.on_error` methods to
-define behavior.
-
-::
-
- from pecan.hooks import PecanHook
-
- class SimpleHook(PecanHook):
-
- def before(self, state):
- print "\nabout to enter the controller..."
-
- def after(self, state):
- print "\nmethod: \t %s" % state.request.method
- print "\nresponse: \t %s" % state.response.status
-
-:func:`~pecan.hooks.PecanHook.on_route`, :func:`~pecan.hooks.PecanHook.before`,
-and :func:`~pecan.hooks.PecanHook.after` are each passed a shared
-state object which includes useful information, such as the request and
-response objects, and which controller was selected by Pecan's routing::
-
- class SimpleHook(PecanHook):
-
- def on_route(self, state):
- print "\nabout to map the URL to a Python method (controller)..."
- assert state.controller is None # Routing hasn't occurred yet
- assert isinstance(state.request, webob.Request)
- assert isinstance(state.response, webob.Response)
- assert isinstance(state.hooks, list) # A list of hooks to apply
-
- def before(self, state):
- print "\nabout to enter the controller..."
- if state.request.path == '/':
- #
- # `state.controller` is a reference to the actual
- # `@pecan.expose()`-ed controller that will be routed to
- # and used to generate the response body
- #
- assert state.controller.__func__ is RootController.index.__func__
- assert isinstance(state.arguments, inspect.Arguments)
- print state.arguments.args
- print state.arguments.varargs
- print state.arguments.keywords
- assert isinstance(state.request, webob.Request)
- assert isinstance(state.response, webob.Response)
- assert isinstance(state.hooks, list)
-
-
-:func:`~pecan.hooks.PecanHook.on_error` is passed a shared state object **and**
-the original exception. If an :func:`~pecan.hooks.PecanHook.on_error` handler
-returns a Response object, this response will be returned to the end user and
-no furthur :func:`~pecan.hooks.PecanHook.on_error` hooks will be executed::
-
- class CustomErrorHook(PecanHook):
-
- def on_error(self, state, exc):
- if isinstance(exc, SomeExceptionType):
- return webob.Response('Custom Error!', status=500)
-
-.. _attaching_hooks:
-
-Attaching Hooks
----------------
-
-Hooks can be attached in a project-wide manner by specifying a list of hooks
-in your project's configuration file.
-
-::
-
- app = {
- 'root' : '...'
- # ...
- 'hooks': lambda: [SimpleHook()]
- }
-
-Hooks can also be applied selectively to controllers and their sub-controllers
-using the :attr:`__hooks__` attribute on one or more controllers and
-subclassing :class:`~pecan.hooks.HookController`.
-
-::
-
- from pecan import expose
- from pecan.hooks import HookController
- from my_hooks import SimpleHook
-
- class SimpleController(HookController):
-
- __hooks__ = [SimpleHook()]
-
- @expose('json')
- def index(self):
- print "DO SOMETHING!"
- return dict()
-
-Now that :class:`SimpleHook` is included, let's see what happens
-when we run the app and browse the application from our web browser.
-
-::
-
- pecan serve config.py
- serving on 0.0.0.0:8080 view at http://127.0.0.1:8080
-
- about to enter the controller...
- DO SOMETHING!
- method: GET
- response: 200 OK
-
-Hooks can be inherited from parent class or mixins. Just make sure to
-subclass from :class:`~pecan.hooks.HookController`.
-
-::
-
- from pecan import expose
- from pecan.hooks import PecanHook, HookController
-
- class ParentHook(PecanHook):
-
- priority = 1
-
- def before(self, state):
- print "\nabout to enter the parent controller..."
-
- class CommonHook(PecanHook):
-
- priority = 2
-
- def before(self, state):
- print "\njust a common hook..."
-
- class SubHook(PecanHook):
-
- def before(self, state):
- print "\nabout to enter the subcontroller..."
-
- class SubMixin(object):
- __hooks__ = [SubHook()]
-
- # We'll use the same instance for both controllers,
- # to avoid double calls
- common = CommonHook()
-
- class SubController(HookController, SubMixin):
-
- __hooks__ = [common]
-
- @expose('json')
- def index(self):
- print "\nI AM THE SUB!"
- return dict()
-
- class RootController(HookController):
-
- __hooks__ = [common, ParentHook()]
-
- @expose('json')
- def index(self):
- print "\nI AM THE ROOT!"
- return dict()
-
- sub = SubController()
-
-Let's see what happens when we run the app.
-First loading the root controller:
-
-::
-
- pecan serve config.py
- serving on 0.0.0.0:8080 view at http://127.0.0.1:8080
-
- GET / HTTP/1.1" 200
-
- about to enter the parent controller...
-
- just a common hook
-
- I AM THE ROOT!
-
-Then loading the sub controller:
-
-::
-
- pecan serve config.py
- serving on 0.0.0.0:8080 view at http://127.0.0.1:8080
-
- GET /sub HTTP/1.1" 200
-
- about to enter the parent controller...
-
- just a common hook
-
- about to enter the subcontroller...
-
- I AM THE SUB!
-
-.. note::
-
- Make sure to set proper priority values for nested hooks in order
- to get them executed in the desired order.
-
-.. warning::
-
- Two hooks of the same type will be added/executed twice, if passed as
- different instances to a parent and a child controller.
- If passed as one instance variable - will be invoked once for both controllers.
-
-Hooks That Come with Pecan
---------------------------
-
-Pecan includes some hooks in its core. This section will describe
-their different uses, how to configure them, and examples of common
-scenarios.
-
-.. _requestviewerhook:
-
-RequestViewerHook
-'''''''''''''''''
-
-This hook is useful for debugging purposes. It has access to every
-attribute the ``response`` object has plus a few others that are specific to
-the framework.
-
-There are two main ways that this hook can provide information about a request:
-
-#. Terminal or logging output (via an file-like stream like ``stdout``)
-#. Custom header keys in the actual response.
-
-By default, both outputs are enabled.
-
-.. seealso::
-
- * :ref:`pecan_hooks`
-
-Configuring RequestViewerHook
-.............................
-
-There are a few ways to get this hook properly configured and running. However,
-it is useful to know that no actual configuration is needed to have it up and
-running.
-
-By default it will output information about these items:
-
-* path : Displays the url that was used to generate this response
-* status : The response from the server (e.g. '200 OK')
-* method : The method for the request (e.g. 'GET', 'POST', 'PUT or 'DELETE')
-* controller : The actual controller method in Pecan responsible for the response
-* params : A list of tuples for the params passed in at request time
-* hooks : Any hooks that are used in the app will be listed here.
-
-The default configuration will show those values in the terminal via
-``stdout`` and it will also add them to the response headers (in the
-form of ``X-Pecan-item_name``).
-
-This is how the terminal output might look for a `/favicon.ico` request::
-
- path - /favicon.ico
- status - 404 Not Found
- method - GET
- controller - The resource could not be found.
- params - []
- hooks - ['RequestViewerHook']
-
-In the above case, the file was not found, and the information was printed to
-`stdout`. Additionally, the following headers would be present in the HTTP
-response::
-
- X-Pecan-path /favicon.ico
- X-Pecan-status 404 Not Found
- X-Pecan-method GET
- X-Pecan-controller The resource could not be found.
- X-Pecan-params []
- X-Pecan-hooks ['RequestViewerHook']
-
-The configuration dictionary is flexible (none of the keys are required) and
-can hold two keys: ``items`` and ``blacklist``.
-
-This is how the hook would look if configured directly (shortened for brevity)::
-
- ...
- 'hooks': lambda: [
- RequestViewerHook({'items':['path']})
- ]
-
-Modifying Output Format
-.......................
-
-The ``items`` list specify the information that the hook will return.
-Sometimes you will need a specific piece of information or a certain
-bunch of them according to the development need so the defaults will
-need to be changed and a list of items specified.
-
-.. note::
-
- When specifying a list of items, this list overrides completely the
- defaults, so if a single item is listed, only that item will be returned by
- the hook.
-
-The hook has access to every single attribute the request object has
-and not only to the default ones that are displayed, so you can fine tune the
-information displayed.
-
-These is a list containing all the possible attributes the hook has access to
-(directly from `webob`):
-
-====================== ==========================
-====================== ==========================
-accept make_tempfile
-accept_charset max_forwards
-accept_encoding method
-accept_language params
-application_url path
-as_string path_info
-authorization path_info_peek
-blank path_info_pop
-body path_qs
-body_file path_url
-body_file_raw postvars
-body_file_seekable pragma
-cache_control query_string
-call_application queryvars
-charset range
-content_length referer
-content_type referrer
-cookies relative_url
-copy remote_addr
-copy_body remote_user
-copy_get remove_conditional_headers
-date request_body_tempfile_limit
-decode_param_names scheme
-environ script_name
-from_file server_name
-from_string server_port
-get_response str_GET
-headers str_POST
-host str_cookies
-host_url str_params
-http_version str_postvars
-if_match str_queryvars
-if_modified_since unicode_errors
-if_none_match upath_info
-if_range url
-if_unmodified_since urlargs
-is_body_readable urlvars
-is_body_seekable uscript_name
-is_xhr user_agent
-make_body_seekable
-====================== ==========================
-
-And these are the specific ones from Pecan and the hook:
-
- * controller
- * hooks
- * params (params is actually available from `webob` but it is parsed
- by the hook for redability)
-
-Blacklisting Certain Paths
-..........................
-
-Sometimes it's annoying to get information about *every* single
-request. To limit the output, pass the list of URL paths for which
-you do not want data as the ``blacklist``.
-
-The matching is done at the start of the URL path, so be careful when using
-this feature. For example, if you pass a configuration like this one::
-
- { 'blacklist': ['/f'] }
-
-It would not show *any* url that starts with ``f``, effectively behaving like
-a globbing regular expression (but not quite as powerful).
-
-For any number of blocking you may need, just add as many items as wanted::
-
- { 'blacklist' : ['/favicon.ico', '/javascript', '/images'] }
-
-Again, the ``blacklist`` key can be used along with the ``items`` key
-or not (it is not required).
diff --git a/docs/source/index.rst b/docs/source/index.rst
deleted file mode 100644
index 520d0f7..0000000
--- a/docs/source/index.rst
+++ /dev/null
@@ -1,127 +0,0 @@
-Introduction and History
-========================
-Welcome to Pecan, a lean Python web framework inspired by CherryPy,
-TurboGears, and Pylons. Pecan was originally created by the developers
-of `ShootQ <http://shootq.com>`_ while working at `Pictage
-<http://pictage.com>`_.
-
-Pecan was created to fill a void in the Python web-framework world – a
-very lightweight framework that provides object-dispatch style
-routing. Pecan does not aim to be a "full stack" framework, and
-therefore includes no out of the box support for things like sessions
-or databases (although tutorials are included for integrating these
-yourself in just a few lines of code). Pecan instead focuses on HTTP
-itself.
-
-Although it is lightweight, Pecan does offer an extensive feature set
-for building HTTP-based applications, including:
-
- * Object-dispatch for easy routing
- * Full support for REST-style controllers
- * Extensible security framework
- * Extensible template language support
- * Extensible JSON support
- * Easy Python-based configuration
-
-Narrative Documentation
-=======================
-
-.. toctree::
- :maxdepth: 2
-
- installation.rst
- quick_start.rst
- routing.rst
- templates.rst
- rest.rst
- configuration.rst
- secure_controller.rst
- hooks.rst
- jsonify.rst
- contextlocals.rst
- commands.rst
- development.rst
- deployment.rst
- logging.rst
- testing.rst
-
-
-Cookbook and Common Patterns
-============================
-.. toctree::
- :maxdepth: 2
-
- forms.rst
- sessions.rst
- databases.rst
- errors.rst
- simple_forms_processing.rst
- simple_ajax.rst
-
-
-API Documentation
-=================
-Pecan's source code is well documented using Python docstrings and
-comments. In addition, we have generated API documentation from the
-docstrings here:
-
-
-.. toctree::
- :maxdepth: 2
-
- pecan_core.rst
- pecan_commands.rst
- pecan_configuration.rst
- pecan_decorators.rst
- pecan_deploy.rst
- pecan_hooks.rst
- pecan_middleware_debug.rst
- pecan_jsonify.rst
- pecan_rest.rst
- pecan_routing.rst
- pecan_secure.rst
- pecan_templating.rst
- pecan_testing.rst
- pecan_util.rst
-
-
-Change History
-=================
-
-.. toctree::
- :maxdepth: 1
-
- changes.rst
-
-
-License
--------
-The Pecan framework and the documentation is BSD Licensed::
-
- Copyright (c) <2010>, Pecan Framework
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of the <organization> nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
deleted file mode 100644
index fb43491..0000000
--- a/docs/source/installation.rst
+++ /dev/null
@@ -1,50 +0,0 @@
-.. _installation:
-
-Installation
-============
-
-Stable Version
---------------
-
-We recommend installing Pecan with `pip
-<http://www.pip-installer.org/>`_, but you
-can also try with :command:`easy_install`. Creating a spot in your environment
-where Pecan can be isolated from other packages is best practice.
-
-To get started with an environment for Pecan, we recommend creating a new
-`virtual environment <http://www.virtualenv.org>`_ using `virtualenv
-<http://www.virtualenv.org>`_::
-
- $ virtualenv pecan-env
- $ cd pecan-env
- $ source bin/activate
-
-The above commands create a virtual environment and *activate* it. This
-will isolate Pecan's dependency installations from your system packages, making
-it easier to debug problems if needed.
-
-Next, let's install Pecan::
-
- $ pip install pecan
-
-
-Development (Unstable) Version
-------------------------------
-If you want to run the latest development version of Pecan you will
-need to install git and clone the repo from GitHub::
-
- $ git clone https://github.com/stackforge/pecan.git
-
-Assuming your virtual environment is still activated, call ``setup.py`` to
-install the development version.::
-
- $ cd pecan
- $ python setup.py develop
-
-.. note::
- The ``master`` development branch is volatile and is generally not
- recommended for production use.
-
-Alternatively, you can also install from GitHub directly with ``pip``.::
-
- $ pip install -e git://github.com/stackforge/pecan.git#egg=pecan
diff --git a/docs/source/jsonify.rst b/docs/source/jsonify.rst
deleted file mode 100644
index b5c6053..0000000
--- a/docs/source/jsonify.rst
+++ /dev/null
@@ -1,70 +0,0 @@
-.. _jsonify:
-
-
-JSON Serialization
-==================
-
-Pecan includes a simple, easy-to-use system for generating and serving
-JSON. To get started, create a file in your project called
-``json.py`` and import it in your project's ``app.py``.
-
-Your ``json`` module will contain a series of rules for generating
-JSON from objects you return in your controller.
-
-Let's say that we have a controller in our Pecan application which
-we want to use to return JSON output for a :class:`User` object::
-
- from myproject.lib import get_current_user
-
- class UsersController(object):
- @expose('json')
- def current_user(self):
- '''
- return an instance of myproject.model.User which represents
- the current authenticated user
- '''
- return get_current_user()
-
-In order for this controller to function, Pecan will need to know how to
-convert the :class:`User` object into data types compatible with JSON. One
-way to tell Pecan how to convert an object into JSON is to define a
-rule in your ``json.py``::
-
- from pecan.jsonify import jsonify
- from myproject import model
-
- @jsonify.register(model.User)
- def jsonify_user(user):
- return dict(
- name = user.name,
- email = user.email,
- birthday = user.birthday.isoformat()
- )
-
-In this example, when an instance of the :class:`model.User` class is
-returned from a controller which is configured to return JSON, the
-:func:`jsonify_user` rule will be called to convert the object to
-JSON-compatible data. Note that the rule does not generate a JSON
-string, but rather generates a Python dictionary which contains only
-JSON friendly data types.
-
-Alternatively, the rule can be specified on the object itself, by
-specifying a :func:`__json__` method in the class::
-
- class User(object):
- def __init__(self, name, email, birthday):
- self.name = name
- self.email = email
- self.birthday = birthday
-
- def __json__(self):
- return dict(
- name = self.name,
- email = self.email,
- birthday = self.birthday.isoformat()
- )
-
-The benefit of using a ``json.py`` module is having all of your JSON
-rules defined in a central location, but some projects prefer the
-simplicity of keeping the JSON rules attached directly to their
-model objects.
diff --git a/docs/source/logging.rst b/docs/source/logging.rst
deleted file mode 100644
index 0060a56..0000000
--- a/docs/source/logging.rst
+++ /dev/null
@@ -1,157 +0,0 @@
-.. _logging:
-
-Logging
-=======
-
-Pecan uses the Python standard library's :py:mod:`logging` module by passing
-logging configuration options into the `logging.config.dictConfig`_
-function. The full documentation for the :func:`dictConfig` format is
-the best source of information for logging configuration, but to get
-you started, this chapter will provide you with a few simple examples.
-
-.. _logging.config.dictConfig: http://docs.python.org/library/logging.config.html#configuration-dictionary-schema
-
-Configuring Logging
--------------------
-
-Sample logging configuration is provided with the quickstart project
-introduced in :ref:`quick_start`:
-
-::
-
- $ pecan create myapp
-
-The default configuration defines one handler and two loggers.
-
-::
-
- # myapp/config.py
-
- app = { ... }
- server = { ... }
-
- logging = {
- 'root' : {'level': 'INFO', 'handlers': ['console']},
- 'loggers': {
- 'myapp': {'level': 'DEBUG', 'handlers': ['console']}
- },
- 'handlers': {
- 'console': {
- 'level': 'DEBUG',
- 'class': 'logging.StreamHandler',
- 'formatter': 'simple'
- }
- },
- 'formatters': {
- 'simple': {
- 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]'
- '[%(threadName)s] %(message)s')
- }
- }
- }
-
-* ``console`` logs messages to ``stderr`` using the ``simple`` formatter.
-
-* ``myapp`` logs messages sent at a level above or equal to ``DEBUG`` to
- the ``console`` handler
-
-* ``root`` logs messages at a level above or equal to the ``INFO`` level to
- the ``console`` handler
-
-
-Writing Log Messages in Your Application
-----------------------------------------
-
-The logger named ``myapp`` is reserved for your usage in your Pecan
-application.
-
-Once you have configured your logging, you can place logging calls in your
-code. Using the logging framework is very simple.
-
-::
-
- # myapp/myapp/controllers/root.py
- from pecan import expose
- import logging
-
- logger = logging.getLogger(__name__)
-
- class RootController(object):
-
- @expose()
- def index(self):
- if bad_stuff():
- logger.error('Uh-oh!')
- return dict()
-
-Logging to Files and Other Locations
-------------------------------------
-
-Python's :py:mod:`logging` library defines a variety of handlers that assist in
-writing logs to file. A few interesting ones are:
-
-* :class:`~logging.FileHandler` - used to log messages to a file on the filesystem
-* :class:`~logging.handlers.RotatingFileHandler` - similar to
- :class:`~logging.FileHandler`, but also rotates logs
- periodically
-* :class:`~logging.handlers.SysLogHandler` - used to log messages to a UNIX syslog
-* :class:`~logging.handlers.SMTPHandler` - used to log messages to an email
- address via SMTP
-
-Using any of them is as simple as defining a new handler in your
-application's ``logging`` block and assigning it to one of more loggers.
-
-Logging Requests with Paste Translogger
----------------------------------------
-
-`Paste <http://pythonpaste.org/>`_ (which is not included with Pecan) includes
-the :class:`~paste.translogger.TransLogger` middleware
-for logging requests in `Apache Combined Log Format
-<http://httpd.apache.org/docs/2.2/logs.html#combined>`_. Combined with
-file-based logging, TransLogger can be used to create an ``access.log`` file
-similar to ``Apache``.
-
-To add this middleware, modify your the ``setup_app`` method in your
-project's ``app.py`` as follows::
-
- # myapp/myapp/app.py
- from pecan import make_app
- from paste.translogger import TransLogger
-
- def setup_app(config):
- # ...
- app = make_app(
- config.app.root
- # ...
- )
- app = TransLogger(app, setup_console_handler=False)
- return app
-
-By default, :class:`~paste.translogger.TransLogger` creates a logger named
-``wsgi``, so you'll need to specify a new (file-based) handler for this logger
-in our Pecan configuration file::
-
- # myapp/config.py
-
- app = { ... }
- server = { ... }
-
- logging = {
- 'loggers': {
- # ...
- 'wsgi': {'level': 'INFO', 'handlers': ['logfile'], 'qualname': 'wsgi'}
- },
- 'handlers': {
- # ...
- 'logfile': {
- 'class': 'logging.FileHandler',
- 'filename': '/etc/access.log',
- 'level': 'INFO',
- 'formatter': 'messageonly'
- }
- },
- 'formatters': {
- # ...
- 'messageonly': {'format': '%(message)s'}
- }
- }
diff --git a/docs/source/pecan_commands.rst b/docs/source/pecan_commands.rst
deleted file mode 100644
index 094fb25..0000000
--- a/docs/source/pecan_commands.rst
+++ /dev/null
@@ -1,26 +0,0 @@
-.. _pecan_commands:
-
-:mod:`pecan.commands` -- Pecan Commands
-=======================================
-
-The :mod:`pecan.commands` module implements the ``pecan`` console script
-used to provide (for example) ``pecan serve`` and ``pecan shell`` command line
-utilities.
-
-.. automodule:: pecan.commands.base
- :members:
- :show-inheritance:
-
-:mod:`pecan.commands.server` -- Pecan Development Server
-++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-.. automodule:: pecan.commands.serve
- :members:
- :show-inheritance:
-
-:mod:`pecan.commands.shell` -- Pecan Interactive Shell
-++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-.. automodule:: pecan.commands.shell
- :members:
- :show-inheritance:
diff --git a/docs/source/pecan_configuration.rst b/docs/source/pecan_configuration.rst
deleted file mode 100644
index 609635a..0000000
--- a/docs/source/pecan_configuration.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. _pecan_configuration:
-
-:mod:`pecan.configuration` -- Pecan Configuration Engine
-========================================================
-
-The :mod:`pecan.configuration` module provides an implementation of a
-Python-based configuration engine for Pecan applications.
-
-.. automodule:: pecan.configuration
- :members:
- :show-inheritance: \ No newline at end of file
diff --git a/docs/source/pecan_core.rst b/docs/source/pecan_core.rst
deleted file mode 100644
index c114b92..0000000
--- a/docs/source/pecan_core.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-.. _pecan_core:
-
-:mod:`pecan.core` -- Pecan Core
-===============================
-
-The :mod:`pecan.core` module is the base module for creating and extending
-Pecan. The core logic for processing HTTP requests and responses lives
-here.
-
-.. automodule:: pecan.core
- :members:
- :show-inheritance: \ No newline at end of file
diff --git a/docs/source/pecan_decorators.rst b/docs/source/pecan_decorators.rst
deleted file mode 100644
index 395f82a..0000000
--- a/docs/source/pecan_decorators.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. _pecan_decorators:
-
-:mod:`pecan.decorators` -- Pecan Decorators
-===========================================
-
-The :mod:`pecan.decorators` module includes useful decorators for
-creating Pecan applications.
-
-.. automodule:: pecan.decorators
- :members:
- :show-inheritance: \ No newline at end of file
diff --git a/docs/source/pecan_deploy.rst b/docs/source/pecan_deploy.rst
deleted file mode 100644
index bcda855..0000000
--- a/docs/source/pecan_deploy.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. _pecan_deploy:
-
-:mod:`pecan.deploy` -- Pecan Deploy
-===========================================
-
-The :mod:`pecan.deploy` module includes fixtures to help deploy Pecan
-applications.
-
-.. automodule:: pecan.deploy
- :members:
- :show-inheritance:
diff --git a/docs/source/pecan_hooks.rst b/docs/source/pecan_hooks.rst
deleted file mode 100644
index 71587d7..0000000
--- a/docs/source/pecan_hooks.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-.. _pecan_hooks:
-
-:mod:`pecan.hooks` -- Pecan Hooks
-=================================
-
-The :mod:`pecan.hooks` module includes support for creating Pecan
-``hooks`` which are a simple way to hook into the request processing
-of requests coming into your application to perform cross-cutting tasks.
-
-.. automodule:: pecan.hooks
- :members:
- :show-inheritance: \ No newline at end of file
diff --git a/docs/source/pecan_jsonify.rst b/docs/source/pecan_jsonify.rst
deleted file mode 100644
index 7da09be..0000000
--- a/docs/source/pecan_jsonify.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. _pecan_jsonify:
-
-:mod:`pecan.jsonify` -- Pecan ``JSON`` Support
-==============================================
-
-The :mod:`pecan.jsonify` module includes support for ``JSON`` rule
-creation using generic functions.
-
-.. automodule:: pecan.jsonify
- :members:
- :show-inheritance: \ No newline at end of file
diff --git a/docs/source/pecan_middleware_debug.rst b/docs/source/pecan_middleware_debug.rst
deleted file mode 100644
index 51dc1a2..0000000
--- a/docs/source/pecan_middleware_debug.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-.. _pecan_middleware_debug:
-
-:mod:`pecan.middleware.debug` -- Pecan Debugging Middleware
-===========================================================
-
-.. automodule:: pecan.middleware.debug
- :members:
- :show-inheritance:
-
diff --git a/docs/source/pecan_rest.rst b/docs/source/pecan_rest.rst
deleted file mode 100644
index 7b31bfd..0000000
--- a/docs/source/pecan_rest.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. _pecan_rest:
-
-:mod:`pecan.rest` -- Pecan ``REST`` Controller
-==============================================
-
-The :mod:`pecan.rest` module includes support for writing fully
-``RESTful`` controllers in your Pecan application.
-
-.. automodule:: pecan.rest
- :members:
- :show-inheritance: \ No newline at end of file
diff --git a/docs/source/pecan_routing.rst b/docs/source/pecan_routing.rst
deleted file mode 100644
index 47ffb6d..0000000
--- a/docs/source/pecan_routing.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. _pecan_routing:
-
-:mod:`pecan.routing` -- Pecan Routing
-=====================================
-
-The :mod:`pecan.routing` module is the basis for all object-dispatch
-routing in Pecan.
-
-.. automodule:: pecan.routing
- :members:
- :show-inheritance: \ No newline at end of file
diff --git a/docs/source/pecan_secure.rst b/docs/source/pecan_secure.rst
deleted file mode 100644
index f2c288a..0000000
--- a/docs/source/pecan_secure.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-.. _pecan_secure:
-
-:mod:`pecan.secure` -- Pecan Secure Controllers
-===============================================
-
-The :mod:`pecan.secure` module includes a basic framework for building
-security into your applications.
-
-.. automodule:: pecan.secure
- :members:
- :show-inheritance:
-
-.. autoclass:: pecan.secure.SecureControllerBase
- :members:
- :show-inheritance:
-
-.. autoclass:: pecan.secure.SecureController
- :members:
- :show-inheritance:
diff --git a/docs/source/pecan_templating.rst b/docs/source/pecan_templating.rst
deleted file mode 100644
index 17bb558..0000000
--- a/docs/source/pecan_templating.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-.. _pecan_templating:
-
-:mod:`pecan.templating` -- Pecan Templating
-===========================================
-
-The :mod:`pecan.templating` module includes support for a variety of
-templating engines, plus the ability to create your own template
-engines.
-
-.. automodule:: pecan.templating
- :members:
- :show-inheritance: \ No newline at end of file
diff --git a/docs/source/pecan_testing.rst b/docs/source/pecan_testing.rst
deleted file mode 100644
index 09d76aa..0000000
--- a/docs/source/pecan_testing.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. _pecan_testing:
-
-:mod:`pecan.testing` -- Pecan Testing
-===========================================
-
-The :mod:`pecan.testing` module includes fixtures to help test Pecan
-applications.
-
-.. automodule:: pecan.testing
- :members:
- :show-inheritance:
diff --git a/docs/source/pecan_util.rst b/docs/source/pecan_util.rst
deleted file mode 100644
index f629d27..0000000
--- a/docs/source/pecan_util.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-.. _pecan_util:
-
-:mod:`pecan.util` -- Pecan Utils
-================================
-
-The :mod:`pecan.util` module includes utility functions for Pecan.
-
-.. automodule:: pecan.util
- :members:
- :show-inheritance: \ No newline at end of file
diff --git a/docs/source/quick_start.rst b/docs/source/quick_start.rst
deleted file mode 100644
index 45915fa..0000000
--- a/docs/source/quick_start.rst
+++ /dev/null
@@ -1,297 +0,0 @@
-.. _quick_start:
-
-Creating Your First Pecan Application
-=====================================
-
-Let's create a small sample project with Pecan.
-
-.. note::
- This guide does not cover the installation of Pecan. If you need
- instructions for installing Pecan, refer to :ref:`installation`.
-
-.. _app_template:
-
-Base Application Template
--------------------------
-
-Pecan includes a basic template for starting a new project. From your
-shell, type::
-
- $ pecan create test_project
-
-This example uses *test_project* as your project name, but you can replace
-it with any valid Python package name you like.
-
-Go ahead and change into your newly created project directory.::
-
- $ cd test_project
-
-You'll want to deploy it in "development mode", such that it’s
-available on :mod:`sys.path`, yet can still be edited directly from its
-source distribution::
-
- $ python setup.py develop
-
-Your new project contain these files::
-
- $ ls
-
- ├── MANIFEST.in
- ├── config.py
- ├── public
- │   ├── css
- │   │   └── style.css
- │   └── images
- ├── setup.cfg
- ├── setup.py
- └── test_project
-    ├── __init__.py
-    ├── app.py
-    ├── controllers
-    │   ├── __init__.py
-    │   └── root.py
-    ├── model
-    │   └── __init__.py
-    ├── templates
-    │   ├── error.html
-    │   ├── index.html
-    │   └── layout.html
-    └── tests
-    ├── __init__.py
-    ├── config.py
-    ├── test_functional.py
-    └── test_units.py
-
-The number of files and directories may vary based on the version of
-Pecan, but the above structure should give you an idea of what to
-expect.
-
-Let's review the files created by the template.
-
-**public**
- All your static files (like CSS, Javascript, and images) live here.
- Pecan comes with a simple file server that serves these static files
- as you develop.
-
-Pecan application structure generally follows the MVC_ pattern. The
-directories under ``test_project`` encompass your models, controllers
-and templates.
-
-.. _MVC: http://en.wikipedia.org/wiki/Model–view–controller
-
-**test_project/controllers**
- The container directory for your controller files.
-**test_project/templates**
- All your templates go in here.
-**test_project/model**
- Container for your model files.
-
-Finally, a directory to house unit and integration tests:
-
-**test_project/tests**
- All of the tests for your application.
-
-The ``test_project/app.py`` file controls how the Pecan application will be
-created. This file must contain a :func:`setup_app` function which returns the
-WSGI application object. Generally you will not need to modify the ``app.py``
-file provided by the base application template unless you need to customize
-your app in a way that cannot be accomplished using config. See
-:ref:`python_based_config` below.
-
-To avoid unneeded dependencies and to remain as flexible as possible,
-Pecan doesn't impose any database or ORM (`Object Relational
-Mapper`_). If your project will interact with a database, you can add
-code to ``model/__init__.py`` to load database bindings from your
-configuration file and define tables and ORM definitions.
-
-.. _Object Relational Mapper: http://en.wikipedia.org/wiki/Object-relational_mapping
-
-.. _running_application:
-
-Running the Application
------------------------
-
-The base project template creates the configuration file with the
-basic settings you need to run your Pecan application in
-``config.py``. This file includes the host and port to run the server
-on, the location where your controllers and templates are stored on
-disk, and the name of the directory containing any static files.
-
-If you just run :command:`pecan serve`, passing ``config.py`` as the
-configuration file, it will bring up the development server and serve
-the app::
-
- $ pecan serve config.py
- Starting server in PID 000.
- serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
-
-The location for the configuration file and the argument itself are very
-flexible - you can pass an absolute or relative path to the file.
-
-.. _python_based_config:
-
-Python-Based Configuration
---------------------------
-For ease of use, Pecan configuration files are pure Python--they're even saved
-as ``.py`` files.
-
-This is how your default (generated) configuration file should look::
-
- # Server Specific Configurations
- server = {
- 'port': '8080',
- 'host': '0.0.0.0'
- }
-
- # Pecan Application Configurations
- app = {
- 'root': '${package}.controllers.root.RootController',
- 'modules': ['${package}'],
- 'static_root': '%(confdir)s/public',
- 'template_path': '%(confdir)s/${package}/templates',
- 'debug': True,
- 'errors': {
- '404': '/error/404',
- '__force_dict__': True
- }
- }
-
- logging = {
- 'loggers': {
- 'root' : {'level': 'INFO', 'handlers': ['console']},
- '${package}': {'level': 'DEBUG', 'handlers': ['console']}
- },
- 'handlers': {
- 'console': {
- 'level': 'DEBUG',
- 'class': 'logging.StreamHandler',
- 'formatter': 'simple'
- }
- },
- 'formatters': {
- 'simple': {
- 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]'
- '[%(threadName)s] %(message)s')
- }
- }
- }
-
- # Custom Configurations must be in Python dictionary format::
- #
- # foo = {'bar':'baz'}
- #
- # All configurations are accessible at::
- # pecan.conf
-
-You can also add your own configuration as Python dictionaries.
-
-There's a lot to cover here, so we'll come back to configuration files in
-a later chapter (:ref:`Configuration`).
-
-
-The Application Root
---------------------
-
-The **Root Controller** is the entry point for your application. You
-can think of it as being analogous to your application's root URL path
-(in our case, ``http://localhost:8080/``).
-
-This is how it looks in the project template
-(``test_project.controllers.root.RootController``)::
-
- from pecan import expose
- from webob.exc import status_map
-
-
- class RootController(object):
-
- @expose(generic=True, template='index.html')
- def index(self):
- return dict()
-
- @index.when(method='POST')
- def index_post(self, q):
- redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % q)
-
- @expose('error.html')
- def error(self, status):
- try:
- status = int(status)
- except ValueError:
- status = 0
- message = getattr(status_map.get(status), 'explanation', '')
- return dict(status=status, message=message)
-
-
-You can specify additional classes and methods if you need to do so, but for
-now, let's examine the sample project, controller by controller::
-
- @expose(generic=True, template='index.html')
- def index(self):
- return dict()
-
-The :func:`index` method is marked as *publicly available* via the
-:func:`~pecan.decorators.expose` decorator (which in turn uses the
-``index.html`` template) at the root of the application
-(http://127.0.0.1:8080/), so any HTTP ``GET`` that hits the root of your
-application (``/``) will be routed to this method.
-
-Notice that the :func:`index` method returns a Python dictionary. This dictionary
-is used as a namespace to render the specified template (``index.html``) into
-HTML, and is the primary mechanism by which data is passed from controller to
-template.
-
-::
-
- @index.when(method='POST')
- def index_post(self, q):
- redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % q)
-
-The :func:`index_post` method receives one HTTP ``POST`` argument (``q``). Because
-the argument ``method`` to :func:`@index.when` has been set to ``'POST'``, any
-HTTP ``POST`` to the application root (in the example project, a form
-submission) will be routed to this method.
-
-::
-
- @expose('error.html')
- def error(self, status):
- try:
- status = int(status)
- except ValueError:
- status = 0
- message = getattr(status_map.get(status), 'explanation', '')
- return dict(status=status, message=message)
-
-Finally, we have the :func:`error` method, which allows the application to display
-custom pages for certain HTTP errors (``404``, etc...).
-
-Running the Tests For Your Application
---------------------------------------
-
-Your application comes with a few example tests that you can run, replace, and
-add to. To run them::
-
- $ python setup.py test -q
- running test
- running egg_info
- writing requirements to sam.egg-info/requires.txt
- writing sam.egg-info/PKG-INFO
- writing top-level names to sam.egg-info/top_level.txt
- writing dependency_links to sam.egg-info/dependency_links.txt
- reading manifest file 'sam.egg-info/SOURCES.txt'
- reading manifest template 'MANIFEST.in'
- writing manifest file 'sam.egg-info/SOURCES.txt'
- running build_ext
- ....
- ----------------------------------------------------------------------
- Ran 4 tests in 0.009s
-
- OK
-
-The tests themselves can be found in the ``tests`` module in your project.
-
-Deploying to a Web Server
--------------------------
-
-Ready to deploy your new Pecan app? Take a look at :ref:`deployment`.
diff --git a/docs/source/reload.rst b/docs/source/reload.rst
deleted file mode 100644
index dfae883..0000000
--- a/docs/source/reload.rst
+++ /dev/null
@@ -1,28 +0,0 @@
-:orphan:
-
-#reload
-Reloading Automatically as Files Change
----------------------------------------
-
-Pausing to restart your development server as you work can be interruptive, so
-:command:`pecan serve` provides a ``--reload`` flag to make life easier.
-
-To provide this functionality, Pecan makes use of the Python
-`watchdog <https://pypi.python.org/pypi/watchdog>`_ library. You'll need to
-install it for development use before continuing::
-
- $ pip install watchdog
- Downloading/unpacking watchdog
- ...
- Successfully installed watchdog
-
-::
-
- $ pecan serve --reload config.py
- Monitoring for changes...
- Starting server in PID 000.
- serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
-
-As you work, Pecan will listen for any file or directory modification
-events in your project and silently restart your server process in the
-background.
diff --git a/docs/source/rest.rst b/docs/source/rest.rst
deleted file mode 100644
index 6f6a01b..0000000
--- a/docs/source/rest.rst
+++ /dev/null
@@ -1,222 +0,0 @@
-.. _rest:
-
-Writing RESTful Web Services with Generic Controllers
-=====================================================
-
-Pecan simplifies RESTful web services by providing a way to overload URLs based
-on the request method. For most API's, the use of `generic controller`
-definitions give you everything you need to build out robust RESTful
-interfaces (and is the *recommended* approach to writing RESTful web services
-in pecan):
-
-::
-
- from pecan import abort, expose
-
- # Note: this is *not* thread-safe. In real life, use a persistent data store.
- BOOKS = {
- '0': 'The Last of the Mohicans',
- '1': 'Catch-22'
- }
-
-
- class BookController(object):
-
- def __init__(self, id_):
- self.id_ = id_
- assert self.book
-
- @property
- def book(self):
- if self.id_ in BOOKS:
- return dict(id=self.id_, name=BOOKS[self.id_])
- abort(404)
-
- # HTTP GET /<id>/
- @expose(generic=True, template='json')
- def index(self):
- return self.book
-
- # HTTP PUT /<id>/
- @index.when(method='PUT', template='json')
- def index_PUT(self, **kw):
- BOOKS[self.id_] = kw['name']
- return self.book
-
- # HTTP DELETE /<id>/
- @index.when(method='DELETE', template='json')
- def index_DELETE(self):
- del BOOKS[self.id_]
- return dict()
-
-
- class RootController(object):
-
- @expose()
- def _lookup(self, id_, *remainder):
- return BookController(id_), remainder
-
- # HTTP GET /
- @expose(generic=True, template='json')
- def index(self):
- return [dict(id=k, name=v) for k, v in BOOKS.items()]
-
- # HTTP POST /
- @index.when(method='POST', template='json')
- def index_POST(self, **kw):
- id_ = len(BOOKS)
- BOOKS[id_] = kw['name']
- return dict(id=id_, name=kw['name'])
-
-
-Writing RESTful Web Services with RestController
-================================================
-
-.. _TurboGears2: http://turbogears.org
-
-For compatability with the TurboGears2_ library, Pecan also provides
-a class-based solution to RESTful routing, :class:`~pecan.rest.RestController`:
-
-::
-
- from pecan import expose
- from pecan.rest import RestController
-
- from mymodel import Book
-
- class BooksController(RestController):
-
- @expose()
- def get(self, id):
- book = Book.get(id)
- if not book:
- abort(404)
- return book.title
-
-URL Mapping
------------
-
-By default, :class:`~pecan.rest.RestController` routes as follows:
-
-+-----------------+--------------------------------------------------------------+--------------------------------------------+
-| Method | Description | Example Method(s) / URL(s) |
-+=================+==============================================================+============================================+
-| get_one | Display one record. | GET /books/1 |
-+-----------------+--------------------------------------------------------------+--------------------------------------------+
-| get_all | Display all records in a resource. | GET /books/ |
-+-----------------+--------------------------------------------------------------+--------------------------------------------+
-| get | A combo of get_one and get_all. | GET /books/ |
-| | +--------------------------------------------+
-| | | GET /books/1 |
-+-----------------+--------------------------------------------------------------+--------------------------------------------+
-| new | Display a page to create a new resource. | GET /books/new |
-+-----------------+--------------------------------------------------------------+--------------------------------------------+
-| edit | Display a page to edit an existing resource. | GET /books/1/edit |
-+-----------------+--------------------------------------------------------------+--------------------------------------------+
-| post | Create a new record. | POST /books/ |
-+-----------------+--------------------------------------------------------------+--------------------------------------------+
-| put | Update an existing record. | POST /books/1?_method=put |
-| | +--------------------------------------------+
-| | | PUT /books/1 |
-+-----------------+--------------------------------------------------------------+--------------------------------------------+
-| get_delete | Display a delete confirmation page. | GET /books/1/delete |
-+-----------------+--------------------------------------------------------------+--------------------------------------------+
-| delete | Delete an existing record. | POST /books/1?_method=delete |
-| | +--------------------------------------------+
-| | | DELETE /books/1 |
-+-----------------+--------------------------------------------------------------+--------------------------------------------+
-
-Pecan's :class:`~pecan.rest.RestController` uses the ``?_method=`` query string
-to work around the lack of support for the PUT and DELETE verbs when
-submitting forms in most current browsers.
-
-In addition to handling REST, the :class:`~pecan.rest.RestController` also
-supports the :meth:`index`, :meth:`_default`, and :meth:`_lookup`
-routing overrides.
-
-.. warning::
-
- If you need to override :meth:`_route`, make sure to call
- :func:`RestController._route` at the end of your custom method so
- that the REST routing described above still occurs.
-
-Nesting ``RestController``
----------------------------
-
-:class:`~pecan.rest.RestController` instances can be nested so that child
-resources receive the parameters necessary to look up parent resources.
-
-For example::
-
- from pecan import expose
- from pecan.rest import RestController
-
- from mymodel import Author, Book
-
- class BooksController(RestController):
-
- @expose()
- def get(self, author_id, id):
- author = Author.get(author_id)
- if not author_id:
- abort(404)
- book = author.get_book(id)
- if not book:
- abort(404)
- return book.title
-
- class AuthorsController(RestController):
-
- books = BooksController()
-
- @expose()
- def get(self, id):
- author = Author.get(id)
- if not author:
- abort(404)
- return author.name
-
- class RootController(object):
-
- authors = AuthorsController()
-
-Accessing ``/authors/1/books/2`` invokes :func:`BooksController.get` with
-``author_id`` set to ``1`` and ``id`` set to ``2``.
-
-To determine which arguments are associated with the parent resource, Pecan
-looks at the :func:`get_one` then :func:`get` method signatures, in that order,
-in the parent controller. If the parent resource takes a variable number of
-arguments, Pecan will pass it everything up to the child resource controller
-name (e.g., ``books`` in the above example).
-
-Defining Custom Actions
------------------------
-
-In addition to the default methods defined above, you can add additional
-behaviors to a :class:`~pecan.rest.RestController` by defining a special
-:attr:`_custom_actions`
-dictionary.
-
-For example::
-
- from pecan import expose
- from pecan.rest import RestController
-
- from mymodel import Book
-
- class BooksController(RestController):
-
- _custom_actions = {
- 'checkout': ['POST']
- }
-
- @expose()
- def checkout(self, id):
- book = Book.get(id)
- if not book:
- abort(404)
- book.checkout()
-
-:attr:`_custom_actions` maps method names to the list of valid HTTP
-verbs for those custom actions. In this case :func:`checkout` supports
-``POST``.
diff --git a/docs/source/routing.rst b/docs/source/routing.rst
deleted file mode 100644
index f099787..0000000
--- a/docs/source/routing.rst
+++ /dev/null
@@ -1,597 +0,0 @@
-.. _routing:
-
-Controllers and Routing
-=======================
-
-Pecan uses a routing strategy known as **object-dispatch** to map an
-HTTP request to a controller, and then the method to call.
-Object-dispatch begins by splitting the path into a list of components
-and then walking an object path, starting at the root controller. You
-can imagine your application's controllers as a tree of objects
-(branches of the object tree map directly to URL paths).
-
-Let's look at a simple bookstore application:
-
-::
-
- from pecan import expose
-
- class BooksController(object):
- @expose()
- def index(self):
- return "Welcome to book section."
-
- @expose()
- def bestsellers(self):
- return "We have 5 books in the top 10."
-
- class CatalogController(object):
- @expose()
- def index(self):
- return "Welcome to the catalog."
-
- books = BooksController()
-
- class RootController(object):
- @expose()
- def index(self):
- return "Welcome to store.example.com!"
-
- @expose()
- def hours(self):
- return "Open 24/7 on the web."
-
- catalog = CatalogController()
-
-A request for ``/catalog/books/bestsellers`` from the online store would
-begin with Pecan breaking the request up into ``catalog``, ``books``, and
-``bestsellers``. Next, Pecan would lookup ``catalog`` on the root
-controller. Using the ``catalog`` object, Pecan would then lookup
-``books``, followed by ``bestsellers``. What if the URL ends in a slash?
-Pecan will check for an ``index`` method on the last controller object.
-
-To illustrate further, the following paths:
-
-::
-
-    └── /
-    ├── /hours
-    └── /catalog
-    └── /catalog/books
-    └── /catalog/books/bestsellers
-
-route to the following controller methods:
-
-::
-
-    └── RootController.index
-    ├── RootController.hours
-    └── CatalogController.index
-    └── BooksController.index
-    └── BooksController.bestsellers
-
-Exposing Controllers
---------------------
-
-You tell Pecan which methods in a class are publically-visible via
-:func:`~pecan.decorators.expose`. If a method is *not* decorated with
-:func:`~pecan.decorators.expose`, Pecan will never route a request to it.
-:func:`~pecan.decorators.expose` accepts three optional parameters, some of
-which can impact routing and the content type of the response body.
-
-::
-
- from pecan import expose
-
- class RootController(object):
- @expose(
- template = None,
- content_type = 'text/html',
- generic = False
- )
- def hello(self):
- return 'Hello World'
-
-
-Let's look at an example using ``template`` and ``content_type``:
-
-::
-
- from pecan import expose
-
- class RootController(object):
- @expose('json')
- @expose('text_template.mako', content_type='text/plain')
- @expose('html_template.mako')
- def hello(self):
- return {'msg': 'Hello!'}
-
-You'll notice that we called :func:`~pecan.decorators.expose` three times, with
-different arguments.
-
-::
-
- @expose('json')
-
-The first tells Pecan to serialize the response namespace using JSON
-serialization when the client requests ``/hello.json``.
-
-::
-
- @expose('text_template.mako', content_type='text/plain')
-
-The second tells Pecan to use the ``text_template.mako`` template file when the
-client requests ``/hello.txt``.
-
-::
-
- @expose('html_template.mako')
-
-The third tells Pecan to use the ``html_template.mako`` template file when the
-client requests ``/hello.html``. If the client requests ``/hello``, Pecan will
-use the ``text/html`` content type by default.
-
-.. seealso::
-
- * :ref:`pecan_decorators`
-
-
-Specifying Explicit Path Segments
----------------------------------
-
-Occasionally, you may want to use a path segment in your routing that doesn't
-work with Pecan's declarative approach to routing because of restrictions in
-Python's syntax. For example, if you wanted to route for a path that includes
-dashes, such as ``/some-path/``, the following is *not* valid Python::
-
-
- class RootController(object):
-
- @pecan.expose()
- def some-path(self):
- return dict()
-
-To work around this, pecan allows you to specify an explicit path segment in
-the :func:`~pecan.decorators.expose` decorator::
-
- class RootController(object):
-
- @pecan.expose(route='some-path')
- def some_path(self):
- return dict()
-
-In this example, the pecan application will reply with an ``HTTP 200`` for
-requests made to ``/some-path/``, but requests made to ``/some_path/`` will
-yield an ``HTTP 404``.
-
-:func:`~pecan.routing.route` can also be used explicitly as an alternative to
-the ``route`` argument in :func:`~pecan.decorators.expose`::
-
- class RootController(object):
-
- @pecan.expose()
- def some_path(self):
- return dict()
-
- pecan.route('some-path', RootController.some_path)
-
-Routing to child controllers can be handled simliarly by utilizing
-:func:`~pecan.routing.route`::
-
-
- class ChildController(object):
-
- @pecan.expose()
- def child(self):
- return dict()
-
- class RootController(object):
- pass
-
- pecan.route(RootController, 'child-path', ChildController())
-
-In this example, the pecan application will reply with an ``HTTP 200`` for
-requests made to ``/child-path/child/``.
-
-
-Routing Based on Request Method
--------------------------------
-
-The ``generic`` argument to :func:`~pecan.decorators.expose` provides support for overloading URLs
-based on the request method. In the following example, the same URL can be
-serviced by two different methods (one for handling HTTP ``GET``, another for
-HTTP ``POST``) using `generic controllers`:
-
-::
-
- from pecan import expose
-
-
- class RootController(object):
-
- # HTTP GET /
- @expose(generic=True, template='json')
- def index(self):
- return dict()
-
- # HTTP POST /
- @index.when(method='POST', template='json')
- def index_POST(self, **kw):
- uuid = create_something()
- return dict(uuid=uuid)
-
-
-
-
-Pecan's Routing Algorithm
--------------------------
-
-Sometimes, the standard object-dispatch routing isn't adequate to properly
-route a URL to a controller. Pecan provides several ways to short-circuit
-the object-dispatch system to process URLs with more control, including the
-special :func:`_lookup`, :func:`_default`, and :func:`_route` methods. Defining
-these methods on your controller objects provides additional flexibility for
-processing all or part of a URL.
-
-
-Routing to Subcontrollers with ``_lookup``
-------------------------------------------
-
-The :func:`_lookup` special method provides a way to process a portion of a URL,
-and then return a new controller object to route to for the remainder.
-
-A :func:`_lookup` method may accept one or more arguments, segments
-of the URL path to be processed (split on
-``/``). :func:`_lookup` should also take variable positional arguments
-representing the rest of the path, and it should include any portion
-of the path it does not process in its return value. The example below
-uses a ``*remainder`` list which will be passed to the returned
-controller when the object-dispatch algorithm continues.
-
-In addition to being used for creating controllers dynamically,
-:func:`_lookup` is called as a last resort, when no other controller
-method matches the URL and there is no :func:`_default` method.
-
-::
-
- from pecan import expose, abort
- from somelib import get_student_by_name
-
- class StudentController(object):
- def __init__(self, student):
- self.student = student
-
- @expose()
- def name(self):
- return self.student.name
-
- class RootController(object):
- @expose()
- def _lookup(self, primary_key, *remainder):
- student = get_student_by_primary_key(primary_key)
- if student:
- return StudentController(student), remainder
- else:
- abort(404)
-
-An HTTP GET request to ``/8/name`` would return the name of the student
-where ``primary_key == 8``.
-
-Falling Back with ``_default``
-------------------------------
-
-The :func:`_default` method is called as a last resort when no other controller
-methods match the URL via standard object-dispatch.
-
-::
-
- from pecan import expose
-
- class RootController(object):
- @expose()
- def english(self):
- return 'hello'
-
- @expose()
- def french(self):
- return 'bonjour'
-
- @expose()
- def _default(self):
- return 'I cannot say hello in that language'
-
-
-In the example above, a request to ``/spanish`` would route to
-:func:`RootController._default`.
-
-
-Defining Customized Routing with ``_route``
--------------------------------------------
-
-The :func:`_route` method allows a controller to completely override the routing
-mechanism of Pecan. Pecan itself uses the :func:`_route` method to implement its
-:class:`~pecan.rest.RestController`. If you want to design an alternative
-routing system on top of Pecan, defining a base controller class that defines
-a :func:`_route` method will enable you to have total control.
-
-
-Interacting with the Request and Response Object
-================================================
-
-For every HTTP request, Pecan maintains a :ref:`thread-local reference
-<contextlocals>` to the request and response object, ``pecan.request`` and
-``pecan.response``. These are instances of :class:`pecan.Request`
-and :class:`pecan.Response`, respectively, and can be interacted with
-from within Pecan controller code::
-
- @pecan.expose()
- def login(self):
- assert pecan.request.path == '/login'
- username = pecan.request.POST.get('username')
- password = pecan.request.POST.get('password')
-
- pecan.response.status = 403
- pecan.response.text = 'Bad Login!'
-
-While Pecan abstracts away much of the need to interact with these objects
-directly, there may be situations where you want to access them, such as:
-
-* Inspecting components of the URI
-* Determining aspects of the request, such as the user's IP address, or the
- referer header
-* Setting specific response headers
-* Manually rendering a response body
-
-
-Specifying a Custom Response
-----------------------------
-
-Set a specific HTTP response code (such as ``203 Non-Authoritative Information``) by
-modifying the ``status`` attribute of the response object.
-
-::
-
- from pecan import expose, response
-
- class RootController(object):
-
- @expose('json')
- def hello(self):
- response.status = 203
- return {'foo': 'bar'}
-
-Use the utility function :func:`~pecan.core.abort` to raise HTTP errors.
-
-::
-
- from pecan import expose, abort
-
- class RootController(object):
-
- @expose('json')
- def hello(self):
- abort(404)
-
-
-:func:`~pecan.core.abort` raises an instance of
-:class:`~webob.exc.WSGIHTTPException` which is used by Pecan to render
-default response bodies for HTTP errors. This exception is stored in
-the WSGI request environ at ``pecan.original_exception``, where it
-can be accessed later in the request cycle (by, for example, other
-middleware or :ref:`errors`).
-
-If you'd like to return an explicit response, you can do so using
-:class:`~pecan.core.Response`:
-
-::
-
- from pecan import expose, Response
-
- class RootController(object):
-
- @expose()
- def hello(self):
- return Response('Hello, World!', 202)
-
-
-
-Extending Pecan's Request and Response Object
----------------------------------------------
-
-The request and response implementations provided by WebOb are powerful, but
-at times, it may be useful to extend application-specific behavior onto your
-request and response (such as specialized parsing of request headers or
-customized response body serialization). To do so, define custom classes that
-inherit from ``pecan.Request`` and ``pecan.Response``, respectively::
-
- class MyRequest(pecan.Request):
- pass
-
- class MyResponse(pecan.Response):
- pass
-
-and modify your application configuration to use them::
-
- from myproject import MyRequest, MyResponse
-
- app = {
- 'root' : 'project.controllers.root.RootController',
- 'modules' : ['project'],
- 'static_root' : '%(confdir)s/public',
- 'template_path' : '%(confdir)s/project/templates',
- 'request_cls': MyRequest,
- 'response_cls': MyResponse
- }
-
-Mapping Controller Arguments
-----------------------------
-
-In Pecan, HTTP ``GET`` and ``POST`` variables that are not consumed
-during the routing process can be passed onto the controller method as
-arguments.
-
-Depending on the signature of the method, these arguments can be mapped
-explicitly to arguments:
-
-::
-
- from pecan import expose
-
- class RootController(object):
- @expose()
- def index(self, arg):
- return arg
-
- @expose()
- def kwargs(self, **kwargs):
- return str(kwargs)
-
-::
-
- $ curl http://localhost:8080/?arg=foo
- foo
- $ curl http://localhost:8080/kwargs?a=1&b=2&c=3
- {u'a': u'1', u'c': u'3', u'b': u'2'}
-
-or can be consumed positionally:
-
-::
-
- from pecan import expose
-
- class RootController(object):
- @expose()
- def args(self, *args):
- return ','.join(args)
-
-::
-
- $ curl http://localhost:8080/args/one/two/three
- one,two,three
-
-The same effect can be achieved with HTTP ``POST`` body variables:
-
-::
-
- from pecan import expose
-
- class RootController(object):
- @expose()
- def index(self, arg):
- return arg
-
-::
-
- $ curl -X POST "http://localhost:8080/" -H "Content-Type: application/x-www-form-urlencoded" -d "arg=foo"
- foo
-
-Static File Serving
--------------------
-
-Because Pecan gives you direct access to the underlying
-:class:`~webob.request.Request`, serving a static file download is as simple as
-setting the WSGI ``app_iter`` and specifying the content type::
-
- import os
- from random import choice
-
- from webob.static import FileIter
-
- from pecan import expose, response
-
-
- class RootController(object):
-
- @expose(content_type='image/gif')
- def gifs(self):
- filepath = choice((
- "/path/to/funny/gifs/catdance.gif",
- "/path/to/funny/gifs/babydance.gif",
- "/path/to/funny/gifs/putindance.gif"
- ))
- f = open(filepath, 'rb')
- response.app_iter = FileIter(f)
- response.headers[
- 'Content-Disposition'
- ] = 'attachment; filename="%s"' % os.path.basename(f.name)
-
-If you don't know the content type ahead of time (for example, if you're
-retrieving files and their content types from a data store), you can specify
-it via ``response.headers`` rather than in the :func:`~pecan.decorators.expose`
-decorator::
-
- import os
- from mimetypes import guess_type
-
- from webob.static import FileIter
-
- from pecan import expose, response
-
-
- class RootController(object):
-
- @expose()
- def download(self):
- f = open('/path/to/some/file', 'rb')
- response.app_iter = FileIter(f)
- response.headers['Content-Type'] = guess_type(f.name)
- response.headers[
- 'Content-Disposition'
- ] = 'attachment; filename="%s"' % os.path.basename(f.name)
-
-Handling File Uploads
----------------------
-
-Pecan makes it easy to handle file uploads via standard multipart forms. Simply
-define your form with a file input:
-
-.. code-block:: html
-
- <form action="/upload" method="POST" enctype="multipart/form-data">
- <input type="file" name="file" />
- <button type="submit">Upload</button>
- </form>
-
-You can then read the uploaded file off of the request object in your
-application's controller:
-
-::
-
- from pecan import expose, request
-
- class RootController(object):
- @expose()
- def upload(self):
- assert isinstance(request.POST['file'], cgi.FieldStorage)
- data = request.POST['file'].file.read()
-
-Thread-Safe Per-Request Storage
--------------------------------
-
-For convenience, Pecan provides a Python dictionary on every request which can
-be accessed and modified in a thread-safe manner throughout the life-cycle of
-an individual request::
-
- pecan.request.context['current_user'] = some_user
- print pecan.request.context.items()
-
-This is particularly useful in situations where you want to store
-metadata/context about a request (e.g., in middleware, or per-routing hooks)
-and access it later (e.g., in controller code).
-
-For more fine-grained control of the request, the underlying WSGI environ for
-a given Pecan request can be accessed and modified via
-``pecan.request.environ``.
-
-
-Helper Functions
-----------------
-
-Pecan also provides several useful helper functions for moving between
-different routes. The :func:`~pecan.core.redirect` function allows you to issue
-internal or ``HTTP 302`` redirects.
-
-.. seealso::
-
- The :func:`redirect` utility, along with several other useful
- helpers, are documented in :ref:`pecan_core`.
diff --git a/docs/source/secure_controller.rst b/docs/source/secure_controller.rst
deleted file mode 100644
index baa6cbe..0000000
--- a/docs/source/secure_controller.rst
+++ /dev/null
@@ -1,263 +0,0 @@
-.. _secure_controller:
-
-Security and Authentication
-===========================
-
-Pecan provides no out-of-the-box support for authentication, but it
-does give you the necessary tools to handle authentication and
-authorization as you see fit.
-
-``secure`` Decorator Basics
----------------------------
-
-You can wrap entire controller subtrees *or* individual method calls
-with access controls using the :func:`~pecan.secure.secure` decorator.
-
-To decorate a method, use one argument::
-
- secure('<check_permissions_method_name>')
-
-To secure a class, invoke with two arguments::
-
- secure(object_instance, '<check_permissions_method_name>')
-
-::
-
- from pecan import expose
- from pecan.secure import secure
-
- class HighlyClassifiedController(object):
- pass
-
- class UnclassifiedController(object):
- pass
-
- class RootController(object):
-
- @classmethod
- def check_permissions(cls):
- if user_is_admin():
- return True
- return False
-
- @expose()
- def index(self):
- #
- # This controller is unlocked to everyone,
- # and will not run any security checks.
- #
- return dict()
-
- @secure('check_permissions')
- @expose()
- def topsecret(self):
- #
- # This controller is top-secret, and should
- # only be reachable by administrators.
- #
- return dict()
-
- highly_classified = secure(HighlyClassifiedController(), 'check_permissions')
- unclassified = UnclassifiedController()
-
-
-``SecureController``
---------------------
-
-Alternatively, the same functionality can also be accomplished by
-subclassing Pecan's :class:`~pecan.secure.SecureController`. Implementations of
-:class:`~pecan.secure.SecureController` should extend the
-:meth:`~pecan.secure.SecureControllerBase.check_permissions` class method to
-return ``True`` if the user has permissions to the controller branch and
-``False`` if they do not.
-
-::
-
- from pecan import expose
- from pecan.secure import SecureController, unlocked
-
- class HighlyClassifiedController(object):
- pass
-
- class UnclassifiedController(object):
- pass
-
- class RootController(SecureController):
-
- @classmethod
- def check_permissions(cls):
- if user_is_admin():
- return True
- return False
-
- @expose()
- @unlocked
- def index(self):
- #
- # This controller is unlocked to everyone,
- # and will not run any security checks.
- #
- return dict()
-
- @expose()
- def topsecret(self):
- #
- # This controller is top-secret, and should
- # only be reachable by administrators.
- #
- return dict()
-
- highly_classified = HighlyClassifiedController()
- unclassified = unlocked(UnclassifiedController())
-
-
-Also note the use of the :func:`~pecan.secure.unlocked` decorator in the above
-example, which can be used similarly to explicitly unlock a controller for
-public access without any security checks.
-
-
-Writing Authentication/Authorization Methods
---------------------------------------------
-
-The :meth:`~pecan.secure.SecureControllerBase.check_permissions` method should
-be used to determine user authentication and authorization. The code you
-implement here could range from simple session assertions (the existing user is
-authenticated as an administrator) to connecting to an LDAP service.
-
-
-More on ``secure``
-------------------
-
-The :func:`~pecan.secure.secure` method has several advanced uses that allow
-you to create robust security policies for your application.
-
-First, you can pass via a string the name of either a class method or an
-instance method of the controller to use as the
-:meth:`~pecan.secure.SecureControllerBase.check_permissions` method. Instance
-methods are particularly useful if you wish to authorize access to attributes
-of a model instance. Consider the following example of a basic virtual
-filesystem.
-
-::
-
- from pecan import expose
- from pecan.secure import secure
-
- from myapp.session import get_current_user
- from myapp.model import FileObject
-
- class FileController(object):
- def __init__(self, name):
- self.file_object = FileObject(name)
-
- def read_access(self):
- self.file_object.read_access(get_current_user())
-
- def write_access(self):
- self.file_object.write_access(get_current_user())
-
- @secure('write_access')
- @expose()
- def upload_file(self):
- pass
-
- @secure('read_access')
- @expose()
- def download_file(self):
- pass
-
- class RootController(object):
- @expose()
- def _lookup(self, name, *remainder):
- return FileController(name), remainder
-
-
-The :func:`~pecan.secure.secure` method also accepts a function argument. When
-passing a function, make sure that the function is imported from another
-file or defined in the same file before the class definition, otherwise
-you will likely get error during module import.
-
-::
-
- from pecan import expose
- from pecan.secure import secure
-
- from myapp.auth import user_authenitcated
-
- class RootController(object):
- @secure(user_authenticated)
- @expose()
- def index(self):
- return 'Logged in'
-
-
-You can also use the :func:`~pecan.secure.secure` method to change the behavior
-of a :class:`~pecan.secure.SecureController`. Decorating a method or wrapping
-a subcontroller tells Pecan to use another security function other than the
-default controller method. This is useful for situations where you want
-a different level or type of security.
-
-::
-
- from pecan import expose
- from pecan.secure import SecureController, secure
-
- from myapp.auth import user_authenticated, admin_user
-
- class ApiController(object):
- pass
-
- class RootController(SecureController):
- @classmethod
- def check_permissions(cls):
- return user_authenticated()
-
- @classmethod
- def check_api_permissions(cls):
- return admin_user()
-
- @expose()
- def index(self):
- return 'logged in user'
-
- api = secure(ApiController(), 'check_api_permissions')
-
-In the example above, pecan will *only* call :func:`admin_user` when a request is
-made for ``/api/``.
-
-
-Multiple Secure Controllers
----------------------------
-
-Secure controllers can be nested to provide increasing levels of
-security on subcontrollers. In the example below, when a request is
-made for ``/admin/index/``, Pecan first calls
-:func:`~pecan.secure.SecureControllerBase.check_permissions` on the
-:class:`RootController` and then
-calls :func:`~pecan.secure.SecureControllerBase.check_permissions` on the
-:class:`AdminController`.
-
-::
-
- from pecan import expose
- from pecan.secure import SecureController
-
- from myapp.auth import user_logged_in, is_admin
-
- class AdminController(SecureController):
- @classmethod
- def check_permissions(cls):
- return is_admin()
-
- @expose()
- def index(self):
- return 'admin dashboard'
-
- class RootController(SecureController):
- @classmethod
- def check_permissions(cls):
- return user_logged_in
-
- @expose()
- def index(self):
- return 'user dashboard'
diff --git a/docs/source/sessions.rst b/docs/source/sessions.rst
deleted file mode 100644
index e6be814..0000000
--- a/docs/source/sessions.rst
+++ /dev/null
@@ -1,42 +0,0 @@
-.. _session:
-
-Working with Sessions and User Authentication
-=============================================
-
-Pecan provides no opinionated support for managing user sessions,
-but it's easy to hook into your session framework of choice with minimal
-effort.
-
-This article details best practices for integrating the popular session
-framework, `Beaker <http://beaker.groovie.org>`_, into your Pecan project.
-
-Setting up Session Management
------------------------------
-
-There are several approaches that can be taken to set up session management.
-One approach is WSGI middleware. Another is Pecan :ref:`hooks`.
-
-Here's an example of wrapping your WSGI application with Beaker's
-:class:`~beaker.middleware.SessionMiddleware` in your project's ``app.py``.
-
-::
-
- from pecan import conf, make_app
- from beaker.middleware import SessionMiddleware
- from test_project import model
-
- app = make_app(
- ...
- )
- app = SessionMiddleware(app, conf.beaker)
-
-And a corresponding dictionary in your configuration file.
-
-::
-
- beaker = {
- 'session.key' : 'sessionkey',
- 'session.type' : 'cookie',
- 'session.validate_key' : '05d2175d1090e31f42fa36e63b8d2aad',
- '__force_dict__' : True
- }
diff --git a/docs/source/simple_ajax.rst b/docs/source/simple_ajax.rst
deleted file mode 100644
index fcebb3a..0000000
--- a/docs/source/simple_ajax.rst
+++ /dev/null
@@ -1,286 +0,0 @@
-.. _simple_ajax:
-
-Example Application: Simple AJAX
-================================
-
-This guide will walk you through building a simple Pecan web application that uses AJAX to fetch JSON data from a server.
-
-Project Setup
--------------
-
-First, you'll need to install Pecan:
-
-::
-
-$ pip install pecan
-
-Use Pecan's basic template support to start a new project:
-
-::
-
-$ pecan create myajax
-$ cd myajax
-
-Install the new project in development mode:
-
-::
-
-$ python setup.py develop
-
-Adding JavaScript AJAX Support
-------------------------------
-
-For this project we will need to add `jQuery <http://jquery.com/>`_ support. To add jQuery go into the ``templates`` folder and edit the ``layout.html`` file.
-
-Adding jQuery support is easy, we actually only need one line of code:
-
-.. code-block:: html
-
- <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
-
-The JavaScript to make the AJAX call is a little more in depth but shouldn't be unfamiliar if you've ever worked with jQuery before.
-
-The ``layout.html`` file will look like this:
-
-.. code-block:: html
-
- <html>
- <head>
- <title>${self.title()}</title>
- ${self.style()}
- ${self.javascript()}
- </head>
- <body>
- ${self.body()}
- </body>
- </html>
-
- <%def name="title()">
- Default Title
- </%def>
-
- <%def name="style()">
- <link rel="stylesheet" type="text/css" media="screen" href="/css/style.css" />
- </%def>
-
- <%def name="javascript()">
- <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
- <script language="text/javascript" src="/javascript/shared.js"></script>
-
- <script>
- function onSuccess(data, status, jqXHR) {
- // Use a template or something here instead
- // Just for demo purposes
- $("#result").html("<div>" +
- "<p></p><strong>Project Name: " + data.name + "</strong></p>" +
- "<p>Project License: " + data.licensing + "</p>" +
- "<p><a href='" + data.repository + "'>Project Repository: " + data.repository + "</a></p>" +
- "<p><a href='" + data.documentation + "'>Project Documentation: " + data.documentation + "</a></p>" +
- "</div>");
- }
-
- function onError(jqXHR, textStatus, errorThrown) {
- alert('HTTP Status Code: ' + jqXHR.status + ', ' + errorThrown);
- }
-
- $(document).ready(function () {
- $("#submit").click(function () {
- $.ajax({
- url: "/projects/",
- data: "id=" + $("#projects").val(),
- contentType: 'application/json',
- dataType: 'json',
- success: onSuccess,
- error: onError
- });
-
- return false;
- });
- });
- </script>
- </%def>
-
-**What did we just do?**
-
-#. In the ``head`` section we added jQuery support via the `Google CDN <https://developers.google.com/speed/libraries/devguide>`_
-#. Added JavaScript to make an AJAX call to the server via an HTTP ``GET`` passing in the ``id`` of the project to fetch more information on
-#. Once the ``onSuccess`` event is triggered by the returning data we take that and display it on the web page below the controls
-
-Adding Additional HTML
-----------------------
-
-Let's edit the ``index.html`` file next. We will add HTML to support the AJAX interaction between the web page and Pecan. Modify ``index.html`` to look like this:
-
-.. code-block:: html
-
- <%inherit file="layout.html" />
-
- <%def name="title()">
- Welcome to Pecan!
- </%def>
-
- <header>
- <h1><img src="/images/logo.png"/></h1>
- </header>
-
- <div id="content">
- <p>Select a project to get details:</p>
- <select id="projects">
- <option value="0">OpenStack</option>
- <option value="1">Pecan</option>
- <option value="2">Stevedore</option>
- </select>
- <button id="submit" type="submit">Submit</button>
-
- <div id="result"></div>
-
- </div>
-
-**What did we just do?**
-
-#. Added a dropdown control and submit button for the user to interact with. Users can pick an open source project and get more details on it
-
-Building the Model with JSON Support
-------------------------------------
-
-The HTML and JavaScript work is now taken care of. At this point we can add a model to our project inside of the ``model`` folder. Create a file in there called ``projects.py`` and add the following to it:
-
-.. code-block:: python
-
- class Project(object):
- def __init__(self, name, licensing, repository, documentation):
- self.name = name
- self.licensing = licensing
- self.repository = repository
- self.documentation = documentation
-
- def __json__(self):
- return dict(
- name=self.name,
- licensing=self.licensing,
- repository=self.repository,
- documentation=self.documentation
- )
-
-**What did we just do?**
-
-#. Created a model called ``Project`` that can hold project specific data
-#. Added a ``__json__`` method so an instance of the ``Project class`` can be easily represented as JSON. The controller we will soon build will make use of that JSON capability
-
-.. note::
-
- There are other ways to return JSON with Pecan, check out :ref:`jsonify` for more information.
-
-Working with the Controllers
-----------------------------
-
-We don't need to do anything major to the ``root.py`` file in the ``controllers`` folder except to add support for a new controller we will call ``ProjectsController``. Modify the ``root.py`` like this:
-
-.. code-block:: python
-
- from pecan import expose
-
- from myajax.controllers.projects import ProjectsController
-
-
- class RootController(object):
-
- projects = ProjectsController()
-
- @expose(generic=True, template='index.html')
- def index(self):
- return dict()
-
-**What did we just do?**
-
-#. Removed some of the initial boilerplate code since we won't be using it
-#. Add support for the upcoming ``ProjectsController``
-
-The final piece is to add a file called ``projects.py`` to the ``controllers`` folder. This new file will host the ``ProjectsController`` which will listen for incoming AJAX ``GET`` calls (in our case) and return the appropriate JSON response.
-
-Add the following code to the ``projects.py`` file:
-
-.. code-block:: python
-
- from pecan import expose, response
- from pecan.rest import RestController
-
- from myajax.model.projects import Project
-
-
- class ProjectsController(RestController):
-
- # Note: You would probably store this information in a database
- # This is just for simplicity and demonstration purposes
- def __init__(self):
- self.projects = [
- Project(name='OpenStack',
- licensing='Apache 2',
- repository='http://github.com/openstack',
- documentation='http://docs.openstack.org'),
- Project(name='Pecan',
- licensing='BSD',
- repository='http://github.com/stackforge/pecan',
- documentation='http://pecan.readthedocs.org'),
- Project(name='stevedore',
- licensing='Apache 2',
- repository='http://github.com/dreamhost/pecan',
- documentation='http://stevedore.readthedocs.org')
- ]
-
-
- @expose('json', content_type='application/json')
- def get(self, id):
- response.status = 200
- return self.projects[int(id)]
-
-**What did we just do?**
-
-#. Created a local class variable called ``projects`` that holds three open source projects and their details. Typically this kind of information would probably reside in a database
-#. Added code for the new controller that will listen on the ``projects`` endpoint and serve back JSON based on the ``id`` passed in from the web page
-
-Run the application:
-
-::
-
-$ pecan serve config.py
-
-Open a web browser: `http://127.0.0.1:8080/ <http://127.0.0.1:8080/>`_
-
-There is something else we could add. What if an ``id`` is passed that is not found? A proper ``HTTP 404`` should be sent back. For this we will modify the ``ProjectsController``.
-
-Change the ``get`` function to look like this:
-
-.. code-block:: python
-
- @expose('json', content_type='application/json')
- def get(self, id):
- try:
- response.status = 200
- return self.projects[int(id)]
- except (IndexError, ValueError) as ex:
- abort(404)
-
-To test this out we need to pass an invalid ``id`` to the ``ProjectsController``. This can be done by going into the ``index.html`` and adding an additional ``option`` tag with an ``id`` value that is outside of 0-2.
-
-.. code-block:: html
-
- <p>Select a project to get details:</p>
- <select id="projects">
- <option value="0">OpenStack</option>
- <option value="1">Pecan</option>
- <option value="2">Stevedore</option>
- <option value="3">WSME</option>
- </select>
-
-You can see that we added ``WSME`` to the list and the value is 3.
-
-Run the application:
-
-::
-
-$ pecan serve config.py
-
-Open a web browser: `http://127.0.0.1:8080/ <http://127.0.0.1:8080/>`_
-
-Select ``WSME`` from the list. You should see the error dialog box triggered.
diff --git a/docs/source/simple_forms_processing.rst b/docs/source/simple_forms_processing.rst
deleted file mode 100644
index d7b73de..0000000
--- a/docs/source/simple_forms_processing.rst
+++ /dev/null
@@ -1,195 +0,0 @@
-.. _simple_forms_processing:
-
-Example Application: Simple Forms Processing
-============================================
-
-This guide will walk you through building a simple Pecan web application that will do some simple forms processing.
-
-Project Setup
--------------
-
-First, you'll need to install Pecan:
-
-::
-
-$ pip install pecan
-
-Use Pecan's basic template support to start a new project:
-
-::
-
-$ pecan create mywebsite
-$ cd mywebsite
-
-Install the new project in development mode:
-
-::
-
-$ python setup.py develop
-
-With the project ready, go into the ``templates`` folder and edit the ``index.html`` file. Modify it so that it resembles this:
-
-.. code-block:: html
-
- <%inherit file="layout.html" />
-
- <%def name="title()">
- Welcome to Pecan!
- </%def>
- <header>
- <h1><img src="/images/logo.png" /></h1>
- </header>
- <div id="content">
- <form method="POST" action="/">
- <fieldset>
- <p>Enter a message: <input name="message" /></p>
- <p>Enter your first name: <input name="first_name" /></p>
- <input type="submit" value="Submit" />
- </fieldset>
- </form>
- % if not form_post_data is UNDEFINED:
- <p>${form_post_data['first_name']}, your message is: ${form_post_data['message']}</p>
- % endif
- </div>
-
-**What did we just do?**
-
-#. Modified the contents of the ``form`` tag to have two ``input`` tags. The first is named ``message`` and the second is named ``first_name``
-#. Added a check if ``form_post_data`` has not been defined so we don't show the message or wording
-#. Added code to display the message from the user's ``POST`` action
-
-Go into the ``controllers`` folder now and edit the ``root.py`` file. There will be two functions inside of the ``RootController`` class which will display the ``index.html`` file when your web browser hits the ``'/'`` endpoint. If the user puts some data into the textbox and hits the submit button then they will see the personalized message displayed back at them.
-
-Modify the ``root.py`` to look like this:
-
-.. code-block:: python
-
- from pecan import expose
-
-
- class RootController(object):
-
- @expose(generic=True, template='index.html')
- def index(self):
- return dict()
-
- @index.when(method='POST', template='index.html')
- def index_post(self, **kwargs):
- return dict(form_post_data=kwargs)
-
-**What did we just do?**
-
-#. Modified the ``index`` function to render the initial ``index.html`` webpage
-#. Modified the ``index_post`` function to return the posted data via keyword arguments
-
-Run the application:
-
-::
-
-$ pecan serve config.py
-
-Open a web browser: `http://127.0.0.1:8080/ <http://127.0.0.1:8080/>`_
-
-Adding Validation
------------------
-
-Enter a message into the textbox along with a name in the second textbox and press the submit button. You should see a personalized message displayed below the form once the page posts back.
-
-One problem you might have noticed is if you don't enter a message or a first name then you simply see no value entered for that part of the message. Let's add a little validation to make sure a message and a first name was actually entered. For this, we will use `WTForms <http://wtforms.simplecodes.com/>`_ but you can substitute anything else for your projects.
-
-Add support for the `WTForms <http://wtforms.simplecodes.com/>`_ library:
-
-::
-
-$ pip install wtforms
-
-.. note::
-
- Keep in mind that Pecan is not opinionated when it comes to a particular library when working with form generation, validation, etc. Choose which libraries you prefer and integrate those with Pecan. This is one way of doing this, there are many more ways so feel free to handle this however you want in your own projects.
-
-Go back to the ``root.py`` files and modify it like this:
-
-.. code-block:: python
-
- from pecan import expose, request
- from wtforms import Form, TextField, validators
-
-
- class PersonalizedMessageForm(Form):
- message = TextField(u'Enter a message',
- validators=[validators.required()])
- first_name = TextField(u'Enter your first name',
- validators=[validators.required()])
-
-
- class RootController(object):
-
- @expose(generic=True, template='index.html')
- def index(self):
- return dict(form=PersonalizedMessageForm())
-
- @index.when(method='POST', template='index.html')
- def index_post(self):
- form = PersonalizedMessageForm(request.POST)
- if form.validate():
- return dict(message=form.message.data,
- first_name=form.first_name.data)
- else:
- return dict(form=form)
-
-**What did we just do?**
-
-#. Added the ``PersonalizedMessageForm`` with two textfields and a required field validator for each
-#. Modified the ``index`` function to create a new instance of the ``PersonalizedMessageForm`` class and return it
-#. In the ``index_post`` function modify it to gather the posted data and validate it. If its valid, then set the returned data to be displayed on the webpage. If not valid, send the form which will contain the data plus the error message(s)
-
-Modify the ``index.html`` like this:
-
-.. code-block:: html
-
- <%inherit file="layout.html" />
-
- ## provide definitions for blocks we want to redefine
- <%def name="title()">
- Welcome to Pecan!
- </%def>
- <header>
- <h1><img src="/images/logo.png" /></h1>
- </header>
- <div id="content">
- % if not form:
- <p>${first_name}, your message is: ${message}</p>
- % else:
- <form method="POST" action="/">
- <div>
- ${form.message.label}:
- ${form.message}
- % if form.message.errors:
- <strong>${form.message.errors[0]}</strong>
- % endif
- </div>
- <div>
- ${form.first_name.label}:
- ${form.first_name}
- % if form.first_name.errors:
- <strong>${form.first_name.errors[0]}</strong>
- % endif
- </div>
- <input type="submit" value="Submit">
- </form>
- % endif
- </div>
-
-.. note::
-
- Keep in mind when using the `WTForms <http://wtforms.simplecodes.com/>`_ library you can customize the error messages and more. Also, you have multiple validation rules so make sure to catch all the errors which will mean you need a loop rather than the simple example above which grabs the first error item in the list. See the `documentation <http://wtforms.simplecodes.com/>`_ for more information.
-
-Run the application:
-
-::
-
-$ pecan serve config.py
-
-Open a web browser: `http://127.0.0.1:8080/ <http://127.0.0.1:8080/>`_
-
-Try the form with valid data and with no data entered.
diff --git a/docs/source/static b/docs/source/static
deleted file mode 100644
index e69de29..0000000
--- a/docs/source/static
+++ /dev/null
diff --git a/docs/source/templates.rst b/docs/source/templates.rst
deleted file mode 100644
index ca06b4a..0000000
--- a/docs/source/templates.rst
+++ /dev/null
@@ -1,143 +0,0 @@
-.. _templates:
-
-Templating in Pecan
-===================
-
-Pecan includes support for a variety of templating engines and also
-makes it easy to add support for new template engines. Currently,
-Pecan supports:
-
-=============== =============
-Template System Renderer Name
-=============== =============
- Mako_ mako
- Genshi_ genshi
- Kajiki_ kajiki
- Jinja2_ jinja
- JSON json
-=============== =============
-
-.. _Mako: http://www.makotemplates.org/
-.. _Genshi: http://genshi.edgewall.org/
-.. _Kajiki: http://kajiki.pythonisito.com/
-.. _Jinja2: http://jinja.pocoo.org/
-
-The default template system is ``mako``, but that can be changed by
-passing the ``default_renderer`` key in your application's
-configuration::
-
- app = {
- 'default_renderer' : 'kajiki',
- # ...
- }
-
-
-Using Template Renderers
-------------------------
-
-:py:mod:`pecan.decorators` defines a decorator called
-:func:`~pecan.decorators.expose`, which is used to flag a method as a public
-controller. The :func:`~pecan.decorators.expose` decorator takes a ``template``
-argument, which can be used to specify the path to the template file to use for
-the controller method being exposed.
-
-::
-
- class MyController(object):
- @expose('path/to/mako/template.html')
- def index(self):
- return dict(message='I am a mako template')
-
-:func:`~pecan.decorators.expose` will use the default template engine unless
-the path is prefixed by another renderer name.
-
-::
-
- @expose('kajiki:path/to/kajiki/template.html')
- def my_controller(self):
- return dict(message='I am a kajiki template')
-
-.. seealso::
-
- * :ref:`pecan_decorators`
- * :ref:`pecan_core`
- * :ref:`routing`
-
-
-Overriding Templates
---------------------
-
-:func:`~pecan.core.override_template` allows you to override the template set
-for a controller method when it is exposed. When
-:func:`~pecan.core.override_template` is called within the body of the
-controller method, it changes the template that will be used for that
-invocation of the method.
-
-::
-
- class MyController(object):
- @expose('template_one.html')
- def index(self):
- # ...
- override_template('template_two.html')
- return dict(message='I will now render with template_two.html')
-
-Manual Rendering
-----------------
-
-:func:`~pecan.core.render` allows you to manually render output using the Pecan
-templating framework. Pass the template path and values to go into the
-template, and :func:`~pecan.core.render` returns the rendered output as text.
-
-::
-
- @expose()
- def controller(self):
- return render('my_template.html', dict(message='I am the namespace'))
-
-
-The JSON Renderer
------------------
-
-Pecan also provides a ``JSON`` renderer, which you can use by exposing
-a controller method with ``@expose('json')``.
-
-.. seealso::
-
- * :ref:`jsonify`
- * :ref:`pecan_jsonify`
-
-
-Defining Custom Renderers
--------------------------
-
-To define a custom renderer, you can create a class that follows the
-renderer protocol::
-
- class MyRenderer(object):
- def __init__(self, path, extra_vars):
- '''
- Your renderer is provided with a path to templates,
- as configured by your application, and any extra
- template variables, also as configured
- '''
- pass
-
- def render(self, template_path, namespace):
- '''
- Lookup the template based on the path, and render
- your output based upon the supplied namespace
- dictionary, as returned from the controller.
- '''
- return str(namespace)
-
-
-To enable your custom renderer, define a ``custom_renderers`` key in
-your application's configuration::
-
- app = {
- 'custom_renderers' : {
- 'my_renderer' : MyRenderer
- },
- # ...
- }
diff --git a/docs/source/testing.rst b/docs/source/testing.rst
deleted file mode 100644
index 5253d6b..0000000
--- a/docs/source/testing.rst
+++ /dev/null
@@ -1,137 +0,0 @@
-.. _testing:
-
-Testing Pecan Applications
-==========================
-Tests can live anywhere in your Pecan project as long as the test runner can
-discover them. Traditionally, they exist in a package named ``myapp.tests``.
-
-The suggested mechanism for unit and integration testing of a Pecan application
-is the :mod:`unittest` module.
-
-Test Discovery and Other Tools
-------------------------------
-
-Tests for a Pecan project can be invoked as simply as ``python setup.py test``,
-though it's possible to run your tests with different discovery and automation
-tools. In particular, Pecan projects are known to work well with
-`nose <http://pypi.python.org/pypi/nose/1.1.2>`_, `pytest
-<http://pytest.org>`_,
-and `tox <http://pypi.python.org/pypi/tox>`_.
-
-Writing Functional Tests with WebTest
--------------------------------------
-A **unit test** typically relies on "mock" or "fake" objects to give the code
-under test enough context to run. In this way, only an individual unit of
-source code is tested.
-
-A healthy suite of tests combines **unit tests** with **functional tests**. In
-the context of a Pecan application, functional tests can be written with the
-help of the :mod:`webtest` library. In this way, it is possible to write tests
-that verify the behavior of an HTTP request life cycle from the controller
-routing down to the HTTP response. The following is an example that is
-similar to the one included with Pecan's quickstart project.
-
-::
-
- # myapp/myapp/tests/__init__.py
-
- import os
- from unittest import TestCase
- from pecan import set_config
- from pecan.testing import load_test_app
-
- class FunctionalTest(TestCase):
- """
- Used for functional tests where you need to test your
- literal application and its integration with the framework.
- """
-
- def setUp(self):
- self.app = load_test_app(os.path.join(
- os.path.dirname(__file__),
- 'config.py'
- ))
-
- def tearDown(self):
- set_config({}, overwrite=True)
-
-The testing utility included with Pecan, :func:`pecan.testing.load_test_app`, can
-be passed a file path representing a Pecan configuration file, and will return
-an instance of the application, wrapped in a :class:`~webtest.app.TestApp`
-environment.
-
-From here, it's possible to extend the :class:`FunctionalTest` base class and write
-tests that issue simulated HTTP requests.
-
-::
-
- class TestIndex(FunctionalTest):
-
- def test_index(self):
- resp = self.app.get('/')
- assert resp.status_int == 200
- assert 'Hello, World' in resp.body
-
-.. seealso::
-
- See the :mod:`webtest` documentation
- for further information about the methods available to a
- :class:`~webtest.app.TestApp` instance.
-
-Special Testing Variables
--------------------------
-
-Sometimes it's not enough to make assertions about the response body of certain
-requests. To aid in inspection, Pecan applications provide a special set of
-"testing variables" to any :class:`~webtest.response.TestResponse` object.
-
-Let's suppose that your Pecan applicaton had some controller which took a
-``name`` as an optional argument in the URL.
-
-::
-
- # myapp/myapp/controllers/root.py
- from pecan import expose
-
- class RootController(object):
-
- @expose('index.html')
- def index(self, name='Joe'):
- """A request to / will access this controller"""
- return dict(name=name)
-
-and rendered that name in it's template (and thus, the response body).
-
-::
-
- # myapp/myapp/templates/index.html
- Hello, ${name}!
-
-A functional test for this controller might look something like
-
-::
-
- class TestIndex(FunctionalTest):
-
- def test_index(self):
- resp = self.app.get('/')
- assert resp.status_int == 200
- assert 'Hello, Joe!' in resp.body
-
-In addition to :attr:`webtest.TestResponse.body`, Pecan also provides
-:attr:`webtest.TestResponse.namespace`, which represents the template namespace
-returned from the controller, and :attr:`webtest.TestResponse.template_name`, which
-contains the name of the template used.
-
-::
-
- class TestIndex(FunctionalTest):
-
- def test_index(self):
- resp = self.app.get('/')
- assert resp.status_int == 200
- assert resp.namespace == {'name': 'Joe'}
- assert resp.template_name == 'index.html'
-
-In this way, it's possible to test the return value and rendered template of
-individual controllers.
diff --git a/pecan/__init__.py b/pecan/__init__.py
deleted file mode 100644
index 1adcc71..0000000
--- a/pecan/__init__.py
+++ /dev/null
@@ -1,134 +0,0 @@
-from .core import (
- abort, override_template, Pecan, Request, Response, load_app,
- redirect, render, request, response
-)
-from .decorators import expose
-from .hooks import RequestViewerHook
-
-from .middleware.debug import DebugMiddleware
-from .middleware.errordocument import ErrorDocumentMiddleware
-from .middleware.recursive import RecursiveMiddleware
-from .middleware.static import StaticFileMiddleware
-from .routing import route
-
-from .configuration import set_config, Config
-from .configuration import _runtime_conf as conf
-from . import middleware
-
-try:
- from logging.config import dictConfig as load_logging_config
-except ImportError:
- from logutils.dictconfig import dictConfig as load_logging_config # noqa
-
-import warnings
-
-
-__all__ = [
- 'make_app', 'load_app', 'Pecan', 'Request', 'Response', 'request',
- 'response', 'override_template', 'expose', 'conf', 'set_config', 'render',
- 'abort', 'redirect', 'route'
-]
-
-
-def make_app(root, **kw):
- '''
- Utility for creating the Pecan application object. This function should
- generally be called from the ``setup_app`` function in your project's
- ``app.py`` file.
-
- :param root: A string representing a root controller object (e.g.,
- "myapp.controller.root.RootController")
- :param static_root: The relative path to a directory containing static
- files. Serving static files is only enabled when
- debug mode is set.
- :param debug: A flag to enable debug mode. This enables the debug
- middleware and serving static files.
- :param wrap_app: A function or middleware class to wrap the Pecan app.
- This must either be a wsgi middleware class or a
- function that returns a wsgi application. This wrapper
- is applied first before wrapping the application in
- other middlewares such as Pecan's debug middleware.
- This should be used if you want to use middleware to
- perform authentication or intercept all requests before
- they are routed to the root controller.
- :param logging: A dictionary used to configure logging. This uses
- ``logging.config.dictConfig``.
-
- All other keyword arguments are passed in to the Pecan app constructor.
-
- :returns: a ``Pecan`` object.
- '''
- # Pass logging configuration (if it exists) on to the Python logging module
- logging = kw.get('logging', {})
- debug = kw.get('debug', False)
- if logging:
- if debug:
- try:
- #
- # By default, Python 2.7+ silences DeprecationWarnings.
- # However, if conf.app.debug is True, we should probably ensure
- # that users see these types of warnings.
- #
- from logging import captureWarnings
- captureWarnings(True)
- warnings.simplefilter("default", DeprecationWarning)
- except ImportError:
- # No captureWarnings on Python 2.6, DeprecationWarnings are on
- pass
-
- if isinstance(logging, Config):
- logging = logging.to_dict()
- if 'version' not in logging:
- logging['version'] = 1
- load_logging_config(logging)
-
- # Instantiate the WSGI app by passing **kw onward
- app = Pecan(root, **kw)
-
- # Optionally wrap the app in another WSGI app
- wrap_app = kw.get('wrap_app', None)
- if wrap_app:
- app = wrap_app(app)
-
- # Configuration for serving custom error messages
- errors = kw.get('errors', getattr(conf.app, 'errors', {}))
- if errors:
- app = middleware.errordocument.ErrorDocumentMiddleware(app, errors)
-
- # Included for internal redirect support
- app = middleware.recursive.RecursiveMiddleware(app)
-
- # When in debug mode, load exception debugging middleware
- static_root = kw.get('static_root', None)
- if debug:
- debug_kwargs = getattr(conf, 'debug', {})
- debug_kwargs.setdefault('context_injectors', []).append(
- lambda environ: {
- 'request': environ.get('pecan.locals', {}).get('request')
- }
- )
- app = DebugMiddleware(
- app,
- **debug_kwargs
- )
-
- # Support for serving static files (for development convenience)
- if static_root:
- app = middleware.static.StaticFileMiddleware(app, static_root)
-
- elif static_root:
- warnings.warn(
- "`static_root` is only used when `debug` is True, ignoring",
- RuntimeWarning
- )
-
- if hasattr(conf, 'requestviewer'):
- warnings.warn(''.join([
- "`pecan.conf.requestviewer` is deprecated. To apply the ",
- "`RequestViewerHook` to your application, add it to ",
- "`pecan.conf.app.hooks` or manually in your project's `app.py` ",
- "file."]),
- DeprecationWarning
- )
-
- return app
diff --git a/pecan/commands/__init__.py b/pecan/commands/__init__.py
deleted file mode 100644
index da02464..0000000
--- a/pecan/commands/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from .base import CommandRunner, BaseCommand # noqa
-from .serve import ServeCommand # noqa
-from .shell import ShellCommand # noqa
-from .create import CreateCommand # noqa
diff --git a/pecan/commands/base.py b/pecan/commands/base.py
deleted file mode 100644
index 441d577..0000000
--- a/pecan/commands/base.py
+++ /dev/null
@@ -1,166 +0,0 @@
-import pkg_resources
-import argparse
-import logging
-import sys
-from warnings import warn
-
-import six
-
-log = logging.getLogger(__name__)
-
-
-class HelpfulArgumentParser(argparse.ArgumentParser):
-
- def error(self, message): # pragma: nocover
- """error(message: string)
-
- Prints a usage message incorporating the message to stderr and
- exits.
-
- If you override this in a subclass, it should not return -- it
- should either exit or raise an exception.
- """
- self.print_help(sys.stderr)
- self._print_message('\n')
- self.exit(2, '%s: %s\n' % (self.prog, message))
-
-
-class CommandManager(object):
- """ Used to discover `pecan.command` entry points. """
-
- def __init__(self):
- self.commands = {}
- self.load_commands()
-
- def load_commands(self):
- for ep in pkg_resources.iter_entry_points('pecan.command'):
- log.debug('%s loading plugin %s', self.__class__.__name__, ep)
- if ep.name in self.commands:
- warn(
- "Duplicate entry points found on `%s` - ignoring %s" % (
- ep.name,
- ep
- ),
- RuntimeWarning
- )
- continue
- try:
- cmd = ep.load()
- cmd.run # ensure existance; catch AttributeError otherwise
- except Exception as e: # pragma: nocover
- warn("Unable to load plugin %s: %s" % (ep, e), RuntimeWarning)
- continue
- self.add({ep.name: cmd})
-
- def add(self, cmd):
- self.commands.update(cmd)
-
-
-class CommandRunner(object):
- """ Dispatches `pecan` command execution requests. """
-
- def __init__(self):
- self.manager = CommandManager()
- self.parser = HelpfulArgumentParser(add_help=True)
- self.parser.add_argument(
- '--version',
- action='version',
- version='Pecan %s' % self.version
- )
- self.parse_sub_commands()
-
- def parse_sub_commands(self):
- subparsers = self.parser.add_subparsers(
- dest='command_name',
- metavar='command'
- )
- for name, cmd in self.commands.items():
- sub = subparsers.add_parser(
- name,
- help=cmd.summary
- )
- for arg in getattr(cmd, 'arguments', tuple()):
- arg = arg.copy()
- if isinstance(arg.get('name'), six.string_types):
- sub.add_argument(arg.pop('name'), **arg)
- elif isinstance(arg.get('name'), list):
- sub.add_argument(*arg.pop('name'), **arg)
-
- def run(self, args):
- ns = self.parser.parse_args(args)
- self.commands[ns.command_name]().run(ns)
-
- @classmethod
- def handle_command_line(cls): # pragma: nocover
- runner = CommandRunner()
- runner.run(sys.argv[1:])
-
- @property
- def version(self):
- return pkg_resources.get_distribution('pecan').version
-
- @property
- def commands(self):
- return self.manager.commands
-
-
-class BaseCommandMeta(type):
-
- @property
- def summary(cls):
- """
- This is used to populate the --help argument on the command line.
-
- This provides a default behavior which takes the first sentence of the
- command's docstring and uses it.
- """
- return cls.__doc__.strip().splitlines()[0].rstrip('.')
-
-
-class BaseCommandParent(object):
- """
- A base interface for Pecan commands.
-
- Can be extended to support ``pecan`` command extensions in individual Pecan
- projects, e.g.,
-
- $ ``pecan my-custom-command config.py``
-
- ::
-
- # myapp/myapp/custom_command.py
- class CustomCommand(pecan.commands.base.BaseCommand):
- '''
- (First) line of the docstring is used to summarize the command.
- '''
-
- arguments = ({
- 'name': '--extra_arg',
- 'help': 'an extra command line argument',
- 'optional': True
- })
-
- def run(self, args):
- super(SomeCommand, self).run(args)
- if args.extra_arg:
- pass
- """
-
- arguments = ({
- 'name': 'config_file',
- 'help': 'a Pecan configuration file',
- 'nargs': '?',
- 'default': None,
- },)
-
- def run(self, args):
- """To be implemented by subclasses."""
- self.args = args
-
- def load_app(self):
- from pecan import load_app
- return load_app(self.args.config_file)
-
-BaseCommand = BaseCommandMeta('BaseCommand', (BaseCommandParent,), {
- '__doc__': BaseCommandParent.__doc__
-})
diff --git a/pecan/commands/create.py b/pecan/commands/create.py
deleted file mode 100644
index 1187489..0000000
--- a/pecan/commands/create.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""
-Create command for Pecan
-"""
-import pkg_resources
-import logging
-from warnings import warn
-from pecan.commands import BaseCommand
-from pecan.scaffolds import DEFAULT_SCAFFOLD
-
-log = logging.getLogger(__name__)
-
-
-class ScaffoldManager(object):
- """ Used to discover `pecan.scaffold` entry points. """
-
- def __init__(self):
- self.scaffolds = {}
- self.load_scaffolds()
-
- def load_scaffolds(self):
- for ep in pkg_resources.iter_entry_points('pecan.scaffold'):
- log.debug('%s loading scaffold %s', self.__class__.__name__, ep)
- try:
- cmd = ep.load()
- cmd.copy_to # ensure existance; catch AttributeError otherwise
- except Exception as e: # pragma: nocover
- warn(
- "Unable to load scaffold %s: %s" % (ep, e), RuntimeWarning
- )
- continue
- self.add({ep.name: cmd})
-
- def add(self, cmd):
- self.scaffolds.update(cmd)
-
-
-class CreateCommand(BaseCommand):
- """
- Creates the file layout for a new Pecan scaffolded project.
- """
-
- manager = ScaffoldManager()
-
- arguments = ({
- 'name': 'project_name',
- 'help': 'the (package) name of the new project'
- }, {
- 'name': 'template_name',
- 'metavar': 'template_name',
- 'help': 'a registered Pecan template',
- 'nargs': '?',
- 'default': DEFAULT_SCAFFOLD,
- 'choices': manager.scaffolds.keys()
- })
-
- def run(self, args):
- super(CreateCommand, self).run(args)
- self.manager.scaffolds[args.template_name]().copy_to(
- args.project_name
- )
diff --git a/pecan/commands/serve.py b/pecan/commands/serve.py
deleted file mode 100644
index 72a536d..0000000
--- a/pecan/commands/serve.py
+++ /dev/null
@@ -1,223 +0,0 @@
-"""
-Serve command for Pecan.
-"""
-from __future__ import print_function
-import logging
-import os
-import sys
-import time
-import subprocess
-from wsgiref.simple_server import WSGIRequestHandler
-
-
-from pecan.commands import BaseCommand
-from pecan import util
-
-
-logger = logging.getLogger(__name__)
-
-
-class ServeCommand(BaseCommand):
- """
- Serves a Pecan web application.
-
- This command serves a Pecan web application using the provided
- configuration file for the server and application.
- """
-
- arguments = BaseCommand.arguments + ({
- 'name': '--reload',
- 'help': 'Watch for changes and automatically reload.',
- 'default': False,
- 'action': 'store_true'
- },)
-
- def run(self, args):
- super(ServeCommand, self).run(args)
- app = self.load_app()
- self.serve(app, app.config)
-
- def create_subprocess(self):
- self.server_process = subprocess.Popen(
- [arg for arg in sys.argv if arg != '--reload'],
- stdout=sys.stdout, stderr=sys.stderr
- )
-
- def watch_and_spawn(self, conf):
- from watchdog.observers import Observer
- from watchdog.events import (
- FileSystemEventHandler, FileSystemMovedEvent, FileModifiedEvent,
- DirModifiedEvent
- )
-
- print('Monitoring for changes...')
- self.create_subprocess()
-
- parent = self
-
- class AggressiveEventHandler(FileSystemEventHandler):
- def should_reload(self, event):
- for t in (
- FileSystemMovedEvent, FileModifiedEvent, DirModifiedEvent
- ):
- if isinstance(event, t):
- return True
- return False
-
- def on_modified(self, event):
- if self.should_reload(event):
- parent.server_process.kill()
- parent.create_subprocess()
-
- # Determine a list of file paths to monitor
- paths = self.paths_to_monitor(conf)
-
- event_handler = AggressiveEventHandler()
- for path, recurse in paths:
- observer = Observer()
- observer.schedule(
- event_handler,
- path=path,
- recursive=recurse
- )
- observer.start()
-
- try:
- while True:
- time.sleep(1)
- except KeyboardInterrupt:
- pass
-
- def paths_to_monitor(self, conf):
- paths = []
-
- for package_name in getattr(conf.app, 'modules', []):
- module = __import__(package_name, fromlist=['app'])
- if hasattr(module, 'app') and hasattr(module.app, 'setup_app'):
- paths.append((
- os.path.dirname(module.__file__),
- True
- ))
- break
-
- paths.append((os.path.dirname(conf.__file__), False))
- return paths
-
- def _serve(self, app, conf):
- from wsgiref.simple_server import make_server
-
- host, port = conf.server.host, int(conf.server.port)
- srv = make_server(
- host,
- port,
- app,
- handler_class=PecanWSGIRequestHandler,
- )
-
- print('Starting server in PID %s' % os.getpid())
-
- if host == '0.0.0.0':
- print(
- 'serving on 0.0.0.0:%s, view at http://127.0.0.1:%s' %
- (port, port)
- )
- else:
- print("serving on http://%s:%s" % (host, port))
-
- try:
- srv.serve_forever()
- except KeyboardInterrupt:
- # allow CTRL+C to shutdown
- pass
-
- def serve(self, app, conf):
- """
- A very simple approach for a WSGI server.
- """
-
- if self.args.reload:
- try:
- self.watch_and_spawn(conf)
- except ImportError:
- print('The `--reload` option requires `watchdog` to be '
- 'installed.')
- print(' $ pip install watchdog')
- else:
- self._serve(app, conf)
-
-
-def gunicorn_run():
- """
- The ``gunicorn_pecan`` command for launching ``pecan`` applications
- """
- try:
- from gunicorn.app.wsgiapp import WSGIApplication
- except ImportError as exc:
- args = exc.args
- arg0 = args[0] if args else ''
- arg0 += ' (are you sure `gunicorn` is installed?)'
- exc.args = (arg0,) + args[1:]
- raise
-
- class PecanApplication(WSGIApplication):
-
- def init(self, parser, opts, args):
- if len(args) != 1:
- parser.error("No configuration file was specified.")
-
- self.cfgfname = os.path.normpath(
- os.path.join(os.getcwd(), args[0])
- )
- self.cfgfname = os.path.abspath(self.cfgfname)
- if not os.path.exists(self.cfgfname):
- parser.error("Config file not found: %s" % self.cfgfname)
-
- from pecan.configuration import _runtime_conf, set_config
- set_config(self.cfgfname, overwrite=True)
-
- # If available, use the host and port from the pecan config file
- cfg = {}
- if _runtime_conf.get('server'):
- server = _runtime_conf['server']
- if hasattr(server, 'host') and hasattr(server, 'port'):
- cfg['bind'] = '%s:%s' % (
- server.host, server.port
- )
- return cfg
-
- def load(self):
- from pecan.deploy import deploy
- return deploy(self.cfgfname)
-
- PecanApplication("%(prog)s [OPTIONS] config.py").run()
-
-
-class PecanWSGIRequestHandler(WSGIRequestHandler, object):
- """
- A wsgiref request handler class that allows actual log output depending on
- the application configuration.
- """
-
- def __init__(self, *args, **kwargs):
- # We set self.path to avoid crashes in log_message() on unsupported
- # requests (like "OPTIONS").
- self.path = ''
- super(PecanWSGIRequestHandler, self).__init__(*args, **kwargs)
-
- def log_message(self, format, *args):
- """
- overrides the ``log_message`` method from the wsgiref server so that
- normal logging works with whatever configuration the application has
- been set to.
-
- Levels are inferred from the HTTP status code, 4XX codes are treated as
- warnings, 5XX as errors and everything else as INFO level.
- """
- code = args[1][0]
- levels = {
- '4': 'warning',
- '5': 'error'
- }
-
- log_handler = getattr(logger, levels.get(code, 'info'))
- log_handler(format % args)
diff --git a/pecan/commands/shell.py b/pecan/commands/shell.py
deleted file mode 100644
index 968ded0..0000000
--- a/pecan/commands/shell.py
+++ /dev/null
@@ -1,177 +0,0 @@
-"""
-Shell command for Pecan.
-"""
-from pecan.commands import BaseCommand
-from webtest import TestApp
-from warnings import warn
-import sys
-
-
-class NativePythonShell(object):
- """
- Open an interactive python shell with the Pecan app loaded.
- """
-
- @classmethod
- def invoke(cls, ns, banner): # pragma: nocover
- """
- :param ns: local namespace
- :param banner: interactive shell startup banner
-
- Embed an interactive native python shell.
- """
- import code
- py_prefix = sys.platform.startswith('java') and 'J' or 'P'
- shell_banner = 'Pecan Interactive Shell\n%sython %s\n\n' % \
- (py_prefix, sys.version)
- shell = code.InteractiveConsole(locals=ns)
- try:
- import readline # noqa
- except ImportError:
- pass
- shell.interact(shell_banner + banner)
-
-
-class IPythonShell(object):
- """
- Open an interactive ipython shell with the Pecan app loaded.
- """
-
- @classmethod
- def invoke(cls, ns, banner): # pragma: nocover
- """
- :param ns: local namespace
- :param banner: interactive shell startup banner
-
- Embed an interactive ipython shell.
- Try the InteractiveShellEmbed API first, fall back on
- IPShellEmbed for older IPython versions.
- """
- try:
- from IPython.frontend.terminal.embed import (
- InteractiveShellEmbed
- )
- # try and load their default profile
- from IPython.frontend.terminal.ipapp import (
- load_default_config
- )
- config = load_default_config()
- shell = InteractiveShellEmbed(config=config, banner2=banner)
- shell(local_ns=ns)
- except ImportError:
- # Support for the IPython <= 0.10 shell API
- from IPython.Shell import IPShellEmbed
- shell = IPShellEmbed(argv=[])
- shell.set_banner(shell.IP.BANNER + '\n\n' + banner)
- shell(local_ns=ns, global_ns={})
-
-
-class BPythonShell(object):
- """
- Open an interactive bpython shell with the Pecan app loaded.
- """
-
- @classmethod
- def invoke(cls, ns, banner): # pragma: nocover
- """
- :param ns: local namespace
- :param banner: interactive shell startup banner
-
- Embed an interactive bpython shell.
- """
- from bpython import embed
- embed(ns, ['-i'], banner)
-
-
-class ShellCommand(BaseCommand):
- """
- Open an interactive shell with the Pecan app loaded.
- Attempt to invoke the specified python shell flavor
- (ipython, bpython, etc.). Fall back on the native
- python shell if the requested flavor variance is not
- installed.
- """
-
- SHELLS = {
- 'python': NativePythonShell,
- 'ipython': IPythonShell,
- 'bpython': BPythonShell,
- }
-
- arguments = BaseCommand.arguments + ({
- 'name': ['--shell', '-s'],
- 'help': 'which Python shell to use',
- 'choices': SHELLS.keys(),
- 'default': 'python'
- },)
-
- def run(self, args):
- """
- Load the pecan app, prepare the locals, sets the
- banner, and invokes the python shell.
- """
- super(ShellCommand, self).run(args)
-
- # load the application
- app = self.load_app()
-
- # prepare the locals
- locs = dict(__name__='pecan-admin')
- locs['wsgiapp'] = app
- locs['app'] = TestApp(app)
-
- model = self.load_model(app.config)
- if model:
- locs['model'] = model
-
- # insert the pecan locals
- from pecan import abort, conf, redirect, request, response
- locs['abort'] = abort
- locs['conf'] = conf
- locs['redirect'] = redirect
- locs['request'] = request
- locs['response'] = response
-
- # prepare the banner
- banner = ' The following objects are available:\n'
- banner += ' %-10s - This project\'s WSGI App instance\n' % 'wsgiapp'
- banner += ' %-10s - The current configuration\n' % 'conf'
- banner += ' %-10s - webtest.TestApp wrapped around wsgiapp\n' % 'app'
- if model:
- model_name = getattr(
- model,
- '__module__',
- getattr(model, '__name__', 'model')
- )
- banner += ' %-10s - Models from %s\n' % ('model', model_name)
-
- self.invoke_shell(locs, banner)
-
- def invoke_shell(self, locs, banner):
- """
- Invokes the appropriate flavor of the python shell.
- Falls back on the native python shell if the requested
- flavor (ipython, bpython,etc) is not installed.
- """
- shell = self.SHELLS[self.args.shell]
- try:
- shell().invoke(locs, banner)
- except ImportError as e:
- warn((
- "%s is not installed, `%s`, "
- "falling back to native shell") % (self.args.shell, e),
- RuntimeWarning
- )
- if shell == NativePythonShell:
- raise
- NativePythonShell().invoke(locs, banner)
-
- def load_model(self, config):
- """
- Load the model extension module
- """
- for package_name in getattr(config.app, 'modules', []):
- module = __import__(package_name, fromlist=['model'])
- if hasattr(module, 'model'):
- return module.model
- return None
diff --git a/pecan/compat/__init__.py b/pecan/compat/__init__.py
deleted file mode 100644
index c929f58..0000000
--- a/pecan/compat/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import inspect
-
-import six
-
-if six.PY3:
- import urllib.parse as urlparse
- from urllib.parse import quote, unquote_plus
- from urllib.request import urlopen, URLError
- from html import escape
- izip = zip
-else:
- import urlparse # noqa
- from urllib import quote, unquote_plus # noqa
- from urllib2 import urlopen, URLError # noqa
- from cgi import escape # noqa
- from itertools import izip
-
-
-def is_bound_method(ob):
- return inspect.ismethod(ob) and six.get_method_self(ob) is not None
diff --git a/pecan/configuration.py b/pecan/configuration.py
deleted file mode 100644
index 6260723..0000000
--- a/pecan/configuration.py
+++ /dev/null
@@ -1,254 +0,0 @@
-import re
-import inspect
-import os
-import sys
-
-import six
-
-if six.PY3:
- from importlib.machinery import SourceFileLoader
-else:
- import imp
-
-
-IDENTIFIER = re.compile(r'[a-z_](\w)*$', re.IGNORECASE)
-
-DEFAULT = {
- # Server Specific Configurations
- 'server': {
- 'port': '8080',
- 'host': '0.0.0.0'
- },
-
- # Pecan Application Configurations
- 'app': {
- 'root': None,
- 'modules': [],
- 'static_root': 'public',
- 'template_path': '',
- 'force_canonical': True
- }
-}
-
-
-class ConfigDict(dict):
- pass
-
-
-class Config(object):
- '''
- Base class for Pecan configurations.
-
- Create a Pecan configuration object from a dictionary or a
- filename.
-
- :param conf_dict: A python dictionary to use for the configuration.
- :param filename: A filename to use for the configuration.
- '''
-
- def __init__(self, conf_dict={}, filename=''):
-
- self.__values__ = {}
- self.__file__ = filename
- self.update(conf_dict)
-
- def empty(self):
- self.__values__ = {}
-
- def update(self, conf_dict):
- '''
- Updates this configuration with a dictionary.
-
- :param conf_dict: A python dictionary to update this configuration
- with.
- '''
-
- if isinstance(conf_dict, dict):
- iterator = six.iteritems(conf_dict)
- else:
- iterator = iter(conf_dict)
-
- for k, v in iterator:
- if not IDENTIFIER.match(k):
- raise ValueError('\'%s\' is not a valid indentifier' % k)
-
- cur_val = self.__values__.get(k)
-
- if isinstance(cur_val, Config):
- cur_val.update(conf_dict[k])
- else:
- self[k] = conf_dict[k]
-
- def get(self, attribute, default=None):
- try:
- return self[attribute]
- except KeyError:
- return default
-
- def __dictify__(self, obj, prefix):
- '''
- Private helper method for to_dict.
- '''
- for k, v in obj.copy().items():
- if prefix:
- del obj[k]
- k = "%s%s" % (prefix, k)
- if isinstance(v, Config):
- v = self.__dictify__(dict(v), prefix)
- obj[k] = v
- return obj
-
- def to_dict(self, prefix=None):
- '''
- Converts recursively the Config object into a valid dictionary.
-
- :param prefix: A string to optionally prefix all key elements in the
- returned dictonary.
- '''
-
- conf_obj = dict(self)
- return self.__dictify__(conf_obj, prefix)
-
- def __getattr__(self, name):
- try:
- return self.__values__[name]
- except KeyError:
- msg = "'pecan.conf' object has no attribute '%s'" % name
- raise AttributeError(msg)
-
- def __getitem__(self, key):
- return self.__values__[key]
-
- def __setitem__(self, key, value):
- if isinstance(value, dict) and not isinstance(value, ConfigDict):
- if value.get('__force_dict__'):
- del value['__force_dict__']
- self.__values__[key] = ConfigDict(value)
- else:
- self.__values__[key] = Config(value, filename=self.__file__)
- elif isinstance(value, six.string_types) and '%(confdir)s' in value:
- confdir = os.path.dirname(self.__file__) or os.getcwd()
- self.__values__[key] = value.replace('%(confdir)s', confdir)
- else:
- self.__values__[key] = value
-
- def __iter__(self):
- return six.iteritems(self.__values__)
-
- def __dir__(self):
- """
- When using dir() returns a list of the values in the config. Note:
- This function only works in Python2.6 or later.
- """
- return list(self.__values__.keys())
-
- def __repr__(self):
- return 'Config(%s)' % str(self.__values__)
-
-
-def conf_from_file(filepath):
- '''
- Creates a configuration dictionary from a file.
-
- :param filepath: The path to the file.
- '''
-
- abspath = os.path.abspath(os.path.expanduser(filepath))
- conf_dict = {}
- if not os.path.isfile(abspath):
- raise RuntimeError('`%s` is not a file.' % abspath)
-
- # First, make sure the code will actually compile (and has no SyntaxErrors)
- with open(abspath, 'rb') as f:
- compiled = compile(f.read(), abspath, 'exec')
-
- # Next, attempt to actually import the file as a module.
- # This provides more verbose import-related error reporting than exec()
- absname, _ = os.path.splitext(abspath)
- basepath, module_name = absname.rsplit(os.sep, 1)
- if six.PY3:
- SourceFileLoader(module_name, abspath).load_module(module_name)
- else:
- imp.load_module(
- module_name,
- *imp.find_module(module_name, [basepath])
- )
-
- # If we were able to import as a module, actually exec the compiled code
- exec(compiled, globals(), conf_dict)
- conf_dict['__file__'] = abspath
-
- return conf_from_dict(conf_dict)
-
-
-def get_conf_path_from_env():
- '''
- If the ``PECAN_CONFIG`` environment variable exists and it points to
- a valid path it will return that, otherwise it will raise
- a ``RuntimeError``.
- '''
- config_path = os.environ.get('PECAN_CONFIG')
- if not config_path:
- error = "PECAN_CONFIG is not set and " \
- "no config file was passed as an argument."
- elif not os.path.isfile(config_path):
- error = "PECAN_CONFIG was set to an invalid path: %s" % config_path
- else:
- return config_path
-
- raise RuntimeError(error)
-
-
-def conf_from_dict(conf_dict):
- '''
- Creates a configuration dictionary from a dictionary.
-
- :param conf_dict: The configuration dictionary.
- '''
- conf = Config(filename=conf_dict.get('__file__', ''))
-
- for k, v in six.iteritems(conf_dict):
- if k.startswith('__'):
- continue
- elif inspect.ismodule(v):
- continue
-
- conf[k] = v
- return conf
-
-
-def initconf():
- '''
- Initializes the default configuration and exposes it at
- ``pecan.configuration.conf``, which is also exposed at ``pecan.conf``.
- '''
- return conf_from_dict(DEFAULT)
-
-
-def set_config(config, overwrite=False):
- '''
- Updates the global configuration.
-
- :param config: Can be a dictionary containing configuration, or a string
- which represents a (relative) configuration filename.
- '''
-
- if config is None:
- config = get_conf_path_from_env()
-
- # must be after the fallback other a bad fallback will incorrectly clear
- if overwrite is True:
- _runtime_conf.empty()
-
- if isinstance(config, six.string_types):
- config = conf_from_file(config)
- _runtime_conf.update(config)
- if config.__file__:
- _runtime_conf.__file__ = config.__file__
- elif isinstance(config, dict):
- _runtime_conf.update(conf_from_dict(config))
- else:
- raise TypeError('%s is neither a dictionary or a string.' % config)
-
-
-_runtime_conf = initconf()
diff --git a/pecan/core.py b/pecan/core.py
deleted file mode 100644
index 854d948..0000000
--- a/pecan/core.py
+++ /dev/null
@@ -1,854 +0,0 @@
-try:
- from simplejson import dumps, loads
-except ImportError: # pragma: no cover
- from json import dumps, loads # noqa
-from inspect import Arguments
-from itertools import chain, tee
-from mimetypes import guess_type, add_type
-from os.path import splitext
-import logging
-import operator
-import sys
-import types
-
-import six
-
-from webob import (Request as WebObRequest, Response as WebObResponse, exc,
- acceptparse)
-from webob.multidict import NestedMultiDict
-
-from .compat import urlparse, izip
-from .secure import handle_security
-from .templating import RendererFactory
-from .routing import lookup_controller, NonCanonicalPath
-from .util import _cfg, encode_if_needed, getargspec
-from .middleware.recursive import ForwardRequestException
-
-if six.PY3:
- from .compat import is_bound_method as ismethod
-else:
- from inspect import ismethod
-
-# make sure that json is defined in mimetypes
-add_type('application/json', '.json', True)
-
-state = None
-logger = logging.getLogger(__name__)
-
-
-class RoutingState(object):
-
- def __init__(self, request, response, app, hooks=[], controller=None,
- arguments=None):
- self.request = request
- self.response = response
- self.app = app
- self.hooks = hooks
- self.controller = controller
- self.arguments = arguments
-
-
-class Request(WebObRequest):
-
- def __getattribute__(self, name):
- try:
- return WebObRequest.__getattribute__(self, name)
- except UnicodeDecodeError as e:
- logger.exception(e)
- abort(400)
-
-
-class Response(WebObResponse):
- pass
-
-
-def proxy(key):
- class ObjectProxy(object):
-
- explanation_ = AttributeError(
- "`pecan.state` is not bound to a context-local context.\n"
- "Ensure that you're accessing `pecan.request` or `pecan.response` "
- "from within the context of a WSGI `__call__` and that "
- "`use_context_locals` = True."
- )
-
- def __getattr__(self, attr):
- try:
- obj = getattr(state, key)
- except AttributeError:
- raise self.explanation_
- return getattr(obj, attr)
-
- def __setattr__(self, attr, value):
- obj = getattr(state, key)
- return setattr(obj, attr, value)
-
- def __delattr__(self, attr):
- obj = getattr(state, key)
- return delattr(obj, attr)
-
- def __dir__(self):
- obj = getattr(state, key)
- return dir(obj)
-
- return ObjectProxy()
-
-
-request = proxy('request')
-response = proxy('response')
-
-
-def override_template(template, content_type=None):
- '''
- Call within a controller to override the template that is used in
- your response.
-
- :param template: a valid path to a template file, just as you would specify
- in an ``@expose``.
- :param content_type: a valid MIME type to use for the response.func_closure
- '''
-
- request.pecan['override_template'] = template
- if content_type:
- request.pecan['override_content_type'] = content_type
-
-
-def abort(status_code=None, detail='', headers=None, comment=None, **kw):
- '''
- Raise an HTTP status code, as specified. Useful for returning status
- codes like 401 Unauthorized or 403 Forbidden.
-
- :param status_code: The HTTP status code as an integer.
- :param detail: The message to send along, as a string.
- :param headers: A dictionary of headers to send along with the response.
- :param comment: A comment to include in the response.
- '''
-
- # If there is a traceback, we need to catch it for a re-raise
- try:
- _, _, traceback = sys.exc_info()
- webob_exception = exc.status_map[status_code](
- detail=detail,
- headers=headers,
- comment=comment,
- **kw
- )
-
- if six.PY3:
- raise webob_exception.with_traceback(traceback)
- else:
- # Using exec to avoid python 3 parsers from crashing
- exec('raise webob_exception, None, traceback')
- finally:
- # Per the suggestion of the Python docs, delete the traceback object
- del traceback
-
-
-def redirect(location=None, internal=False, code=None, headers={},
- add_slash=False, request=None):
- '''
- Perform a redirect, either internal or external. An internal redirect
- performs the redirect server-side, while the external redirect utilizes
- an HTTP 302 status code.
-
- :param location: The HTTP location to redirect to.
- :param internal: A boolean indicating whether the redirect should be
- internal.
- :param code: The HTTP status code to use for the redirect. Defaults to 302.
- :param headers: Any HTTP headers to send with the response, as a
- dictionary.
- :param request: The :class:`pecan.Request` instance to use.
- '''
- request = request or state.request
-
- if add_slash:
- if location is None:
- split_url = list(urlparse.urlsplit(request.url))
- new_proto = request.environ.get(
- 'HTTP_X_FORWARDED_PROTO', split_url[0]
- )
- split_url[0] = new_proto
- else:
- split_url = urlparse.urlsplit(location)
-
- split_url[2] = split_url[2].rstrip('/') + '/'
- location = urlparse.urlunsplit(split_url)
-
- if not headers:
- headers = {}
- if internal:
- if code is not None:
- raise ValueError('Cannot specify a code for internal redirects')
- request.environ['pecan.recursive.context'] = request.context
- raise ForwardRequestException(location)
- if code is None:
- code = 302
- raise exc.status_map[code](location=location, headers=headers)
-
-
-def render(template, namespace, app=None):
- '''
- Render the specified template using the Pecan rendering framework
- with the specified template namespace as a dictionary. Useful in a
- controller where you have no template specified in the ``@expose``.
-
- :param template: The path to your template, as you would specify in
- ``@expose``.
- :param namespace: The namespace to use for rendering the template, as a
- dictionary.
- :param app: The instance of :class:`pecan.Pecan` to use
- '''
- app = app or state.app
- return app.render(template, namespace)
-
-
-def load_app(config, **kwargs):
- '''
- Used to load a ``Pecan`` application and its environment based on passed
- configuration.
-
- :param config: Can be a dictionary containing configuration, a string which
- represents a (relative) configuration filename
-
- returns a pecan.Pecan object
- '''
- from .configuration import _runtime_conf, set_config
- set_config(config, overwrite=True)
-
- for package_name in getattr(_runtime_conf.app, 'modules', []):
- module = __import__(package_name, fromlist=['app'])
- if hasattr(module, 'app') and hasattr(module.app, 'setup_app'):
- app = module.app.setup_app(_runtime_conf, **kwargs)
- app.config = _runtime_conf
- return app
- raise RuntimeError(
- 'No app.setup_app found in any of the configured app.modules'
- )
-
-
-class PecanBase(object):
-
- SIMPLEST_CONTENT_TYPES = (
- ['text/html'],
- ['text/plain']
- )
-
- def __init__(self, root, default_renderer='mako',
- template_path='templates', hooks=lambda: [],
- custom_renderers={}, extra_template_vars={},
- force_canonical=True, guess_content_type_from_ext=True,
- context_local_factory=None, request_cls=Request,
- response_cls=Response, **kw):
- if isinstance(root, six.string_types):
- root = self.__translate_root__(root)
-
- self.root = root
- self.request_cls = request_cls
- self.response_cls = response_cls
- self.renderers = RendererFactory(custom_renderers, extra_template_vars)
- self.default_renderer = default_renderer
-
- # pre-sort these so we don't have to do it per-request
- if six.callable(hooks):
- hooks = hooks()
-
- self.hooks = list(sorted(
- hooks,
- key=operator.attrgetter('priority')
- ))
- self.template_path = template_path
- self.force_canonical = force_canonical
- self.guess_content_type_from_ext = guess_content_type_from_ext
-
- def __translate_root__(self, item):
- '''
- Creates a root controller instance from a string root, e.g.,
-
- > __translate_root__("myproject.controllers.RootController")
- myproject.controllers.RootController()
-
- :param item: The string to the item
- '''
-
- if '.' in item:
- parts = item.split('.')
- name = '.'.join(parts[:-1])
- fromlist = parts[-1:]
-
- module = __import__(name, fromlist=fromlist)
- kallable = getattr(module, parts[-1])
- msg = "%s does not represent a callable class or function."
- if not six.callable(kallable):
- raise TypeError(msg % item)
- return kallable()
-
- raise ImportError('No item named %s' % item)
-
- def route(self, req, node, path):
- '''
- Looks up a controller from a node based upon the specified path.
-
- :param node: The node, such as a root controller object.
- :param path: The path to look up on this node.
- '''
- path = path.split('/')[1:]
- try:
- node, remainder = lookup_controller(node, path, req)
- return node, remainder
- except NonCanonicalPath as e:
- if self.force_canonical and \
- not _cfg(e.controller).get('accept_noncanonical', False):
- if req.method == 'POST':
- raise RuntimeError(
- "You have POSTed to a URL '%s' which "
- "requires a slash. Most browsers will not maintain "
- "POST data when redirected. Please update your code "
- "to POST to '%s/' or set force_canonical to False" %
- (req.pecan['routing_path'],
- req.pecan['routing_path'])
- )
- redirect(code=302, add_slash=True, request=req)
- return e.controller, e.remainder
-
- def determine_hooks(self, controller=None):
- '''
- Determines the hooks to be run, in which order.
-
- :param controller: If specified, includes hooks for a specific
- controller.
- '''
-
- controller_hooks = []
- if controller:
- controller_hooks = _cfg(controller).get('hooks', [])
- if controller_hooks:
- return list(
- sorted(
- chain(controller_hooks, self.hooks),
- key=operator.attrgetter('priority')
- )
- )
- return self.hooks
-
- def handle_hooks(self, hooks, hook_type, *args):
- '''
- Processes hooks of the specified type.
-
- :param hook_type: The type of hook, including ``before``, ``after``,
- ``on_error``, and ``on_route``.
- :param \*args: Arguments to pass to the hooks.
- '''
- if hook_type not in ['before', 'on_route']:
- hooks = reversed(hooks)
-
- for hook in hooks:
- result = getattr(hook, hook_type)(*args)
- # on_error hooks can choose to return a Response, which will
- # be used instead of the standard error pages.
- if hook_type == 'on_error' and isinstance(result, WebObResponse):
- return result
-
- def get_args(self, state, all_params, remainder, argspec, im_self):
- '''
- Determines the arguments for a controller based upon parameters
- passed the argument specification for the controller.
- '''
- args = []
- varargs = []
- kwargs = dict()
- valid_args = argspec.args[:]
- if ismethod(state.controller) or im_self:
- valid_args.pop(0) # pop off `self`
- pecan_state = state.request.pecan
-
- remainder = [x for x in remainder if x]
-
- if im_self is not None:
- args.append(im_self)
-
- # grab the routing args from nested REST controllers
- if 'routing_args' in pecan_state:
- remainder = pecan_state['routing_args'] + list(remainder)
- del pecan_state['routing_args']
-
- # handle positional arguments
- if valid_args and remainder:
- args.extend(remainder[:len(valid_args)])
- remainder = remainder[len(valid_args):]
- valid_args = valid_args[len(args):]
-
- # handle wildcard arguments
- if [i for i in remainder if i]:
- if not argspec[1]:
- abort(404)
- varargs.extend(remainder)
-
- # get the default positional arguments
- if argspec[3]:
- defaults = dict(izip(argspec[0][-len(argspec[3]):], argspec[3]))
- else:
- defaults = dict()
-
- # handle positional GET/POST params
- for name in valid_args:
- if name in all_params:
- args.append(all_params.pop(name))
- elif name in defaults:
- args.append(defaults[name])
- else:
- break
-
- # handle wildcard GET/POST params
- if argspec[2]:
- for name, value in six.iteritems(all_params):
- if name not in argspec[0]:
- kwargs[encode_if_needed(name)] = value
-
- return args, varargs, kwargs
-
- def render(self, template, namespace):
- if template == 'json':
- renderer = self.renderers.get('json', self.template_path)
- elif ':' in template:
- renderer_name, template = template.split(':', 1)
- renderer = self.renderers.get(
- renderer_name,
- self.template_path
- )
- else:
- renderer = self.renderers.get(
- self.default_renderer,
- self.template_path
- )
- return renderer.render(template, namespace)
-
- def find_controller(self, state):
- '''
- The main request handler for Pecan applications.
- '''
- # get a sorted list of hooks, by priority (no controller hooks yet)
- req = state.request
- pecan_state = req.pecan
-
- # store the routing path for the current application to allow hooks to
- # modify it
- pecan_state['routing_path'] = path = req.path_info
-
- # handle "on_route" hooks
- self.handle_hooks(self.hooks, 'on_route', state)
-
- # lookup the controller, respecting content-type as requested
- # by the file extension on the URI
- pecan_state['extension'] = None
-
- # attempt to guess the content type based on the file extension
- if self.guess_content_type_from_ext \
- and not pecan_state['content_type'] \
- and '.' in path:
- new_path, extension = splitext(path)
-
- # preface with a letter to ensure compat for 2.5
- potential_type = guess_type('x' + extension)[0]
-
- if potential_type is not None:
- path = new_path
- pecan_state['extension'] = extension
- pecan_state['content_type'] = potential_type
-
- controller, remainder = self.route(req, self.root, path)
- cfg = _cfg(controller)
-
- if cfg.get('generic_handler'):
- raise exc.HTTPNotFound
-
- # handle generic controllers
- im_self = None
- if cfg.get('generic'):
- im_self = six.get_method_self(controller)
- handlers = cfg['generic_handlers']
- controller = handlers.get(req.method, handlers['DEFAULT'])
- handle_security(controller, im_self)
- cfg = _cfg(controller)
-
- # add the controller to the state so that hooks can use it
- state.controller = controller
-
- # if unsure ask the controller for the default content type
- content_types = cfg.get('content_types', {})
- if not pecan_state['content_type']:
- # attempt to find a best match based on accept headers (if they
- # exist)
- accept = getattr(req.accept, 'header_value', '*/*')
- if accept == '*/*' or (
- accept.startswith('text/html,') and
- list(content_types.keys()) in self.SIMPLEST_CONTENT_TYPES):
- pecan_state['content_type'] = cfg.get(
- 'content_type',
- 'text/html'
- )
- else:
- best_default = acceptparse.MIMEAccept(
- accept
- ).best_match(
- content_types.keys()
- )
-
- if best_default is None:
- msg = "Controller '%s' defined does not support " + \
- "content_type '%s'. Supported type(s): %s"
- logger.error(
- msg % (
- controller.__name__,
- pecan_state['content_type'],
- content_types.keys()
- )
- )
- raise exc.HTTPNotAcceptable()
-
- pecan_state['content_type'] = best_default
- elif cfg.get('content_type') is not None and \
- pecan_state['content_type'] not in content_types:
-
- msg = "Controller '%s' defined does not support content_type " + \
- "'%s'. Supported type(s): %s"
- logger.error(
- msg % (
- controller.__name__,
- pecan_state['content_type'],
- content_types.keys()
- )
- )
- raise exc.HTTPNotFound
-
- # fetch any parameters
- if req.method == 'GET':
- params = req.GET
- elif req.content_type in ('application/json',
- 'application/javascript'):
- try:
- if not isinstance(req.json, dict):
- raise TypeError('%s is not a dict' % req.json)
- params = NestedMultiDict(req.GET, req.json)
- except (TypeError, ValueError):
- params = req.params
- else:
- params = req.params
-
- # fetch the arguments for the controller
- args, varargs, kwargs = self.get_args(
- state,
- params.mixed(),
- remainder,
- cfg['argspec'],
- im_self
- )
- state.arguments = Arguments(args, varargs, kwargs)
-
- # handle "before" hooks
- self.handle_hooks(self.determine_hooks(controller), 'before', state)
-
- return controller, args + varargs, kwargs
-
- def invoke_controller(self, controller, args, kwargs, state):
- '''
- The main request handler for Pecan applications.
- '''
- cfg = _cfg(controller)
- content_types = cfg.get('content_types', {})
- req = state.request
- resp = state.response
- pecan_state = req.pecan
-
- # If a keyword is supplied via HTTP GET or POST arguments, but the
- # function signature does not allow it, just drop it (rather than
- # generating a TypeError).
- argspec = getargspec(controller)
- keys = kwargs.keys()
- for key in keys:
- if key not in argspec.args and not argspec.keywords:
- kwargs.pop(key)
-
- # get the result from the controller
- result = controller(*args, **kwargs)
-
- # a controller can return the response object which means they've taken
- # care of filling it out
- if result is response:
- return
- elif isinstance(result, WebObResponse):
- state.response = result
- return
-
- raw_namespace = result
-
- # pull the template out based upon content type and handle overrides
- template = content_types.get(pecan_state['content_type'])
-
- # check if for controller override of template
- template = pecan_state.get('override_template', template)
- if template is None and cfg['explicit_content_type'] is False:
- if self.default_renderer == 'json':
- template = 'json'
-
- pecan_state['content_type'] = pecan_state.get(
- 'override_content_type',
- pecan_state['content_type']
- )
-
- # if there is a template, render it
- if template:
- if template == 'json':
- pecan_state['content_type'] = 'application/json'
- result = self.render(template, result)
-
- # If we are in a test request put the namespace where it can be
- # accessed directly
- if req.environ.get('paste.testing'):
- testing_variables = req.environ['paste.testing_variables']
- testing_variables['namespace'] = raw_namespace
- testing_variables['template_name'] = template
- testing_variables['controller_output'] = result
-
- # set the body content
- if result and isinstance(result, six.text_type):
- resp.text = result
- elif result:
- resp.body = result
-
- if pecan_state['content_type']:
- # set the content type
- resp.content_type = pecan_state['content_type']
-
- def _handle_empty_response_body(self, state):
- # Enforce HTTP 204 for responses which contain no body
- if state.response.status_int == 200:
- # If the response is a generator...
- if isinstance(state.response.app_iter, types.GeneratorType):
- # Split the generator into two so we can peek at one of them
- # and determine if there is any response body content
- a, b = tee(state.response.app_iter)
- try:
- next(a)
- except StopIteration:
- # If we hit StopIteration, the body is empty
- state.response.status = 204
- finally:
- state.response.app_iter = b
- else:
- text = None
- if state.response.charset:
- # `response.text` cannot be accessed without a valid
- # charset (because we don't know which encoding to use)
- try:
- text = state.response.text
- except UnicodeDecodeError:
- # If a valid charset is not specified, don't bother
- # trying to guess it (because there's obviously
- # content, so we know this shouldn't be a 204)
- pass
- if not any((state.response.body, text)):
- state.response.status = 204
-
- if state.response.status_int in (204, 304):
- state.response.content_type = None
-
- def __call__(self, environ, start_response):
- '''
- Implements the WSGI specification for Pecan applications, utilizing
- ``WebOb``.
- '''
-
- # create the request and response object
- req = self.request_cls(environ)
- resp = self.response_cls()
- state = RoutingState(req, resp, self)
- environ['pecan.locals'] = {
- 'request': req,
- 'response': resp
- }
- controller = None
-
- # handle the request
- try:
- # add context and environment to the request
- req.context = environ.get('pecan.recursive.context', {})
- req.pecan = dict(content_type=None)
-
- controller, args, kwargs = self.find_controller(state)
- self.invoke_controller(controller, args, kwargs, state)
- except Exception as e:
- # if this is an HTTP Exception, set it as the response
- if isinstance(e, exc.HTTPException):
- # if the client asked for JSON, do our best to provide it
- best_match = acceptparse.MIMEAccept(
- getattr(req.accept, 'header_value', '*/*')
- ).best_match(('text/plain', 'text/html', 'application/json'))
- state.response = e
- if best_match == 'application/json':
- json_body = dumps({
- 'code': e.status_int,
- 'title': e.title,
- 'description': e.detail
- })
- if isinstance(json_body, six.text_type):
- e.text = json_body
- else:
- e.body = json_body
- state.response.content_type = best_match
- environ['pecan.original_exception'] = e
-
- # if this is not an internal redirect, run error hooks
- on_error_result = None
- if not isinstance(e, ForwardRequestException):
- on_error_result = self.handle_hooks(
- self.determine_hooks(state.controller),
- 'on_error',
- state,
- e
- )
-
- # if the on_error handler returned a Response, use it.
- if isinstance(on_error_result, WebObResponse):
- state.response = on_error_result
- else:
- if not isinstance(e, exc.HTTPException):
- raise
-
- # if this is an HTTP 405, attempt to specify an Allow header
- if isinstance(e, exc.HTTPMethodNotAllowed) and controller:
- allowed_methods = _cfg(controller).get('allowed_methods', [])
- if allowed_methods:
- state.response.allow = sorted(allowed_methods)
- finally:
- # handle "after" hooks
- self.handle_hooks(
- self.determine_hooks(state.controller), 'after', state
- )
-
- self._handle_empty_response_body(state)
-
- # get the response
- return state.response(environ, start_response)
-
-
-class ExplicitPecan(PecanBase):
-
- def get_args(self, state, all_params, remainder, argspec, im_self):
- # When comparing the argspec of the method to GET/POST params,
- # ignore the implicit (req, resp) at the beginning of the function
- # signature
- if hasattr(state.controller, '__self__'):
- _repr = '.'.join((
- state.controller.__self__.__class__.__module__,
- state.controller.__self__.__class__.__name__,
- state.controller.__name__
- ))
- else:
- _repr = '.'.join((
- state.controller.__module__,
- state.controller.__name__
- ))
-
- signature_error = TypeError(
- 'When `use_context_locals` is `False`, pecan passes an explicit '
- 'reference to the request and response as the first two arguments '
- 'to the controller.\nChange the `%s` signature to accept exactly '
- '2 initial arguments (req, resp)' % _repr
- )
- try:
- positional = argspec.args[:]
- positional.pop(1) # req
- positional.pop(1) # resp
- argspec = argspec._replace(args=positional)
- except IndexError:
- raise signature_error
-
- args, varargs, kwargs = super(ExplicitPecan, self).get_args(
- state, all_params, remainder, argspec, im_self
- )
-
- if ismethod(state.controller):
- args = [state.request, state.response] + args
- else:
- # generic controllers have an explicit self *first*
- # (because they're decorated functions, not instance methods)
- args[1:1] = [state.request, state.response]
- return args, varargs, kwargs
-
-
-class Pecan(PecanBase):
- '''
- Pecan application object. Generally created using ``pecan.make_app``,
- rather than being created manually.
-
- Creates a Pecan application instance, which is a WSGI application.
-
- :param root: A string representing a root controller object (e.g.,
- "myapp.controller.root.RootController")
- :param default_renderer: The default template rendering engine to use.
- Defaults to mako.
- :param template_path: A relative file system path (from the project root)
- where template files live. Defaults to 'templates'.
- :param hooks: A callable which returns a list of
- :class:`pecan.hooks.PecanHook`
- :param custom_renderers: Custom renderer objects, as a dictionary keyed
- by engine name.
- :param extra_template_vars: Any variables to inject into the template
- namespace automatically.
- :param force_canonical: A boolean indicating if this project should
- require canonical URLs.
- :param guess_content_type_from_ext: A boolean indicating if this project
- should use the extension in the URL for guessing
- the content type to return.
- :param use_context_locals: When `True`, `pecan.request` and
- `pecan.response` will be available as
- thread-local references.
- :param request_cls: Can be used to specify a custom `pecan.request` object.
- Defaults to `pecan.Request`.
- :param response_cls: Can be used to specify a custom `pecan.response`
- object. Defaults to `pecan.Response`.
- '''
-
- def __new__(cls, *args, **kw):
- if kw.get('use_context_locals') is False:
- self = super(Pecan, cls).__new__(ExplicitPecan, *args, **kw)
- self.__init__(*args, **kw)
- return self
- return super(Pecan, cls).__new__(cls)
-
- def __init__(self, *args, **kw):
- self.init_context_local(kw.get('context_local_factory'))
- super(Pecan, self).__init__(*args, **kw)
-
- def __call__(self, environ, start_response):
- try:
- state.hooks = []
- state.app = self
- state.controller = None
- state.arguments = None
- return super(Pecan, self).__call__(environ, start_response)
- finally:
- del state.hooks
- del state.request
- del state.response
- del state.controller
- del state.arguments
- del state.app
-
- def init_context_local(self, local_factory):
- global state
- if local_factory is None:
- from threading import local as local_factory
- state = local_factory()
-
- def find_controller(self, _state):
- state.request = _state.request
- state.response = _state.response
- controller, args, kw = super(Pecan, self).find_controller(_state)
- state.controller = controller
- state.arguments = _state.arguments
- return controller, args, kw
-
- def handle_hooks(self, hooks, *args, **kw):
- state.hooks = hooks
- return super(Pecan, self).handle_hooks(hooks, *args, **kw)
diff --git a/pecan/decorators.py b/pecan/decorators.py
deleted file mode 100644
index 15808d4..0000000
--- a/pecan/decorators.py
+++ /dev/null
@@ -1,175 +0,0 @@
-from inspect import getmembers, isclass, ismethod, isfunction
-
-import six
-
-from .util import _cfg, getargspec
-
-__all__ = [
- 'expose', 'transactional', 'accept_noncanonical', 'after_commit',
- 'after_rollback'
-]
-
-
-def when_for(controller):
- def when(method=None, **kw):
- def decorate(f):
- _cfg(f)['generic_handler'] = True
- controller._pecan['generic_handlers'][method.upper()] = f
- controller._pecan['allowed_methods'].append(method.upper())
- expose(**kw)(f)
- return f
- return decorate
- return when
-
-
-def expose(template=None,
- generic=False,
- route=None,
- **kw):
-
- '''
- Decorator used to flag controller methods as being "exposed" for
- access via HTTP, and to configure that access.
-
- :param template: The path to a template, relative to the base template
- directory.
- :param content_type: The content-type to use for this template.
- :param generic: A boolean which flags this as a "generic" controller,
- which uses generic functions based upon
- ``functools.singledispatch`` generic functions. Allows you
- to split a single controller into multiple paths based upon
- HTTP method.
- :param route: The name of the path segment to match (excluding
- separator characters, like `/`). Defaults to the name of
- the function itself, but this can be used to resolve paths
- which are not valid Python function names, e.g., if you
- wanted to route a function to `some-special-path'.
- '''
-
- content_type = kw.get('content_type', 'text/html')
-
- if template == 'json':
- content_type = 'application/json'
-
- def decorate(f):
- # flag the method as exposed
- f.exposed = True
-
- cfg = _cfg(f)
- cfg['explicit_content_type'] = 'content_type' in kw
-
- if route:
- # This import is here to avoid a circular import issue
- from pecan import routing
- if cfg.get('generic_handler'):
- raise ValueError(
- 'Path segments cannot be overridden for generic '
- 'controllers.'
- )
- routing.route(route, f)
-
- # set a "pecan" attribute, where we will store details
- cfg['content_type'] = content_type
- cfg.setdefault('template', []).append(template)
- cfg.setdefault('content_types', {})[content_type] = template
-
- # handle generic controllers
- if generic:
- if f.__name__ in ('_default', '_lookup', '_route'):
- raise ValueError(
- 'The special method %s cannot be used as a generic '
- 'controller' % f.__name__
- )
- cfg['generic'] = True
- cfg['generic_handlers'] = dict(DEFAULT=f)
- cfg['allowed_methods'] = []
- f.when = when_for(f)
-
- # store the arguments for this controller method
- cfg['argspec'] = getargspec(f)
-
- return f
-
- return decorate
-
-
-def transactional(ignore_redirects=True):
- '''
- If utilizing the :mod:`pecan.hooks` ``TransactionHook``, allows you
- to flag a controller method or class as being wrapped in a transaction,
- regardless of HTTP method.
-
- :param ignore_redirects: Indicates if the hook should ignore redirects
- for this controller or not.
- '''
-
- def deco(f):
- if isclass(f):
- for meth in [
- m[1] for m in getmembers(f)
- if (isfunction if six.PY3 else ismethod)(m[1])
- ]:
- if getattr(meth, 'exposed', False):
- _cfg(meth)['transactional'] = True
- _cfg(meth)['transactional_ignore_redirects'] = _cfg(
- meth
- ).get(
- 'transactional_ignore_redirects',
- ignore_redirects
- )
- else:
- _cfg(f)['transactional'] = True
- _cfg(f)['transactional_ignore_redirects'] = ignore_redirects
- return f
- return deco
-
-
-def after_action(action_type, action):
- '''
- If utilizing the :mod:`pecan.hooks` ``TransactionHook``, allows you
- to flag a controller method to perform a callable action after the
- action_type is successfully issued.
-
- :param action: The callable to call after the commit is successfully
- issued. '''
-
- if action_type not in ('commit', 'rollback'):
- raise Exception('action_type (%s) is not valid' % action_type)
-
- def deco(func):
- _cfg(func).setdefault('after_%s' % action_type, []).append(action)
- return func
- return deco
-
-
-def after_commit(action):
- '''
- If utilizing the :mod:`pecan.hooks` ``TransactionHook``, allows you
- to flag a controller method to perform a callable action after the
- commit is successfully issued.
-
- :param action: The callable to call after the commit is successfully
- issued.
- '''
- return after_action('commit', action)
-
-
-def after_rollback(action):
- '''
- If utilizing the :mod:`pecan.hooks` ``TransactionHook``, allows you
- to flag a controller method to perform a callable action after the
- rollback is successfully issued.
-
- :param action: The callable to call after the rollback is successfully
- issued.
- '''
- return after_action('rollback', action)
-
-
-def accept_noncanonical(func):
- '''
- Flags a controller method as accepting non-canoncial URLs.
- '''
-
- _cfg(func)['accept_noncanonical'] = True
- return func
diff --git a/pecan/deploy.py b/pecan/deploy.py
deleted file mode 100644
index be6b359..0000000
--- a/pecan/deploy.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from .core import load_app
-
-
-def deploy(config):
- """
- Given a config (dictionary of relative filename), returns a configured
- WSGI app.
- """
- return load_app(config)
diff --git a/pecan/ext/__init__.py b/pecan/ext/__init__.py
deleted file mode 100644
index c768f9c..0000000
--- a/pecan/ext/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-def install():
- from pecan.extensions import PecanExtensionImporter
- PecanExtensionImporter().install()
-
-install()
-del install
diff --git a/pecan/extensions.py b/pecan/extensions.py
deleted file mode 100644
index 87b9925..0000000
--- a/pecan/extensions.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import sys
-import pkg_resources
-import inspect
-import logging
-
-log = logging.getLogger(__name__)
-
-
-class PecanExtensionMissing(ImportError):
- pass
-
-
-class PecanExtensionImporter(object):
- """
- Short circuits imports for extensions.
-
- This is used in combination with ``pecan.ext`` so that when a user does
- ``from pecan.ext import foo``, it will attempt to map ``foo`` to a
- registered setuptools entry point in some other (Pecan extension) project.
-
- Conversely, an extension developer may define an entry point in his
- ``setup.py``, e.g.,
-
- setup(
- ...
- entry_points='''
- [pecan.extension]
- celery = pecancelery.lib.core
- '''
- )
-
- This is mostly for convenience and consistency. In this way, Pecan can
- maintain an ecosystem of extensions that share a common namespace,
- ``pecan.ext``, while still maintaining backwards compatibility for simple
- package names (e.g., ``pecancelery``).
- """
-
- extension_module = 'pecan.ext'
- prefix = extension_module + '.'
-
- def install(self):
- if self not in sys.meta_path:
- sys.meta_path.append(self)
-
- def __eq__(self, b):
- return self.__class__.__module__ == b.__class__.__module__ and \
- self.__class__.__name__ == b.__class__.__name__
-
- def __ne__(self, b):
- return not self.__eq__(b)
-
- def find_module(self, fullname, path=None):
- if fullname.startswith(self.prefix):
- return self
-
- def load_module(self, fullname):
- if fullname in sys.modules:
- return self
- extname = fullname.split(self.prefix)[1]
- module = self.find_module_for_extension(extname)
- realname = module.__name__
- try:
- __import__(realname)
- except ImportError:
- raise sys.exc_info()
- module = sys.modules[fullname] = sys.modules[realname]
- if '.' not in extname:
- setattr(sys.modules[self.extension_module], extname, module)
- return module
-
- def find_module_for_extension(self, name):
- for ep in pkg_resources.iter_entry_points('pecan.extension'):
- if ep.name != name:
- continue
- log.debug('%s loading extension %s', self.__class__.__name__, ep)
- module = ep.load()
- if not inspect.ismodule(module):
- log.debug('%s is not a module, skipping...' % module)
- continue
- return module
- raise PecanExtensionMissing(
- 'The `pecan.ext.%s` extension is not installed.' % name
- )
diff --git a/pecan/hooks.py b/pecan/hooks.py
deleted file mode 100644
index 0b666d3..0000000
--- a/pecan/hooks.py
+++ /dev/null
@@ -1,375 +0,0 @@
-import types
-import sys
-from inspect import getmembers
-
-import six
-from webob.exc import HTTPFound
-
-from .util import iscontroller, _cfg
-
-__all__ = [
- 'PecanHook', 'TransactionHook', 'HookController',
- 'RequestViewerHook'
-]
-
-
-def walk_controller(root_class, controller, hooks, seen=None):
- seen = seen or set()
- if type(controller) not in vars(six.moves.builtins).values():
- # Avoid recursion loops
- try:
- if controller in seen:
- return
- seen.add(controller)
- except TypeError:
- # If we discover an unhashable item (like a list), it's not
- # something that we want to traverse because it's not the sort of
- # thing we would add a hook to
- return
-
- for hook in getattr(controller, '__hooks__', []):
- # Append hooks from controller class definition
- hooks.add(hook)
-
- for name, value in getmembers(controller):
- if name == 'controller':
- continue
- if name.startswith('__') and name.endswith('__'):
- continue
-
- if iscontroller(value):
- for hook in hooks:
- value._pecan.setdefault('hooks', set()).add(hook)
- elif hasattr(value, '__class__'):
- # Skip non-exposed methods that are defined in parent classes;
- # they're internal implementation details of that class, and
- # not actual routable controllers, so we shouldn't bother
- # assigning hooks to them.
- if (
- isinstance(value, types.MethodType) and
- any(filter(lambda c: value.__func__ in c.__dict__.values(),
- value.im_class.mro()[1:]))
- ):
- continue
- walk_controller(root_class, value, hooks, seen)
-
-
-class HookControllerMeta(type):
- '''
- A base class for controllers that would like to specify hooks on
- their controller methods. Simply create a list of hook objects
- called ``__hooks__`` as a member of the controller's namespace.
- '''
-
- def __init__(cls, name, bases, dict_):
- hooks = set(dict_.get('__hooks__', []))
- for base in bases:
- # Add hooks from parent class and mixins
- for hook in getattr(base, '__hooks__', []):
- hooks.add(hook)
- walk_controller(cls, cls, hooks)
-
-
-HookController = HookControllerMeta(
- 'HookController',
- (object,),
- {'__doc__': ("A base class for controllers that would like to specify "
- "hooks on their controller methods. Simply create a list "
- "of hook objects called ``__hooks__`` as a class attribute "
- "of your controller.")}
-)
-
-
-class PecanHook(object):
- '''
- A base class for Pecan hooks. Inherit from this class to create your
- own hooks. Set a priority on a hook by setting the ``priority``
- attribute for the hook, which defaults to 100.
- '''
-
- priority = 100
-
- def on_route(self, state):
- '''
- Override this method to create a hook that gets called upon
- the start of routing.
-
- :param state: The Pecan ``state`` object for the current request.
- '''
- return
-
- def before(self, state):
- '''
- Override this method to create a hook that gets called after
- routing, but before the request gets passed to your controller.
-
- :param state: The Pecan ``state`` object for the current request.
- '''
- return
-
- def after(self, state):
- '''
- Override this method to create a hook that gets called after
- the request has been handled by the controller.
-
- :param state: The Pecan ``state`` object for the current request.
- '''
- return
-
- def on_error(self, state, e):
- '''
- Override this method to create a hook that gets called upon
- an exception being raised in your controller.
-
- :param state: The Pecan ``state`` object for the current request.
- :param e: The ``Exception`` object that was raised.
- '''
- return
-
-
-class TransactionHook(PecanHook):
- '''
- :param start: A callable that will bind to a writable database and
- start a transaction.
- :param start_ro: A callable that will bind to a readable database.
- :param commit: A callable that will commit the active transaction.
- :param rollback: A callable that will roll back the active
- transaction.
- :param clear: A callable that will clear your current context.
-
- A basic framework hook for supporting wrapping requests in
- transactions. By default, it will wrap all but ``GET`` and ``HEAD``
- requests in a transaction. Override the ``is_transactional`` method
- to define your own rules for what requests should be transactional.
- '''
-
- def __init__(self, start, start_ro, commit, rollback, clear):
-
- self.start = start
- self.start_ro = start_ro
- self.commit = commit
- self.rollback = rollback
- self.clear = clear
-
- def is_transactional(self, state):
- '''
- Decide if a request should be wrapped in a transaction, based
- upon the state of the request. By default, wraps all but ``GET``
- and ``HEAD`` requests in a transaction, along with respecting
- the ``transactional`` decorator from :mod:pecan.decorators.
-
- :param state: The Pecan state object for the current request.
- '''
-
- controller = getattr(state, 'controller', None)
- if controller:
- force_transactional = _cfg(controller).get('transactional', False)
- else:
- force_transactional = False
-
- if state.request.method not in ('GET', 'HEAD') or force_transactional:
- return True
- return False
-
- def on_route(self, state):
- state.request.error = False
- if self.is_transactional(state):
- state.request.transactional = True
- self.start()
- else:
- state.request.transactional = False
- self.start_ro()
-
- def before(self, state):
- if self.is_transactional(state) \
- and not getattr(state.request, 'transactional', False):
- self.clear()
- state.request.transactional = True
- self.start()
-
- def on_error(self, state, e):
- #
- # If we should ignore redirects,
- # (e.g., shouldn't consider them rollback-worthy)
- # don't set `state.request.error = True`.
- #
- trans_ignore_redirects = (
- state.request.method not in ('GET', 'HEAD')
- )
- if state.controller is not None:
- trans_ignore_redirects = (
- _cfg(state.controller).get(
- 'transactional_ignore_redirects',
- trans_ignore_redirects
- )
- )
- if type(e) is HTTPFound and trans_ignore_redirects is True:
- return
- state.request.error = True
-
- def after(self, state):
- if getattr(state.request, 'transactional', False):
- action_name = None
- if state.request.error:
- action_name = 'after_rollback'
- self.rollback()
- else:
- action_name = 'after_commit'
- self.commit()
-
- #
- # If a controller was routed to, find any
- # after_* actions it may have registered, and perform
- # them.
- #
- if action_name:
- controller = getattr(state, 'controller', None)
- if controller is not None:
- actions = _cfg(controller).get(action_name, [])
- for action in actions:
- action()
-
- self.clear()
-
-
-class RequestViewerHook(PecanHook):
- '''
- :param config: A (optional) dictionary that can hold ``items`` and/or
- ``blacklist`` keys.
- :param writer: The stream writer to use. Can redirect output to other
- streams as long as the passed in stream has a
- ``write`` callable method.
- :param terminal: Outputs to the chosen stream writer (usually
- the terminal)
- :param headers: Sets values to the X-HTTP headers
-
- Returns some information about what is going on in a single request. It
- accepts specific items to report on but uses a default list of items when
- none are passed in. Based on the requested ``url``, items can also be
- blacklisted.
- Configuration is flexible, can be passed in (or not) and can contain
- some or all the keys supported.
-
- **items**
-
- This key holds the items that this hook will display. When this key is
- passed only the items in the list will be used. Valid items are *any*
- item that the ``request`` object holds, by default it uses the
- following:
-
- * path
- * status
- * method
- * controller
- * params
- * hooks
-
- .. note::
- This key should always use a ``list`` of items to use.
-
- **blacklist**
-
- This key holds items that will be blacklisted based on ``url``. If
- there is a need to omit urls that start with `/javascript`, then this
- key would look like::
-
- 'blacklist': ['/javascript']
-
- As many blacklisting items as needed can be contained in the list. The hook
- will verify that the url is not starting with items in this list to display
- results, otherwise it will get omitted.
-
- .. note::
- This key should always use a ``list`` of items to use.
-
- For more detailed documentation about this hook, please see
- :ref:`requestviewerhook`
- '''
-
- available = ['path', 'status', 'method', 'controller', 'params', 'hooks']
-
- def __init__(self, config=None, writer=sys.stdout, terminal=True,
- headers=True):
-
- if not config:
- self.config = {'items': self.available}
- else:
- if config.__class__.__name__ == 'Config':
- self.config = config.to_dict()
- else:
- self.config = config
- self.writer = writer
- self.items = self.config.get('items', self.available)
- self.blacklist = self.config.get('blacklist', [])
- self.terminal = terminal
- self.headers = headers
-
- def after(self, state):
-
- # Default and/or custom response information
- responses = {
- 'controller': lambda self, state: self.get_controller(state),
- 'method': lambda self, state: state.request.method,
- 'path': lambda self, state: state.request.path,
- 'params': lambda self, state: [
- (p[0].encode('utf-8'), p[1].encode('utf-8'))
- for p in state.request.params.items()
- ],
- 'status': lambda self, state: state.response.status,
- 'hooks': lambda self, state: self.format_hooks(state.app.hooks),
- }
-
- is_available = [
- i for i in self.items
- if i in self.available or hasattr(state.request, i)
- ]
-
- terminal = []
- headers = []
- will_skip = [
- i for i in self.blacklist
- if state.request.path.startswith(i)
- ]
-
- if will_skip:
- return
-
- for request_info in is_available:
- try:
- value = responses.get(request_info)
- if not value:
- value = getattr(state.request, request_info)
- else:
- value = value(self, state)
- except Exception as e:
- value = e
-
- terminal.append('%-12s - %s\n' % (request_info, value))
- headers.append((request_info, value))
-
- if self.terminal:
- self.writer.write(''.join(terminal))
- self.writer.write('\n\n')
-
- if self.headers:
- for h in headers:
- key = str(h[0])
- value = str(h[1])
- name = 'X-Pecan-%s' % key
- state.response.headers[name] = value
-
- def get_controller(self, state):
- '''
- Retrieves the actual controller name from the application
- Specific to Pecan (not available in the request object)
- '''
- path = state.request.pecan['routing_path'].split('/')[1:]
- return state.controller.__str__().split()[2]
-
- def format_hooks(self, hooks):
- '''
- Tries to format the hook objects to be more readable
- Specific to Pecan (not available in the request object)
- '''
- str_hooks = [str(i).split()[0].strip('<') for i in hooks]
- return [i.split('.')[-1] for i in str_hooks if '.' in i]
diff --git a/pecan/jsonify.py b/pecan/jsonify.py
deleted file mode 100644
index 8eb310e..0000000
--- a/pecan/jsonify.py
+++ /dev/null
@@ -1,137 +0,0 @@
-try:
- from simplejson import JSONEncoder
-except ImportError: # pragma: no cover
- from json import JSONEncoder # noqa
-
-from datetime import datetime, date
-from decimal import Decimal
-
-# depending on the version WebOb might have 2 types of dicts
-try:
- # WebOb <= 1.1.1
- from webob.multidict import MultiDict, UnicodeMultiDict
- webob_dicts = (MultiDict, UnicodeMultiDict) # pragma: no cover
-except ImportError: # pragma no cover
- # WebOb >= 1.2
- from webob.multidict import MultiDict
- webob_dicts = (MultiDict,)
-
-import six
-try:
- from functools import singledispatch
-except ImportError: # pragma: no cover
- from singledispatch import singledispatch
-
-try:
- from sqlalchemy.engine.result import ResultProxy, RowProxy
-except ImportError: # pragma no cover
- try:
- from sqlalchemy.engine.base import ResultProxy, RowProxy
- except ImportError: # pragma no cover
- # dummy classes since we don't have SQLAlchemy installed
-
- class ResultProxy(object): # noqa
- pass
-
- class RowProxy(object): # noqa
- pass
-
-
-#
-# encoders
-#
-
-def is_saobject(obj):
- return hasattr(obj, '_sa_class_manager')
-
-
-class GenericJSON(JSONEncoder):
- '''
- Generic JSON encoder. Makes several attempts to correctly JSONify
- requested response objects.
- '''
- def default(self, obj):
- '''
- Converts an object and returns a ``JSON``-friendly structure.
-
- :param obj: object or structure to be converted into a
- ``JSON``-ifiable structure
-
- Considers the following special cases in order:
-
- * object has a callable __json__() attribute defined
- returns the result of the call to __json__()
- * date and datetime objects
- returns the object cast to str
- * Decimal objects
- returns the object cast to float
- * SQLAlchemy objects
- returns a copy of the object.__dict__ with internal SQLAlchemy
- parameters removed
- * SQLAlchemy ResultProxy objects
- Casts the iterable ResultProxy into a list of tuples containing
- the entire resultset data, returns the list in a dictionary
- along with the resultset "row" count.
-
- .. note:: {'count': 5, 'rows': [('Ed Jones',), ('Pete Jones',),
- ('Wendy Williams',), ('Mary Contrary',), ('Fred Smith',)]}
-
- * SQLAlchemy RowProxy objects
- Casts the RowProxy cursor object into a dictionary, probably
- losing its ordered dictionary behavior in the process but
- making it JSON-friendly.
- * webob_dicts objects
- returns webob_dicts.mixed() dictionary, which is guaranteed
- to be JSON-friendly.
- '''
- if hasattr(obj, '__json__') and six.callable(obj.__json__):
- return obj.__json__()
- elif isinstance(obj, (date, datetime)):
- return str(obj)
- elif isinstance(obj, Decimal):
- # XXX What to do about JSONEncoder crappy handling of Decimals?
- # SimpleJSON has better Decimal encoding than the std lib
- # but only in recent versions
- return float(obj)
- elif is_saobject(obj):
- props = {}
- for key in obj.__dict__:
- if not key.startswith('_sa_'):
- props[key] = getattr(obj, key)
- return props
- elif isinstance(obj, ResultProxy):
- props = dict(rows=list(obj), count=obj.rowcount)
- if props['count'] < 0:
- props['count'] = len(props['rows'])
- return props
- elif isinstance(obj, RowProxy):
- return dict(obj)
- elif isinstance(obj, webob_dicts):
- return obj.mixed()
- else:
- return JSONEncoder.default(self, obj)
-
-_default = GenericJSON()
-
-
-def with_when_type(f):
- # Add some backwards support for simplegeneric's API
- f.when_type = f.register
- return f
-
-
-@with_when_type
-@singledispatch
-def jsonify(obj):
- return _default.default(obj)
-
-
-class GenericFunctionJSON(GenericJSON):
- def default(self, obj):
- return jsonify(obj)
-
-_instance = GenericFunctionJSON()
-
-
-def encode(obj):
- return _instance.encode(obj)
diff --git a/pecan/log.py b/pecan/log.py
deleted file mode 100644
index 133fdf5..0000000
--- a/pecan/log.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import logging
-
-from logutils.colorize import ColorizingStreamHandler
-
-
-class DefaultColorizer(ColorizingStreamHandler):
-
- level_map = {
- logging.DEBUG: (None, 'blue', True),
- logging.INFO: (None, None, True),
- logging.WARNING: (None, 'yellow', True),
- logging.ERROR: (None, 'red', True),
- logging.CRITICAL: (None, 'red', True),
- }
-
-
-class ColorFormatter(logging.Formatter):
- """
- A very basic logging formatter that not only applies color to the
- levels of the ouput but can also add padding to the the level names so that
- they do not alter the visuals of logging when presented on the terminal.
-
- The padding is provided by a convenient keyword that adds padding to the
- ``levelname`` so that log output is easier to follow::
-
- %(padded_color_levelname)s
-
- Which would result in log level output that looks like::
-
- [INFO ]
- [WARNING ]
- [ERROR ]
- [DEBUG ]
- [CRITICAL]
-
- If colored output is not supported, it falls back to non-colored output
- without any extra settings.
- """
-
- def __init__(self, _logging=None, colorizer=None, *a, **kw):
- self.logging = _logging or logging
- self.color = colorizer or DefaultColorizer()
- logging.Formatter.__init__(self, *a, **kw)
-
- def format(self, record):
- levelname = record.levelname
- padded_level = '%-8s' % levelname
-
- record.color_levelname = self.color.colorize(levelname, record)
- record.padded_color_levelname = self.color.colorize(
- padded_level,
- record
- )
- return self.logging.Formatter.format(self, record)
diff --git a/pecan/middleware/__init__.py b/pecan/middleware/__init__.py
deleted file mode 100644
index 0c82911..0000000
--- a/pecan/middleware/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from . import errordocument
-from . import recursive
-from . import static
diff --git a/pecan/middleware/debug.py b/pecan/middleware/debug.py
deleted file mode 100644
index 1a6a482..0000000
--- a/pecan/middleware/debug.py
+++ /dev/null
@@ -1,96 +0,0 @@
-__CONFIG_HELP__ = '''
-<div class="traceback">
- <b>To disable this interface, set </b>
- <a target="window"
- href="https://pecan.readthedocs.org/en/latest/deployment.html#disabling-debug-mode">
- <pre>conf.app.debug = False</pre>
- </a>
-</div>
-''' # noqa
-
-try:
- import re
- from backlash.debug import DebuggedApplication
-
- class DebugMiddleware(DebuggedApplication):
-
- body_re = re.compile('(<body[^>]*>)', re.I)
-
- def debug_application(self, environ, start_response):
- for part in super(DebugMiddleware, self).debug_application(
- environ, start_response
- ):
- yield self.body_re.sub('\g<1>%s' % __CONFIG_HELP__, part)
-
-
-except ImportError:
- from traceback import print_exc
- from pprint import pformat
-
- from mako.template import Template
- from six.moves import cStringIO as StringIO
- from webob import Response
- from webob.exc import HTTPException
-
- debug_template_raw = '''<html>
- <head>
- <title>Pecan - Application Error</title>
- <body>
- <header>
- <h1>
- An error occurred!
- </h1>
- </header>
- <div id="error-content">
- <p>
- %(config_help)s
- Pecan offers support for interactive debugging by installing the <a href="https://pypi.python.org/pypi/backlash" target="window">backlash</a> package:
- <br />
- <b><pre>pip install backlash</pre></b>
- ...and reloading this page.
- </p>
- <h2>Traceback</h2>
- <div id="traceback">
- <pre>${traceback}</pre>
- </div>
- <h2>WSGI Environment</h2>
- <div id="environ">
- <pre>${environment}</pre>
- </div>
- </div>
- </body>
- </html>
- ''' % {'config_help': __CONFIG_HELP__} # noqa
-
- debug_template = Template(debug_template_raw)
-
- class DebugMiddleware(object):
-
- def __init__(self, app, *args, **kwargs):
- self.app = app
-
- def __call__(self, environ, start_response):
- try:
- return self.app(environ, start_response)
- except Exception as exc:
- # get a formatted exception
- out = StringIO()
- print_exc(file=out)
-
- # get formatted WSGI environment
- formatted_environ = pformat(environ)
-
- # render our template
- result = debug_template.render(
- traceback=out.getvalue(),
- environment=formatted_environ
- )
-
- # construct and return our response
- response = Response()
- if isinstance(exc, HTTPException):
- response.status_int = exc.status
- else:
- response.status_int = 500
- response.unicode_body = result
- return response(environ, start_response)
diff --git a/pecan/middleware/errordocument.py b/pecan/middleware/errordocument.py
deleted file mode 100644
index 12a3c9e..0000000
--- a/pecan/middleware/errordocument.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import sys
-
-from six import b as b_
-from .recursive import ForwardRequestException, RecursionLoop
-
-
-class StatusPersist(object):
-
- def __init__(self, app, status, url):
- self.app = app
- self.status = status
- self.url = url
-
- def __call__(self, environ, start_response):
- def keep_status_start_response(status, headers, exc_info=None):
- return start_response(self.status, headers, exc_info)
- parts = self.url.split('?')
- environ['PATH_INFO'] = parts[0]
- if len(parts) > 1:
- environ['QUERY_STRING'] = parts[1]
- else:
- environ['QUERY_STRING'] = ''
-
- try:
- return self.app(environ, keep_status_start_response)
- except RecursionLoop as e:
- environ['wsgi.errors'].write(
- 'Recursion error getting error page: %s\n' % e
- )
- keep_status_start_response(
- '500 Server Error',
- [('Content-type', 'text/plain')],
- sys.exc_info()
- )
- return [b_(
- 'Error: %s. (Error page could not be fetched)' % self.status
- )]
-
-
-class ErrorDocumentMiddleware(object):
- '''
- Intersects HTTP response status code, looks it up in the error map defined
- in the Pecan app config.py, and routes to the controller assigned to that
- status.
- '''
- def __init__(self, app, error_map):
- self.app = app
- self.error_map = error_map
-
- def __call__(self, environ, start_response):
-
- def replacement_start_response(status, headers, exc_info=None):
- '''
- Overrides the default response if the status is defined in the
- Pecan app error map configuration.
- '''
- try:
- status_code = int(status.split(' ')[0])
- except (ValueError, TypeError): # pragma: nocover
- raise Exception((
- 'ErrorDocumentMiddleware received an invalid '
- 'status %s' % status
- ))
-
- if status_code in self.error_map:
- def factory(app):
- return StatusPersist(
- app,
- status,
- self.error_map[status_code]
- )
- raise ForwardRequestException(factory=factory)
- return start_response(status, headers, exc_info)
-
- app_iter = self.app(environ, replacement_start_response)
- return app_iter
diff --git a/pecan/middleware/recursive.py b/pecan/middleware/recursive.py
deleted file mode 100644
index fb9d19c..0000000
--- a/pecan/middleware/recursive.py
+++ /dev/null
@@ -1,184 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste
-# Licensed under the MIT license:
-# http://www.opensource.org/licenses/mit-license.php
-
-"""
-Middleware to make internal requests and forward requests internally.
-
-Raise ``ForwardRequestException(new_path_info)`` to do a forward
-(aborting the current request).
-"""
-
-__all__ = ['RecursiveMiddleware']
-
-
-class RecursionLoop(AssertionError):
- # Subclasses AssertionError for legacy reasons
- """Raised when a recursion enters into a loop"""
-
-
-class CheckForRecursionMiddleware(object):
- def __init__(self, app, env):
- self.app = app
- self.env = env
-
- def __call__(self, environ, start_response):
- path_info = environ.get('PATH_INFO', '')
- if path_info in self.env.get('pecan.recursive.old_path_info', []):
- raise RecursionLoop(
- "Forwarding loop detected; %r visited twice (internal "
- "redirect path: %s)"
- % (path_info, self.env['pecan.recursive.old_path_info'])
- )
- old_path_info = self.env.setdefault(
- 'pecan.recursive.old_path_info', []
- )
- old_path_info.append(self.env.get('PATH_INFO', ''))
- return self.app(environ, start_response)
-
-
-class RecursiveMiddleware(object):
-
- """
- A WSGI middleware that allows for recursive and forwarded calls.
- All these calls go to the same 'application', but presumably that
- application acts differently with different URLs. The forwarded
- URLs must be relative to this container.
- """
-
- def __init__(self, application, global_conf=None):
- self.application = application
-
- def __call__(self, environ, start_response):
- my_script_name = environ.get('SCRIPT_NAME', '')
- environ['pecan.recursive.script_name'] = my_script_name
- try:
- return self.application(environ, start_response)
- except ForwardRequestException as e:
- middleware = CheckForRecursionMiddleware(
- e.factory(self), environ)
- return middleware(environ, start_response)
-
-
-class ForwardRequestException(Exception):
- """
- Used to signal that a request should be forwarded to a different location.
-
- ``url``
- The URL to forward to starting with a ``/`` and relative to
- ``RecursiveMiddleware``. URL fragments can also contain query strings
- so ``/error?code=404`` would be a valid URL fragment.
-
- ``environ``
- An altertative WSGI environment dictionary to use for the forwarded
- request. If specified is used *instead* of the ``url_fragment``
-
- ``factory``
- If specifed ``factory`` is used instead of ``url`` or ``environ``.
- ``factory`` is a callable that takes a WSGI application object
- as the first argument and returns an initialised WSGI middleware
- which can alter the forwarded response.
-
- Basic usage (must have ``RecursiveMiddleware`` present) :
-
- .. code-block:: python
-
- from pecan.middleware.recursive import ForwardRequestException
- def app(environ, start_response):
- if environ['PATH_INFO'] == '/hello':
- start_response("200 OK", [('Content-type', 'text/plain')])
- return ['Hello World!']
- elif environ['PATH_INFO'] == '/error':
- start_response("404 Not Found",
- [('Content-type', 'text/plain')]
- )
- return ['Page not found']
- else:
- raise ForwardRequestException('/error')
-
- from pecan.middleware.recursive import RecursiveMiddleware
- app = RecursiveMiddleware(app)
-
- If you ran this application and visited ``/hello`` you would get a
- ``Hello World!`` message. If you ran the application and visited
- ``/not_found`` a ``ForwardRequestException`` would be raised and the caught
- by the ``RecursiveMiddleware``. The ``RecursiveMiddleware`` would then
- return the headers and response from the ``/error`` URL but would display
- a ``404 Not found`` status message.
-
- You could also specify an ``environ`` dictionary instead of a url. Using
- the same example as before:
-
- .. code-block:: python
-
- def app(environ, start_response):
- ... same as previous example ...
- else:
- new_environ = environ.copy()
- new_environ['PATH_INFO'] = '/error'
- raise ForwardRequestException(environ=new_environ)
- """
-
- def __init__(self, url=None, environ={}, factory=None, path_info=None):
- # Check no incompatible options have been chosen
- if factory and url:
- raise TypeError(
- 'You cannot specify factory and a url in '
- 'ForwardRequestException'
- ) # pragma: nocover
- elif factory and environ:
- raise TypeError(
- 'You cannot specify factory and environ in '
- 'ForwardRequestException'
- ) # pragma: nocover
- if url and environ:
- raise TypeError(
- 'You cannot specify environ and url in '
- 'ForwardRequestException'
- ) # pragma: nocover
-
- # set the path_info or warn about its use.
- if path_info:
- self.path_info = path_info
-
- # If the url can be treated as a path_info do that
- if url and '?' not in str(url):
- self.path_info = url
-
- # Base middleware
- class ForwardRequestExceptionMiddleware(object):
- def __init__(self, app):
- self.app = app
-
- # Otherwise construct the appropriate middleware factory
- if hasattr(self, 'path_info'):
- p = self.path_info
-
- def factory_pi(app):
- class PathInfoForward(ForwardRequestExceptionMiddleware):
- def __call__(self, environ, start_response):
- environ['PATH_INFO'] = p
- return self.app(environ, start_response)
- return PathInfoForward(app)
-
- self.factory = factory_pi
- elif url:
- def factory_url(app):
- class URLForward(ForwardRequestExceptionMiddleware):
- def __call__(self, environ, start_response):
- environ['PATH_INFO'] = url.split('?')[0]
- environ['QUERY_STRING'] = url.split('?')[1]
- return self.app(environ, start_response)
- return URLForward(app)
-
- self.factory = factory_url
- elif environ:
- def factory_env(app):
- class EnvironForward(ForwardRequestExceptionMiddleware):
- def __call__(self, environ_, start_response):
- return self.app(environ, start_response)
- return EnvironForward(app)
-
- self.factory = factory_env
- else:
- self.factory = factory
diff --git a/pecan/middleware/static.py b/pecan/middleware/static.py
deleted file mode 100644
index ab6dd4e..0000000
--- a/pecan/middleware/static.py
+++ /dev/null
@@ -1,166 +0,0 @@
-"""
-This code is adapted from the Werkzeug project, under the BSD license.
-
-:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
-:license: BSD, see LICENSE for more details.
-"""
-
-import os
-import mimetypes
-from datetime import datetime
-from time import gmtime
-
-import six
-
-
-class FileWrapper(object):
- """This class can be used to convert a :class:`file`-like object into
- an iterable. It yields `buffer_size` blocks until the file is fully
- read.
-
- You should not use this class directly but rather use the
- :func:`wrap_file` function that uses the WSGI server's file wrapper
- support if it's available.
-
- :param file: a :class:`file`-like object with a :meth:`~file.read` method.
- :param buffer_size: number of bytes for one iteration.
- """
-
- def __init__(self, file, buffer_size=8192):
- self.file = file
- self.buffer_size = buffer_size
-
- def close(self):
- if hasattr(self.file, 'close'):
- self.file.close()
-
- def __iter__(self):
- return self
-
- def next(self):
- data = self.file.read(self.buffer_size)
- if data:
- return data
- raise StopIteration()
-
-
-if six.PY3:
- FileWrapper.__next__ = FileWrapper.next
-
-
-def wrap_file(environ, file, buffer_size=8192):
- """Wraps a file. This uses the WSGI server's file wrapper if available
- or otherwise the generic :class:`FileWrapper`.
-
- If the file wrapper from the WSGI server is used it's important to not
- iterate over it from inside the application but to pass it through
- unchanged.
-
- More information about file wrappers are available in :pep:`333`.
-
- :param file: a :class:`file`-like object with a :meth:`~file.read` method.
- :param buffer_size: number of bytes for one iteration.
- """
- return environ.get('wsgi.file_wrapper', FileWrapper)(file, buffer_size)
-
-
-def _dump_date(d, delim):
- """Used for `http_date` and `cookie_date`."""
- if d is None:
- d = gmtime()
- elif isinstance(d, datetime):
- d = d.utctimetuple()
- elif isinstance(d, (int, float)):
- d = gmtime(d)
- return '%s, %02d%s%s%s%s %02d:%02d:%02d GMT' % (
- ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')[d.tm_wday],
- d.tm_mday, delim,
- ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
- 'Oct', 'Nov', 'Dec')[d.tm_mon - 1],
- delim, str(d.tm_year), d.tm_hour, d.tm_min, d.tm_sec
- )
-
-
-def http_date(timestamp=None):
- """Formats the time to match the RFC1123 date format.
-
- Accepts a floating point number expressed in seconds since the epoch in, a
- datetime object or a timetuple. All times in UTC.
-
- Outputs a string in the format ``Wdy, DD Mon YYYY HH:MM:SS GMT``.
-
- :param timestamp: If provided that date is used, otherwise the current.
- """
- return _dump_date(timestamp, ' ')
-
-
-class StaticFileMiddleware(object):
- """A WSGI middleware that provides static content for development
- environments.
-
- Currently the middleware does not support non ASCII filenames. If the
- encoding on the file system happens to be the encoding of the URI it may
- work but this could also be by accident. We strongly suggest using ASCII
- only file names for static files.
-
- The middleware will guess the mimetype using the Python `mimetype`
- module. If it's unable to figure out the charset it will fall back
- to `fallback_mimetype`.
-
- :param app: the application to wrap. If you don't want to wrap an
- application you can pass it :exc:`NotFound`.
- :param directory: the directory to serve up.
- :param fallback_mimetype: the fallback mimetype for unknown files.
- """
-
- def __init__(self, app, directory, fallback_mimetype='text/plain'):
- self.app = app
- self.loader = self.get_directory_loader(directory)
- self.fallback_mimetype = fallback_mimetype
-
- def _opener(self, filename):
- return lambda: (
- open(filename, 'rb'),
- datetime.utcfromtimestamp(os.path.getmtime(filename)),
- int(os.path.getsize(filename))
- )
-
- def get_directory_loader(self, directory):
- def loader(path):
- path = path or directory
- if path is not None:
- path = os.path.join(directory, path)
- if os.path.isfile(path):
- return os.path.basename(path), self._opener(path)
- return None, None
- return loader
-
- def __call__(self, environ, start_response):
- # sanitize the path for non unix systems
- cleaned_path = environ.get('PATH_INFO', '').strip('/')
- for sep in os.sep, os.altsep:
- if sep and sep != '/':
- cleaned_path = cleaned_path.replace(sep, '/')
- path = '/'.join([''] + [x for x in cleaned_path.split('/')
- if x and x != '..'])
-
- # attempt to find a loader for the file
- real_filename, file_loader = self.loader(path[1:])
- if file_loader is None:
- return self.app(environ, start_response)
-
- # serve the file with the appropriate name if we found it
- guessed_type = mimetypes.guess_type(real_filename)
- mime_type = guessed_type[0] or self.fallback_mimetype
- f, mtime, file_size = file_loader()
-
- headers = [('Date', http_date())]
- headers.append(('Cache-Control', 'public'))
- headers.extend((
- ('Content-Type', mime_type),
- ('Content-Length', str(file_size)),
- ('Last-Modified', http_date(mtime))
- ))
-
- start_response('200 OK', headers)
- return wrap_file(environ, f)
diff --git a/pecan/rest.py b/pecan/rest.py
deleted file mode 100644
index e231c71..0000000
--- a/pecan/rest.py
+++ /dev/null
@@ -1,412 +0,0 @@
-from inspect import ismethod, getmembers
-import warnings
-
-from webob import exc
-import six
-
-from .core import abort
-from .decorators import expose
-from .routing import lookup_controller, handle_lookup_traversal
-from .util import iscontroller, getargspec
-
-
-class RestController(object):
- '''
- A base class for ``REST`` based controllers. Inherit from this class
- to implement a REST controller.
-
- ``RestController`` implements a set of routing functions which override
- the default pecan routing with behavior consistent with RESTful routing.
- This functionality covers navigation to the requested resource
- controllers, and the appropriate handling of both the common (``GET``,
- ``POST``, ``PUT``, ``DELETE``) as well as custom-defined REST action
- methods.
-
- For more on developing **RESTful** web applications with Pecan, see
- :ref:`rest`.
- '''
- _custom_actions = {}
-
- def __new__(cls, *args, **kwargs):
- """
- RestController does not support the `route` argument to
- :func:`~pecan.decorators.expose`
-
- Implement this with __new__ rather than a metaclass, because it's very
- common for pecan users to mixin RestController (with other bases that
- have their own metaclasses).
- """
- for name, value in getmembers(cls):
- if iscontroller(value) and getattr(value, 'custom_route', None):
- raise ValueError(
- 'Path segments cannot be used in combination with '
- 'pecan.rest.RestController. Remove the `route` argument '
- 'to @pecan.expose on %s.%s.%s' % (
- cls.__module__, cls.__name__, value.__name__
- )
- )
-
- # object.__new__ will error if called with extra arguments, and either
- # __new__ is overridden or __init__ is not overridden;
- # https://hg.python.org/cpython/file/78d36d54391c/Objects/typeobject.c#l3034
- # In PY3, this is actually a TypeError (in PY2, it just raises
- # a DeprecationWarning)
- new = super(RestController, cls).__new__
- if new is object.__new__:
- return new(cls)
- return new(cls, *args, **kwargs)
-
- def _get_args_for_controller(self, controller):
- """
- Retrieve the arguments we actually care about. For Pecan applications
- that utilize thread locals, we should truncate the first argument,
- `self`. For applications that explicitly pass request/response
- references as the first controller arguments, we should truncate the
- first three arguments, `self, req, resp`.
- """
- argspec = getargspec(controller)
- from pecan import request
- try:
- request.path
- except AttributeError:
- return argspec.args[3:]
- return argspec.args[1:]
-
- def _handle_bad_rest_arguments(self, controller, remainder, request):
- """
- Ensure that the argspec for a discovered controller actually matched
- the positional arguments in the request path. If not, raise
- a webob.exc.HTTPBadRequest.
- """
- argspec = self._get_args_for_controller(controller)
- fixed_args = len(argspec) - len(
- request.pecan.get('routing_args', [])
- )
- if len(remainder) < fixed_args:
- # For controllers that are missing intermediate IDs
- # (e.g., /authors/books vs /authors/1/books), return a 404 for an
- # invalid path.
- abort(404)
-
- def _lookup_child(self, remainder):
- """
- Lookup a child controller with a named path (handling Unicode paths
- properly for Python 2).
- """
- try:
- controller = getattr(self, remainder, None)
- except UnicodeEncodeError:
- return None
- return controller
-
- @expose()
- def _route(self, args, request=None):
- '''
- Routes a request to the appropriate controller and returns its result.
-
- Performs a bit of validation - refuses to route delete and put actions
- via a GET request).
- '''
- if request is None:
- from pecan import request
- # convention uses "_method" to handle browser-unsupported methods
- method = request.params.get('_method', request.method).lower()
-
- # make sure DELETE/PUT requests don't use GET
- if request.method == 'GET' and method in ('delete', 'put'):
- abort(405)
-
- # check for nested controllers
- result = self._find_sub_controllers(args, request)
- if result:
- return result
-
- # handle the request
- handler = getattr(
- self,
- '_handle_%s' % method,
- self._handle_unknown_method
- )
-
- try:
- if len(getargspec(handler).args) == 3:
- result = handler(method, args)
- else:
- result = handler(method, args, request)
-
- #
- # If the signature of the handler does not match the number
- # of remaining positional arguments, attempt to handle
- # a _lookup method (if it exists)
- #
- argspec = self._get_args_for_controller(result[0])
- num_args = len(argspec)
- if num_args < len(args):
- _lookup_result = self._handle_lookup(args, request)
- if _lookup_result:
- return _lookup_result
- except (exc.HTTPClientError, exc.HTTPNotFound,
- exc.HTTPMethodNotAllowed) as e:
- #
- # If the matching handler results in a 400, 404, or 405, attempt to
- # handle a _lookup method (if it exists)
- #
- _lookup_result = self._handle_lookup(args, request)
- if _lookup_result:
- return _lookup_result
-
- # Build a correct Allow: header
- if isinstance(e, exc.HTTPMethodNotAllowed):
-
- def method_iter():
- for func in ('get', 'get_one', 'get_all', 'new', 'edit',
- 'get_delete'):
- if self._find_controller(func):
- yield 'GET'
- break
- for method in ('HEAD', 'POST', 'PUT', 'DELETE', 'TRACE',
- 'PATCH'):
- func = method.lower()
- if self._find_controller(func):
- yield method
-
- e.allow = sorted(method_iter())
-
- raise
-
- # return the result
- return result
-
- def _handle_lookup(self, args, request=None):
- if request is None:
- self._raise_method_deprecation_warning(self.handle_lookup)
-
- # filter empty strings from the arg list
- args = list(six.moves.filter(bool, args))
-
- # check for lookup controllers
- lookup = getattr(self, '_lookup', None)
- if args and iscontroller(lookup):
- result = handle_lookup_traversal(lookup, args)
- if result:
- obj, remainder = result
- return lookup_controller(obj, remainder, request)
-
- def _find_controller(self, *args):
- '''
- Returns the appropriate controller for routing a custom action.
- '''
- for name in args:
- obj = self._lookup_child(name)
- if obj and iscontroller(obj):
- return obj
- return None
-
- def _find_sub_controllers(self, remainder, request):
- '''
- Identifies the correct controller to route to by analyzing the
- request URI.
- '''
- # need either a get_one or get to parse args
- method = None
- for name in ('get_one', 'get'):
- if hasattr(self, name):
- method = name
- break
- if not method:
- return
-
- # get the args to figure out how much to chop off
- args = self._get_args_for_controller(getattr(self, method))
- fixed_args = len(args) - len(
- request.pecan.get('routing_args', [])
- )
- var_args = getargspec(getattr(self, method)).varargs
-
- # attempt to locate a sub-controller
- if var_args:
- for i, item in enumerate(remainder):
- controller = self._lookup_child(item)
- if controller and not ismethod(controller):
- self._set_routing_args(request, remainder[:i])
- return lookup_controller(controller, remainder[i + 1:],
- request)
- elif fixed_args < len(remainder) and hasattr(
- self, remainder[fixed_args]
- ):
- controller = self._lookup_child(remainder[fixed_args])
- if not ismethod(controller):
- self._set_routing_args(request, remainder[:fixed_args])
- return lookup_controller(
- controller,
- remainder[fixed_args + 1:],
- request
- )
-
- def _handle_unknown_method(self, method, remainder, request=None):
- '''
- Routes undefined actions (like RESET) to the appropriate controller.
- '''
- if request is None:
- self._raise_method_deprecation_warning(self._handle_unknown_method)
-
- # try finding a post_{custom} or {custom} method first
- controller = self._find_controller('post_%s' % method, method)
- if controller:
- return controller, remainder
-
- # if no controller exists, try routing to a sub-controller; note that
- # since this isn't a safe GET verb, any local exposes are 405'd
- if remainder:
- if self._find_controller(remainder[0]):
- abort(405)
- sub_controller = self._lookup_child(remainder[0])
- if sub_controller:
- return lookup_controller(sub_controller, remainder[1:],
- request)
-
- abort(405)
-
- def _handle_get(self, method, remainder, request=None):
- '''
- Routes ``GET`` actions to the appropriate controller.
- '''
- if request is None:
- self._raise_method_deprecation_warning(self._handle_get)
-
- # route to a get_all or get if no additional parts are available
- if not remainder or remainder == ['']:
- remainder = list(six.moves.filter(bool, remainder))
- controller = self._find_controller('get_all', 'get')
- if controller:
- self._handle_bad_rest_arguments(controller, remainder, request)
- return controller, []
- abort(405)
-
- method_name = remainder[-1]
- # check for new/edit/delete GET requests
- if method_name in ('new', 'edit', 'delete'):
- if method_name == 'delete':
- method_name = 'get_delete'
- controller = self._find_controller(method_name)
- if controller:
- return controller, remainder[:-1]
-
- match = self._handle_custom_action(method, remainder, request)
- if match:
- return match
-
- controller = self._lookup_child(remainder[0])
- if controller and not ismethod(controller):
- return lookup_controller(controller, remainder[1:], request)
-
- # finally, check for the regular get_one/get requests
- controller = self._find_controller('get_one', 'get')
- if controller:
- self._handle_bad_rest_arguments(controller, remainder, request)
- return controller, remainder
-
- abort(405)
-
- def _handle_delete(self, method, remainder, request=None):
- '''
- Routes ``DELETE`` actions to the appropriate controller.
- '''
- if request is None:
- self._raise_method_deprecation_warning(self._handle_delete)
-
- if remainder:
- match = self._handle_custom_action(method, remainder, request)
- if match:
- return match
-
- controller = self._lookup_child(remainder[0])
- if controller and not ismethod(controller):
- return lookup_controller(controller, remainder[1:], request)
-
- # check for post_delete/delete requests first
- controller = self._find_controller('post_delete', 'delete')
- if controller:
- return controller, remainder
-
- # if no controller exists, try routing to a sub-controller; note that
- # since this is a DELETE verb, any local exposes are 405'd
- if remainder:
- if self._find_controller(remainder[0]):
- abort(405)
- sub_controller = self._lookup_child(remainder[0])
- if sub_controller:
- return lookup_controller(sub_controller, remainder[1:],
- request)
-
- abort(405)
-
- def _handle_post(self, method, remainder, request=None):
- '''
- Routes ``POST`` requests.
- '''
- if request is None:
- self._raise_method_deprecation_warning(self._handle_post)
-
- # check for custom POST/PUT requests
- if remainder:
- match = self._handle_custom_action(method, remainder, request)
- if match:
- return match
-
- controller = self._lookup_child(remainder[0])
- if controller and not ismethod(controller):
- return lookup_controller(controller, remainder[1:], request)
-
- # check for regular POST/PUT requests
- controller = self._find_controller(method)
- if controller:
- return controller, remainder
-
- abort(405)
-
- def _handle_put(self, method, remainder, request=None):
- return self._handle_post(method, remainder, request)
-
- def _handle_custom_action(self, method, remainder, request=None):
- if request is None:
- self._raise_method_deprecation_warning(self._handle_custom_action)
-
- remainder = [r for r in remainder if r]
- if remainder:
- if method in ('put', 'delete'):
- # For PUT and DELETE, additional arguments are supplied, e.g.,
- # DELETE /foo/XYZ
- method_name = remainder[0]
- remainder = remainder[1:]
- else:
- method_name = remainder[-1]
- remainder = remainder[:-1]
- if method.upper() in self._custom_actions.get(method_name, []):
- controller = self._find_controller(
- '%s_%s' % (method, method_name),
- method_name
- )
- if controller:
- return controller, remainder
-
- def _set_routing_args(self, request, args):
- '''
- Sets default routing arguments.
- '''
- request.pecan.setdefault('routing_args', []).extend(args)
-
- def _raise_method_deprecation_warning(self, handler):
- warnings.warn(
- (
- "The function signature for %s.%s.%s is changing "
- "in the next version of pecan.\nPlease update to: "
- "`%s(self, method, remainder, request)`." % (
- self.__class__.__module__,
- self.__class__.__name__,
- handler.__name__,
- handler.__name__
- )
- ),
- DeprecationWarning
- )
diff --git a/pecan/routing.py b/pecan/routing.py
deleted file mode 100644
index aed8ad5..0000000
--- a/pecan/routing.py
+++ /dev/null
@@ -1,325 +0,0 @@
-import re
-import warnings
-from inspect import getmembers, ismethod
-
-from webob import exc
-import six
-
-from .secure import handle_security, cross_boundary
-from .util import iscontroller, getargspec, _cfg
-
-__all__ = ['lookup_controller', 'find_object', 'route']
-__observed_controllers__ = set()
-__custom_routes__ = {}
-
-
-def route(*args):
- """
- This function is used to define an explicit route for a path segment.
-
- You generally only want to use this in situations where your desired path
- segment is not a valid Python variable/function name.
-
- For example, if you wanted to be able to route to:
-
- /path/with-dashes/
-
- ...the following is invalid Python syntax::
-
- class Controller(object):
-
- with-dashes = SubController()
-
- ...so you would instead define the route explicitly::
-
- class Controller(object):
- pass
-
- pecan.route(Controller, 'with-dashes', SubController())
- """
-
- def _validate_route(route):
- if not isinstance(route, six.string_types):
- raise TypeError('%s must be a string' % route)
-
- if route in ('.', '..') or not re.match(
- '^[0-9a-zA-Z-_$\(\)\.~!,;:*+@=]+$', route
- ):
- raise ValueError(
- '%s must be a valid path segment. Keep in mind '
- 'that path segments should not contain path separators '
- '(e.g., /) ' % route
- )
-
- if len(args) == 2:
- # The handler in this situation is a @pecan.expose'd callable,
- # and is generally only used by the @expose() decorator itself.
- #
- # This sets a special attribute, `custom_route` on the callable, which
- # pecan's routing logic knows how to make use of (as a special case)
- route, handler = args
- if ismethod(handler):
- handler = handler.__func__
- if not iscontroller(handler):
- raise TypeError(
- '%s must be a callable decorated with @pecan.expose' % handler
- )
- obj, attr, value = handler, 'custom_route', route
-
- if handler.__name__ in ('_lookup', '_default', '_route'):
- raise ValueError(
- '%s is a special method in pecan and cannot be used in '
- 'combination with custom path segments.' % handler.__name__
- )
- elif len(args) == 3:
- # This is really just a setattr on the parent controller (with some
- # additional validation for the path segment itself)
- _, route, handler = args
- obj, attr, value = args
-
- if hasattr(obj, attr):
- raise RuntimeError(
- (
- "%(module)s.%(class)s already has an "
- "existing attribute named \"%(route)s\"." % {
- 'module': obj.__module__,
- 'class': obj.__name__,
- 'route': attr
- }
- ),
- )
- else:
- raise TypeError(
- 'pecan.route should be called in the format '
- 'route(ParentController, "path-segment", SubController())'
- )
-
- _validate_route(route)
- setattr(obj, attr, value)
-
-
-class PecanNotFound(Exception):
- pass
-
-
-class NonCanonicalPath(Exception):
- '''
- Exception Raised when a non-canonical path is encountered when 'walking'
- the URI. This is typically a ``POST`` request which requires a trailing
- slash.
- '''
- def __init__(self, controller, remainder):
- self.controller = controller
- self.remainder = remainder
-
-
-def lookup_controller(obj, remainder, request=None):
- '''
- Traverses the requested url path and returns the appropriate controller
- object, including default routes.
-
- Handles common errors gracefully.
- '''
- if request is None:
- warnings.warn(
- (
- "The function signature for %s.lookup_controller is changing "
- "in the next version of pecan.\nPlease update to: "
- "`lookup_controller(self, obj, remainder, request)`." % (
- __name__,
- )
- ),
- DeprecationWarning
- )
-
- notfound_handlers = []
- while True:
- try:
- obj, remainder = find_object(obj, remainder, notfound_handlers,
- request)
- handle_security(obj)
- return obj, remainder
- except (exc.HTTPNotFound, exc.HTTPMethodNotAllowed,
- PecanNotFound) as e:
- if isinstance(e, PecanNotFound):
- e = exc.HTTPNotFound()
- while notfound_handlers:
- name, obj, remainder = notfound_handlers.pop()
- if name == '_default':
- # Notfound handler is, in fact, a controller, so stop
- # traversal
- return obj, remainder
- else:
- # Notfound handler is an internal redirect, so continue
- # traversal
- result = handle_lookup_traversal(obj, remainder)
- if result:
- # If no arguments are passed to the _lookup, yet the
- # argspec requires at least one, raise a 404
- if (
- remainder == [''] and
- len(obj._pecan['argspec'].args) > 1
- ):
- raise e
- obj_, remainder_ = result
- return lookup_controller(obj_, remainder_, request)
- else:
- raise e
-
-
-def handle_lookup_traversal(obj, args):
- try:
- result = obj(*args)
- if result:
- prev_obj = obj
- obj, remainder = result
- # crossing controller boundary
- cross_boundary(prev_obj, obj)
- return result
- except TypeError as te:
- msg = 'Got exception calling lookup(): %s (%s)'
- warnings.warn(
- msg % (te, te.args),
- RuntimeWarning
- )
-
-
-def find_object(obj, remainder, notfound_handlers, request):
- '''
- 'Walks' the url path in search of an action for which a controller is
- implemented and returns that controller object along with what's left
- of the remainder.
- '''
- prev_obj = None
- while True:
- if obj is None:
- raise PecanNotFound
- if iscontroller(obj):
- if getattr(obj, 'custom_route', None) is None:
- return obj, remainder
-
- _detect_custom_path_segments(obj)
-
- if remainder:
- custom_route = __custom_routes__.get((obj.__class__, remainder[0]))
- if custom_route:
- return getattr(obj, custom_route), remainder[1:]
-
- # are we traversing to another controller
- cross_boundary(prev_obj, obj)
- try:
- next_obj, rest = remainder[0], remainder[1:]
- if next_obj == '':
- index = getattr(obj, 'index', None)
- if iscontroller(index):
- return index, rest
- except IndexError:
- # the URL has hit an index method without a trailing slash
- index = getattr(obj, 'index', None)
- if iscontroller(index):
- raise NonCanonicalPath(index, [])
-
- default = getattr(obj, '_default', None)
- if iscontroller(default):
- notfound_handlers.append(('_default', default, remainder))
-
- lookup = getattr(obj, '_lookup', None)
- if iscontroller(lookup):
- notfound_handlers.append(('_lookup', lookup, remainder))
-
- route = getattr(obj, '_route', None)
- if iscontroller(route):
- if len(getargspec(route).args) == 2:
- warnings.warn(
- (
- "The function signature for %s.%s._route is changing "
- "in the next version of pecan.\nPlease update to: "
- "`def _route(self, args, request)`." % (
- obj.__class__.__module__,
- obj.__class__.__name__
- )
- ),
- DeprecationWarning
- )
- next_obj, next_remainder = route(remainder)
- else:
- next_obj, next_remainder = route(remainder, request)
- cross_boundary(route, next_obj)
- return next_obj, next_remainder
-
- if not remainder:
- raise PecanNotFound
-
- prev_remainder = remainder
- prev_obj = obj
- remainder = rest
- try:
- obj = getattr(obj, next_obj, None)
- except UnicodeEncodeError:
- obj = None
-
- # Last-ditch effort: if there's not a matching subcontroller, no
- # `_default`, no `_lookup`, and no `_route`, look to see if there's
- # an `index` that has a generic method defined for the current request
- # method.
- if not obj and not notfound_handlers and hasattr(prev_obj, 'index'):
- if request.method in _cfg(prev_obj.index).get('generic_handlers',
- {}):
- return prev_obj.index, prev_remainder
-
-
-def _detect_custom_path_segments(obj):
- # Detect custom controller routes (on the initial traversal)
- if obj.__class__.__module__ == '__builtin__':
- return
-
- attrs = set(dir(obj))
-
- if obj.__class__ not in __observed_controllers__:
- for key, val in getmembers(obj):
- if iscontroller(val) and isinstance(
- getattr(val, 'custom_route', None),
- six.string_types
- ):
- route = val.custom_route
-
- # Detect class attribute name conflicts
- for conflict in attrs.intersection(set((route,))):
- raise RuntimeError(
- (
- "%(module)s.%(class)s.%(function)s has "
- "a custom path segment, \"%(route)s\", "
- "but %(module)s.%(class)s already has an "
- "existing attribute named \"%(route)s\"." % {
- 'module': obj.__class__.__module__,
- 'class': obj.__class__.__name__,
- 'function': val.__name__,
- 'route': conflict
- }
- ),
- )
-
- existing = __custom_routes__.get(
- (obj.__class__, route)
- )
- if existing:
- # Detect custom path conflicts between functions
- raise RuntimeError(
- (
- "%(module)s.%(class)s.%(function)s and "
- "%(module)s.%(class)s.%(other)s have a "
- "conflicting custom path segment, "
- "\"%(route)s\"." % {
- 'module': obj.__class__.__module__,
- 'class': obj.__class__.__name__,
- 'function': val.__name__,
- 'other': existing,
- 'route': route
- }
- ),
- )
-
- __custom_routes__[
- (obj.__class__, route)
- ] = key
- __observed_controllers__.add(obj.__class__)
diff --git a/pecan/scaffolds/__init__.py b/pecan/scaffolds/__init__.py
deleted file mode 100644
index 2dbe46f..0000000
--- a/pecan/scaffolds/__init__.py
+++ /dev/null
@@ -1,142 +0,0 @@
-import sys
-import os
-import re
-import pkg_resources
-from string import Template
-
-import six
-
-DEFAULT_SCAFFOLD = 'base'
-_bad_chars_re = re.compile('[^a-zA-Z0-9_]')
-
-
-class PecanScaffold(object):
- """
- A base Pecan scaffold. New scaffolded implementations should extend this
- class and define a ``_scaffold_dir`` attribute, e.g.,
-
- class CoolAddOnScaffold(PecanScaffold):
-
- _scaffold_dir = ('package', os.path.join('scaffolds', 'scaffold_name'))
-
- ...where...
-
- pkg_resources.resource_listdir(_scaffold_dir[0], _scaffold_dir[1]))
-
- ...points to some scaffold directory root.
- """
-
- def normalize_output_dir(self, dest):
- return os.path.abspath(os.path.normpath(dest))
-
- def normalize_pkg_name(self, dest):
- return _bad_chars_re.sub('', dest.lower())
-
- def copy_to(self, dest, **kw):
- output_dir = self.normalize_output_dir(dest)
- pkg_name = self.normalize_pkg_name(dest)
- copy_dir(self._scaffold_dir, output_dir, {'package': pkg_name}, **kw)
-
-
-class BaseScaffold(PecanScaffold):
- _scaffold_dir = ('pecan', os.path.join('scaffolds', 'base'))
-
-
-class RestAPIScaffold(PecanScaffold):
- _scaffold_dir = ('pecan', os.path.join('scaffolds', 'rest-api'))
-
-
-def copy_dir(source, dest, variables, out_=sys.stdout, i=0):
- """
- Copies the ``source`` directory to the ``dest`` directory, where
- ``source`` is some tuple representing an installed package and a
- subdirectory in the package, e.g.,
-
- ('pecan', os.path.join('scaffolds', 'base'))
- ('pecan_extension', os.path.join('scaffolds', 'scaffold_name'))
-
- ``variables``: A dictionary of variables to use in any substitutions.
- Substitution is performed via ``string.Template``.
-
- ``out_``: File object to write to (default is sys.stdout).
- """
- def out(msg):
- out_.write('%s%s' % (' ' * (i * 2), msg))
- out_.write('\n')
- out_.flush()
-
- names = sorted(pkg_resources.resource_listdir(source[0], source[1]))
- if not os.path.exists(dest):
- out('Creating %s' % dest)
- makedirs(dest)
- else:
- out('%s already exists' % dest)
- return
-
- for name in names:
-
- full = '/'.join([source[1], name])
- dest_full = os.path.join(dest, substitute_filename(name, variables))
-
- sub_file = False
- if dest_full.endswith('_tmpl'):
- dest_full = dest_full[:-5]
- sub_file = True
-
- if pkg_resources.resource_isdir(source[0], full):
- out('Recursing into %s' % os.path.basename(full))
- copy_dir((source[0], full), dest_full, variables, out_, i + 1)
- continue
- else:
- content = pkg_resources.resource_string(source[0], full)
-
- if sub_file:
- content = render_template(content, variables)
- if content is None:
- continue # pragma: no cover
-
- out('Copying %s to %s' % (full, dest_full))
-
- f = open(dest_full, 'wb')
- f.write(content)
- f.close()
-
-
-def makedirs(directory):
- """ Resursively create a named directory. """
- parent = os.path.dirname(os.path.abspath(directory))
- if not os.path.exists(parent):
- makedirs(parent)
- os.mkdir(directory)
-
-
-def substitute_filename(fn, variables):
- """ Substitute +variables+ in file directory names. """
- for var, value in variables.items():
- fn = fn.replace('+%s+' % var, str(value))
- return fn
-
-
-def render_template(content, variables):
- """
- Return a bytestring representing a templated file based on the
- input (content) and the variable names defined (vars).
- """
- fsenc = sys.getfilesystemencoding()
-
- def to_native(s, encoding='latin-1', errors='strict'):
- if six.PY3:
- if isinstance(s, six.text_type):
- return s
- return str(s, encoding, errors)
- else:
- if isinstance(s, six.text_type):
- return s.encode(encoding, errors)
- return str(s)
-
- output = Template(
- to_native(content, fsenc)
- ).substitute(variables)
- if isinstance(output, six.text_type):
- output = output.encode(fsenc, 'strict')
- return output
diff --git a/pecan/scaffolds/base/+package+/__init__.py b/pecan/scaffolds/base/+package+/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/scaffolds/base/+package+/__init__.py
+++ /dev/null
diff --git a/pecan/scaffolds/base/+package+/app.py_tmpl b/pecan/scaffolds/base/+package+/app.py_tmpl
deleted file mode 100644
index bf904b6..0000000
--- a/pecan/scaffolds/base/+package+/app.py_tmpl
+++ /dev/null
@@ -1,14 +0,0 @@
-from pecan import make_app
-from ${package} import model
-
-
-def setup_app(config):
-
- model.init_model()
- app_conf = dict(config.app)
-
- return make_app(
- app_conf.pop('root'),
- logging=getattr(config, 'logging', {}),
- **app_conf
- )
diff --git a/pecan/scaffolds/base/+package+/controllers/__init__.py b/pecan/scaffolds/base/+package+/controllers/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/scaffolds/base/+package+/controllers/__init__.py
+++ /dev/null
diff --git a/pecan/scaffolds/base/+package+/controllers/root.py b/pecan/scaffolds/base/+package+/controllers/root.py
deleted file mode 100644
index bc1e72b..0000000
--- a/pecan/scaffolds/base/+package+/controllers/root.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from pecan import expose, redirect
-from webob.exc import status_map
-
-
-class RootController(object):
-
- @expose(generic=True, template='index.html')
- def index(self):
- return dict()
-
- @index.when(method='POST')
- def index_post(self, q):
- redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % q)
-
- @expose('error.html')
- def error(self, status):
- try:
- status = int(status)
- except ValueError: # pragma: no cover
- status = 500
- message = getattr(status_map.get(status), 'explanation', '')
- return dict(status=status, message=message)
diff --git a/pecan/scaffolds/base/+package+/model/__init__.py b/pecan/scaffolds/base/+package+/model/__init__.py
deleted file mode 100644
index d983f7b..0000000
--- a/pecan/scaffolds/base/+package+/model/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from pecan import conf # noqa
-
-
-def init_model():
- """
- This is a stub method which is called at application startup time.
-
- If you need to bind to a parsed database configuration, set up tables or
- ORM classes, or perform any database initialization, this is the
- recommended place to do it.
-
- For more information working with databases, and some common recipes,
- see http://pecan.readthedocs.org/en/latest/databases.html
- """
- pass
diff --git a/pecan/scaffolds/base/+package+/templates/error.html b/pecan/scaffolds/base/+package+/templates/error.html
deleted file mode 100644
index f2d9796..0000000
--- a/pecan/scaffolds/base/+package+/templates/error.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<%inherit file="layout.html" />
-
-## provide definitions for blocks we want to redefine
-<%def name="title()">
- Server Error ${status}
-</%def>
-
-## now define the body of the template
- <header>
- <h1>Server Error ${status}</h1>
- </header>
- <p>${message}</p>
diff --git a/pecan/scaffolds/base/+package+/templates/index.html b/pecan/scaffolds/base/+package+/templates/index.html
deleted file mode 100644
index f17c386..0000000
--- a/pecan/scaffolds/base/+package+/templates/index.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<%inherit file="layout.html" />
-
-## provide definitions for blocks we want to redefine
-<%def name="title()">
- Welcome to Pecan!
-</%def>
-
-## now define the body of the template
- <header>
- <h1><img src="/images/logo.png" /></h1>
- </header>
-
- <div id="content">
-
- <p>This is a sample Pecan project.</p>
-
- <p>
- Instructions for getting started can be found online at <a
- href="http://pecanpy.org" target="window">pecanpy.org</a>
- </p>
-
- <p>
- ...or you can search the documentation here:
- </p>
-
- <form method="POST" action="/">
- <fieldset>
- <input name="q" />
- <input type="submit" value="Search" />
- </fieldset>
- <small>Enter search terms or a module, class or function name.</small>
- </form>
-
- </div>
diff --git a/pecan/scaffolds/base/+package+/templates/layout.html b/pecan/scaffolds/base/+package+/templates/layout.html
deleted file mode 100644
index 4090859..0000000
--- a/pecan/scaffolds/base/+package+/templates/layout.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<html>
- <head>
- <title>${self.title()}</title>
- ${self.style()}
- ${self.javascript()}
- </head>
- <body>
- ${self.body()}
- </body>
-</html>
-
-<%def name="title()">
- Default Title
-</%def>
-
-<%def name="style()">
- <link rel="stylesheet" type="text/css" media="screen" href="/css/style.css" />
-</%def>
-
-<%def name="javascript()">
- <script language="text/javascript" src="/javascript/shared.js"></script>
-</%def>
diff --git a/pecan/scaffolds/base/+package+/tests/__init__.py_tmpl b/pecan/scaffolds/base/+package+/tests/__init__.py_tmpl
deleted file mode 100644
index 78ea527..0000000
--- a/pecan/scaffolds/base/+package+/tests/__init__.py_tmpl
+++ /dev/null
@@ -1,22 +0,0 @@
-import os
-from unittest import TestCase
-from pecan import set_config
-from pecan.testing import load_test_app
-
-__all__ = ['FunctionalTest']
-
-
-class FunctionalTest(TestCase):
- """
- Used for functional tests where you need to test your
- literal application and its integration with the framework.
- """
-
- def setUp(self):
- self.app = load_test_app(os.path.join(
- os.path.dirname(__file__),
- 'config.py'
- ))
-
- def tearDown(self):
- set_config({}, overwrite=True)
diff --git a/pecan/scaffolds/base/+package+/tests/config.py_tmpl b/pecan/scaffolds/base/+package+/tests/config.py_tmpl
deleted file mode 100644
index b745a8c..0000000
--- a/pecan/scaffolds/base/+package+/tests/config.py_tmpl
+++ /dev/null
@@ -1,25 +0,0 @@
-# Server Specific Configurations
-server = {
- 'port': '8080',
- 'host': '0.0.0.0'
-}
-
-# Pecan Application Configurations
-app = {
- 'root': '${package}.controllers.root.RootController',
- 'modules': ['${package}'],
- 'static_root': '%(confdir)s/../../public',
- 'template_path': '%(confdir)s/../templates',
- 'debug': True,
- 'errors': {
- '404': '/error/404',
- '__force_dict__': True
- }
-}
-
-# Custom Configurations must be in Python dictionary format::
-#
-# foo = {'bar':'baz'}
-#
-# All configurations are accessible at::
-# pecan.conf
diff --git a/pecan/scaffolds/base/+package+/tests/test_functional.py_tmpl b/pecan/scaffolds/base/+package+/tests/test_functional.py_tmpl
deleted file mode 100644
index 2d7c6f0..0000000
--- a/pecan/scaffolds/base/+package+/tests/test_functional.py_tmpl
+++ /dev/null
@@ -1,22 +0,0 @@
-from unittest import TestCase
-from webtest import TestApp
-from ${package}.tests import FunctionalTest
-
-
-class TestRootController(FunctionalTest):
-
- def test_get(self):
- response = self.app.get('/')
- assert response.status_int == 200
-
- def test_search(self):
- response = self.app.post('/', params={'q': 'RestController'})
- assert response.status_int == 302
- assert response.headers['Location'] == (
- 'http://pecan.readthedocs.org/en/latest/search.html'
- '?q=RestController'
- )
-
- def test_get_not_found(self):
- response = self.app.get('/a/bogus/url', expect_errors=True)
- assert response.status_int == 404
diff --git a/pecan/scaffolds/base/+package+/tests/test_units.py b/pecan/scaffolds/base/+package+/tests/test_units.py
deleted file mode 100644
index 573fb68..0000000
--- a/pecan/scaffolds/base/+package+/tests/test_units.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from unittest import TestCase
-
-
-class TestUnits(TestCase):
-
- def test_units(self):
- assert 5 * 5 == 25
diff --git a/pecan/scaffolds/base/MANIFEST.in b/pecan/scaffolds/base/MANIFEST.in
deleted file mode 100644
index c922f11..0000000
--- a/pecan/scaffolds/base/MANIFEST.in
+++ /dev/null
@@ -1 +0,0 @@
-recursive-include public *
diff --git a/pecan/scaffolds/base/config.py_tmpl b/pecan/scaffolds/base/config.py_tmpl
deleted file mode 100644
index 1578f19..0000000
--- a/pecan/scaffolds/base/config.py_tmpl
+++ /dev/null
@@ -1,54 +0,0 @@
-# Server Specific Configurations
-server = {
- 'port': '8080',
- 'host': '0.0.0.0'
-}
-
-# Pecan Application Configurations
-app = {
- 'root': '${package}.controllers.root.RootController',
- 'modules': ['${package}'],
- 'static_root': '%(confdir)s/public',
- 'template_path': '%(confdir)s/${package}/templates',
- 'debug': True,
- 'errors': {
- 404: '/error/404',
- '__force_dict__': True
- }
-}
-
-logging = {
- 'root': {'level': 'INFO', 'handlers': ['console']},
- 'loggers': {
- '${package}': {'level': 'DEBUG', 'handlers': ['console']},
- 'pecan': {'level': 'DEBUG', 'handlers': ['console']},
- 'py.warnings': {'handlers': ['console']},
- '__force_dict__': True
- },
- 'handlers': {
- 'console': {
- 'level': 'DEBUG',
- 'class': 'logging.StreamHandler',
- 'formatter': 'color'
- }
- },
- 'formatters': {
- 'simple': {
- 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]'
- '[%(threadName)s] %(message)s')
- },
- 'color': {
- '()': 'pecan.log.ColorFormatter',
- 'format': ('%(asctime)s [%(padded_color_levelname)s] [%(name)s]'
- '[%(threadName)s] %(message)s'),
- '__force_dict__': True
- }
- }
-}
-
-# Custom Configurations must be in Python dictionary format::
-#
-# foo = {'bar':'baz'}
-#
-# All configurations are accessible at::
-# pecan.conf
diff --git a/pecan/scaffolds/base/public/css/style.css b/pecan/scaffolds/base/public/css/style.css
deleted file mode 100644
index 55c9db5..0000000
--- a/pecan/scaffolds/base/public/css/style.css
+++ /dev/null
@@ -1,43 +0,0 @@
-body {
- background: #311F00;
- color: white;
- font-family: 'Helvetica Neue', 'Helvetica', 'Verdana', sans-serif;
- padding: 1em 2em;
-}
-
-a {
- color: #FAFF78;
- text-decoration: none;
-}
-
-a:hover {
- text-decoration: underline;
-}
-
-div#content {
- width: 800px;
- margin: 0 auto;
-}
-
-form {
- margin: 0;
- padding: 0;
- border: 0;
-}
-
-fieldset {
- border: 0;
-}
-
-input.error {
- background: #FAFF78;
-}
-
-header {
- text-align: center;
-}
-
-h1, h2, h3, h4, h5, h6 {
- font-family: 'Futura-CondensedExtraBold', 'Futura', 'Helvetica', sans-serif;
- text-transform: uppercase;
-}
diff --git a/pecan/scaffolds/base/public/images/logo.png b/pecan/scaffolds/base/public/images/logo.png
deleted file mode 100644
index a8f403e..0000000
--- a/pecan/scaffolds/base/public/images/logo.png
+++ /dev/null
Binary files differ
diff --git a/pecan/scaffolds/base/setup.cfg_tmpl b/pecan/scaffolds/base/setup.cfg_tmpl
deleted file mode 100644
index 111f7cc..0000000
--- a/pecan/scaffolds/base/setup.cfg_tmpl
+++ /dev/null
@@ -1,6 +0,0 @@
-[nosetests]
-match=^test
-where=${package}
-nocapture=1
-cover-package=${package}
-cover-erase=1
diff --git a/pecan/scaffolds/base/setup.py_tmpl b/pecan/scaffolds/base/setup.py_tmpl
deleted file mode 100644
index ec47896..0000000
--- a/pecan/scaffolds/base/setup.py_tmpl
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-try:
- from setuptools import setup, find_packages
-except ImportError:
- from ez_setup import use_setuptools
- use_setuptools()
- from setuptools import setup, find_packages
-
-setup(
- name='${package}',
- version='0.1',
- description='',
- author='',
- author_email='',
- install_requires=[
- "pecan",
- ],
- test_suite='${package}',
- zip_safe=False,
- include_package_data=True,
- packages=find_packages(exclude=['ez_setup'])
-)
diff --git a/pecan/scaffolds/rest-api/+package+/__init__.py b/pecan/scaffolds/rest-api/+package+/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/scaffolds/rest-api/+package+/__init__.py
+++ /dev/null
diff --git a/pecan/scaffolds/rest-api/+package+/app.py_tmpl b/pecan/scaffolds/rest-api/+package+/app.py_tmpl
deleted file mode 100644
index 3eb5edf..0000000
--- a/pecan/scaffolds/rest-api/+package+/app.py_tmpl
+++ /dev/null
@@ -1,16 +0,0 @@
-from pecan import make_app
-from ${package} import model
-from ${package}.errors import JSONErrorHook
-
-
-def setup_app(config):
-
- model.init_model()
- app_conf = dict(config.app)
-
- return make_app(
- app_conf.pop('root'),
- logging=getattr(config, 'logging', {}),
- hooks=[JSONErrorHook()],
- **app_conf
- )
diff --git a/pecan/scaffolds/rest-api/+package+/controllers/__init__.py b/pecan/scaffolds/rest-api/+package+/controllers/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/scaffolds/rest-api/+package+/controllers/__init__.py
+++ /dev/null
diff --git a/pecan/scaffolds/rest-api/+package+/controllers/root.py b/pecan/scaffolds/rest-api/+package+/controllers/root.py
deleted file mode 100644
index f106bb2..0000000
--- a/pecan/scaffolds/rest-api/+package+/controllers/root.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from pecan import expose, response, abort
-
-people = {
- 1: 'Luke',
- 2: 'Leia',
- 3: 'Han',
- 4: 'Anakin'
-}
-
-
-class PersonController(object):
-
- def __init__(self, person_id):
- self.person_id = person_id
-
- @expose(generic=True)
- def index(self):
- return people.get(self.person_id) or abort(404)
-
- @index.when(method='PUT')
- def put(self):
- # TODO: Idempotent PUT (returns 200 or 204)
- response.status = 204
-
- @index.when(method='DELETE')
- def delete(self):
- # TODO: Idempotent DELETE
- response.status = 204
-
-
-class PeopleController(object):
-
- @expose()
- def _lookup(self, person_id, *remainder):
- return PersonController(int(person_id)), remainder
-
- @expose(generic=True, template='json')
- def index(self):
- return people
-
- @index.when(method='POST', template='json')
- def post(self):
- # TODO: Create a new person
- response.status = 201
-
-
-class RootController(object):
-
- people = PeopleController()
-
- @expose()
- def index(self):
- return "Hello, World!"
diff --git a/pecan/scaffolds/rest-api/+package+/errors.py b/pecan/scaffolds/rest-api/+package+/errors.py
deleted file mode 100644
index 4d4d06c..0000000
--- a/pecan/scaffolds/rest-api/+package+/errors.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import json
-import webob
-from pecan.hooks import PecanHook
-
-
-class JSONErrorHook(PecanHook):
- """
- A pecan hook that translates webob HTTP errors into a JSON format.
- """
-
- def on_error(self, state, exc):
- if isinstance(exc, webob.exc.HTTPError):
- return webob.Response(
- body=json.dumps({'reason': str(exc)}),
- status=exc.status,
- headerlist=exc.headerlist,
- content_type='application/json'
- )
diff --git a/pecan/scaffolds/rest-api/+package+/model/__init__.py b/pecan/scaffolds/rest-api/+package+/model/__init__.py
deleted file mode 100644
index d983f7b..0000000
--- a/pecan/scaffolds/rest-api/+package+/model/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from pecan import conf # noqa
-
-
-def init_model():
- """
- This is a stub method which is called at application startup time.
-
- If you need to bind to a parsed database configuration, set up tables or
- ORM classes, or perform any database initialization, this is the
- recommended place to do it.
-
- For more information working with databases, and some common recipes,
- see http://pecan.readthedocs.org/en/latest/databases.html
- """
- pass
diff --git a/pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl b/pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl
deleted file mode 100644
index 78ea527..0000000
--- a/pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl
+++ /dev/null
@@ -1,22 +0,0 @@
-import os
-from unittest import TestCase
-from pecan import set_config
-from pecan.testing import load_test_app
-
-__all__ = ['FunctionalTest']
-
-
-class FunctionalTest(TestCase):
- """
- Used for functional tests where you need to test your
- literal application and its integration with the framework.
- """
-
- def setUp(self):
- self.app = load_test_app(os.path.join(
- os.path.dirname(__file__),
- 'config.py'
- ))
-
- def tearDown(self):
- set_config({}, overwrite=True)
diff --git a/pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl b/pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl
deleted file mode 100644
index 09efcb7..0000000
--- a/pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl
+++ /dev/null
@@ -1,19 +0,0 @@
-# Server Specific Configurations
-server = {
- 'port': '8080',
- 'host': '0.0.0.0'
-}
-
-# Pecan Application Configurations
-app = {
- 'root': '${package}.controllers.root.RootController',
- 'modules': ['${package}'],
- 'debug': True
-}
-
-# Custom Configurations must be in Python dictionary format::
-#
-# foo = {'bar':'baz'}
-#
-# All configurations are accessible at::
-# pecan.conf
diff --git a/pecan/scaffolds/rest-api/+package+/tests/test_functional.py_tmpl b/pecan/scaffolds/rest-api/+package+/tests/test_functional.py_tmpl
deleted file mode 100644
index db414db..0000000
--- a/pecan/scaffolds/rest-api/+package+/tests/test_functional.py_tmpl
+++ /dev/null
@@ -1,37 +0,0 @@
-import json
-from ${package}.tests import FunctionalTest
-
-
-class TestRootController(FunctionalTest):
-
- def test_get_all(self):
- response = self.app.get('/people/')
- assert response.status_int == 200
- assert response.namespace[1] == 'Luke'
- assert response.namespace[2] == 'Leia'
- assert response.namespace[3] == 'Han'
- assert response.namespace[4] == 'Anakin'
-
- def test_get_one(self):
- response = self.app.get('/people/1/')
- assert response.status_int == 200
- assert response.body.decode() == 'Luke'
-
- def test_post(self):
- response = self.app.post('/people/')
- assert response.status_int == 201
-
- def test_put(self):
- response = self.app.put('/people/1/')
- assert response.status_int == 204
-
- def test_delete(self):
- response = self.app.delete('/people/1/')
- assert response.status_int == 204
-
- def test_not_found(self):
- response = self.app.get('/missing/', expect_errors=True)
- assert response.status_int == 404
- assert json.loads(response.body.decode()) == {
- 'reason': 'The resource could not be found.'
- }
diff --git a/pecan/scaffolds/rest-api/+package+/tests/test_units.py b/pecan/scaffolds/rest-api/+package+/tests/test_units.py
deleted file mode 100644
index 573fb68..0000000
--- a/pecan/scaffolds/rest-api/+package+/tests/test_units.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from unittest import TestCase
-
-
-class TestUnits(TestCase):
-
- def test_units(self):
- assert 5 * 5 == 25
diff --git a/pecan/scaffolds/rest-api/config.py_tmpl b/pecan/scaffolds/rest-api/config.py_tmpl
deleted file mode 100644
index a865c6c..0000000
--- a/pecan/scaffolds/rest-api/config.py_tmpl
+++ /dev/null
@@ -1,48 +0,0 @@
-# Server Specific Configurations
-server = {
- 'port': '8080',
- 'host': '0.0.0.0'
-}
-
-# Pecan Application Configurations
-app = {
- 'root': '${package}.controllers.root.RootController',
- 'modules': ['${package}'],
- 'debug': True
-}
-
-logging = {
- 'root': {'level': 'INFO', 'handlers': ['console']},
- 'loggers': {
- '${package}': {'level': 'DEBUG', 'handlers': ['console']},
- 'pecan': {'level': 'DEBUG', 'handlers': ['console']},
- 'py.warnings': {'handlers': ['console']},
- '__force_dict__': True
- },
- 'handlers': {
- 'console': {
- 'level': 'DEBUG',
- 'class': 'logging.StreamHandler',
- 'formatter': 'color'
- }
- },
- 'formatters': {
- 'simple': {
- 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]'
- '[%(threadName)s] %(message)s')
- },
- 'color': {
- '()': 'pecan.log.ColorFormatter',
- 'format': ('%(asctime)s [%(padded_color_levelname)s] [%(name)s]'
- '[%(threadName)s] %(message)s'),
- '__force_dict__': True
- }
- }
-}
-
-# Custom Configurations must be in Python dictionary format::
-#
-# foo = {'bar':'baz'}
-#
-# All configurations are accessible at::
-# pecan.conf
diff --git a/pecan/scaffolds/rest-api/setup.cfg_tmpl b/pecan/scaffolds/rest-api/setup.cfg_tmpl
deleted file mode 100644
index 111f7cc..0000000
--- a/pecan/scaffolds/rest-api/setup.cfg_tmpl
+++ /dev/null
@@ -1,6 +0,0 @@
-[nosetests]
-match=^test
-where=${package}
-nocapture=1
-cover-package=${package}
-cover-erase=1
diff --git a/pecan/scaffolds/rest-api/setup.py_tmpl b/pecan/scaffolds/rest-api/setup.py_tmpl
deleted file mode 100644
index ec47896..0000000
--- a/pecan/scaffolds/rest-api/setup.py_tmpl
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-try:
- from setuptools import setup, find_packages
-except ImportError:
- from ez_setup import use_setuptools
- use_setuptools()
- from setuptools import setup, find_packages
-
-setup(
- name='${package}',
- version='0.1',
- description='',
- author='',
- author_email='',
- install_requires=[
- "pecan",
- ],
- test_suite='${package}',
- zip_safe=False,
- include_package_data=True,
- packages=find_packages(exclude=['ez_setup'])
-)
diff --git a/pecan/secure.py b/pecan/secure.py
deleted file mode 100644
index ea9fd51..0000000
--- a/pecan/secure.py
+++ /dev/null
@@ -1,232 +0,0 @@
-from functools import wraps
-from inspect import getmembers, isfunction
-from webob import exc
-
-import six
-
-from .decorators import expose
-from .util import _cfg, iscontroller
-
-__all__ = ['unlocked', 'secure', 'SecureController']
-
-if six.PY3:
- from .compat import is_bound_method as ismethod
-else:
- from inspect import ismethod
-
-
-class _SecureState(object):
- def __init__(self, desc, boolean_value):
- self.description = desc
- self.boolean_value = boolean_value
-
- def __repr__(self):
- return '<SecureState %s>' % self.description
-
- def __nonzero__(self):
- return self.boolean_value
-
- def __bool__(self):
- return self.__nonzero__()
-
-Any = _SecureState('Any', False)
-Protected = _SecureState('Protected', True)
-
-
-# security method decorators
-def _unlocked_method(func):
- _cfg(func)['secured'] = Any
- return func
-
-
-def _secure_method(check_permissions_func):
- def wrap(func):
- cfg = _cfg(func)
- cfg['secured'] = Protected
- cfg['check_permissions'] = check_permissions_func
- return func
- return wrap
-
-
-# classes to assist with wrapping attributes
-class _UnlockedAttribute(object):
- def __init__(self, obj):
- self.obj = obj
-
- @_unlocked_method
- @expose()
- def _lookup(self, *remainder):
- return self.obj, remainder
-
-
-class _SecuredAttribute(object):
- def __init__(self, obj, check_permissions):
- self.obj = obj
- self.check_permissions = check_permissions
- self._parent = None
-
- def _check_permissions(self):
- if isinstance(self.check_permissions, six.string_types):
- return getattr(self.parent, self.check_permissions)()
- else:
- return self.check_permissions()
-
- def __get_parent(self):
- return self._parent
-
- def __set_parent(self, parent):
- if ismethod(parent):
- self._parent = six.get_method_self(parent)
- else:
- self._parent = parent
- parent = property(__get_parent, __set_parent)
-
- @_secure_method('_check_permissions')
- @expose()
- def _lookup(self, *remainder):
- return self.obj, remainder
-
-
-# helper for secure decorator
-def _allowed_check_permissions_types(x):
- return (
- ismethod(x) or
- isfunction(x) or
- isinstance(x, six.string_types)
- )
-
-
-# methods that can either decorate functions or wrap classes
-# these should be the main methods used for securing or unlocking
-def unlocked(func_or_obj):
- """
- This method unlocks method or class attribute on a SecureController. Can
- be used to decorate or wrap an attribute
- """
- if ismethod(func_or_obj) or isfunction(func_or_obj):
- return _unlocked_method(func_or_obj)
- else:
- return _UnlockedAttribute(func_or_obj)
-
-
-def secure(func_or_obj, check_permissions_for_obj=None):
- """
- This method secures a method or class depending on invocation.
-
- To decorate a method use one argument:
- @secure(<check_permissions_method>)
-
- To secure a class, invoke with two arguments:
- secure(<obj instance>, <check_permissions_method>)
- """
- if _allowed_check_permissions_types(func_or_obj):
- return _secure_method(func_or_obj)
- else:
- if not _allowed_check_permissions_types(check_permissions_for_obj):
- msg = "When securing an object, secure() requires the " + \
- "second argument to be method"
- raise TypeError(msg)
- return _SecuredAttribute(func_or_obj, check_permissions_for_obj)
-
-
-class SecureControllerMeta(type):
- """
- Used to apply security to a controller.
- Implementations of SecureController should extend the
- `check_permissions` method to return a True or False
- value (depending on whether or not the user has permissions
- to the controller).
- """
- def __init__(cls, name, bases, dict_):
- cls._pecan = dict(
- secured=Protected,
- check_permissions=cls.check_permissions,
- unlocked=[]
- )
-
- for name, value in getmembers(cls)[:]:
- if (isfunction if six.PY3 else ismethod)(value):
- if iscontroller(value) and value._pecan.get(
- 'secured'
- ) is None:
- # Wrap the function so that the security context is
- # local to this class definition. This works around
- # the fact that unbound method attributes are shared
- # across classes with the same bases.
- wrapped = _make_wrapper(value)
- wrapped._pecan['secured'] = Protected
- wrapped._pecan['check_permissions'] = \
- cls.check_permissions
- setattr(cls, name, wrapped)
- elif hasattr(value, '__class__'):
- if name.startswith('__') and name.endswith('__'):
- continue
- if isinstance(value, _UnlockedAttribute):
- # mark it as unlocked and remove wrapper
- cls._pecan['unlocked'].append(value.obj)
- setattr(cls, name, value.obj)
- elif isinstance(value, _SecuredAttribute):
- # The user has specified a different check_permissions
- # than the class level version. As far as the class
- # is concerned, this method is unlocked because
- # it is using a check_permissions function embedded in
- # the _SecuredAttribute wrapper
- cls._pecan['unlocked'].append(value)
-
-
-class SecureControllerBase(object):
-
- @classmethod
- def check_permissions(cls):
- """
- Returns `True` or `False` to grant access. Implemented in subclasses
- of :class:`SecureController`.
- """
- return False
-
-
-SecureController = SecureControllerMeta(
- 'SecureController',
- (SecureControllerBase,),
- {'__doc__': SecureControllerMeta.__doc__}
-)
-
-
-def _make_wrapper(f):
- """return a wrapped function with a copy of the _pecan context"""
- @wraps(f)
- def wrapper(*args, **kwargs):
- return f(*args, **kwargs)
- wrapper._pecan = f._pecan.copy()
- return wrapper
-
-
-# methods to evaluate security during routing
-def handle_security(controller, im_self=None):
- """ Checks the security of a controller. """
- if controller._pecan.get('secured', False):
- check_permissions = controller._pecan['check_permissions']
-
- if isinstance(check_permissions, six.string_types):
- check_permissions = getattr(
- im_self or six.get_method_self(controller),
- check_permissions
- )
-
- if not check_permissions():
- raise exc.HTTPUnauthorized
-
-
-def cross_boundary(prev_obj, obj):
- """ Check permissions as we move between object instances. """
- if prev_obj is None:
- return
-
- if isinstance(obj, _SecuredAttribute):
- # a secure attribute can live in unsecure class so we have to set
- # while we walk the route
- obj.parent = prev_obj
-
- if hasattr(prev_obj, '_pecan'):
- if obj not in prev_obj._pecan.get('unlocked', []):
- handle_security(prev_obj)
diff --git a/pecan/templating.py b/pecan/templating.py
deleted file mode 100644
index 81a60a0..0000000
--- a/pecan/templating.py
+++ /dev/null
@@ -1,286 +0,0 @@
-from .compat import escape
-from .jsonify import encode
-
-_builtin_renderers = {}
-error_formatters = []
-
-#
-# JSON rendering engine
-#
-
-
-class JsonRenderer(object):
- '''
- Defines the builtin ``JSON`` renderer.
- '''
- def __init__(self, path, extra_vars):
- pass
-
- def render(self, template_path, namespace):
- '''
- Implements ``JSON`` rendering.
- '''
- return encode(namespace)
-
- # TODO: add error formatter for json (pass it through json lint?)
-
-_builtin_renderers['json'] = JsonRenderer
-
-#
-# Genshi rendering engine
-#
-
-try:
- from genshi.template import (TemplateLoader,
- TemplateError as gTemplateError)
-
- class GenshiRenderer(object):
- '''
- Defines the builtin ``Genshi`` renderer.
- '''
- def __init__(self, path, extra_vars):
- self.loader = TemplateLoader([path], auto_reload=True)
- self.extra_vars = extra_vars
-
- def render(self, template_path, namespace):
- '''
- Implements ``Genshi`` rendering.
- '''
- tmpl = self.loader.load(template_path)
- stream = tmpl.generate(**self.extra_vars.make_ns(namespace))
- return stream.render('html')
-
- _builtin_renderers['genshi'] = GenshiRenderer
-
- def format_genshi_error(exc_value):
- '''
- Implements ``Genshi`` renderer error formatting.
- '''
- if isinstance(exc_value, (gTemplateError)):
- retval = '<h4>Genshi error %s</h4>' % escape(
- exc_value.args[0],
- True
- )
- retval += format_line_context(exc_value.filename, exc_value.lineno)
- return retval
- error_formatters.append(format_genshi_error)
-except ImportError: # pragma no cover
- pass
-
-
-#
-# Mako rendering engine
-#
-
-try:
- from mako.lookup import TemplateLookup
- from mako.exceptions import (CompileException, SyntaxException,
- html_error_template)
-
- class MakoRenderer(object):
- '''
- Defines the builtin ``Mako`` renderer.
- '''
- def __init__(self, path, extra_vars):
- self.loader = TemplateLookup(
- directories=[path],
- output_encoding='utf-8'
- )
- self.extra_vars = extra_vars
-
- def render(self, template_path, namespace):
- '''
- Implements ``Mako`` rendering.
- '''
- tmpl = self.loader.get_template(template_path)
- return tmpl.render(**self.extra_vars.make_ns(namespace))
-
- _builtin_renderers['mako'] = MakoRenderer
-
- def format_mako_error(exc_value):
- '''
- Implements ``Mako`` renderer error formatting.
- '''
- if isinstance(exc_value, (CompileException, SyntaxException)):
- return html_error_template().render(full=False, css=False)
-
- error_formatters.append(format_mako_error)
-except ImportError: # pragma no cover
- pass
-
-
-#
-# Kajiki rendering engine
-#
-
-try:
- from kajiki.loader import FileLoader
-
- class KajikiRenderer(object):
- '''
- Defines the builtin ``Kajiki`` renderer.
- '''
- def __init__(self, path, extra_vars):
- self.loader = FileLoader(path, reload=True)
- self.extra_vars = extra_vars
-
- def render(self, template_path, namespace):
- '''
- Implements ``Kajiki`` rendering.
- '''
- Template = self.loader.import_(template_path)
- stream = Template(self.extra_vars.make_ns(namespace))
- return stream.render()
- _builtin_renderers['kajiki'] = KajikiRenderer
- # TODO: add error formatter for kajiki
-except ImportError: # pragma no cover
- pass
-
-#
-# Jinja2 rendering engine
-#
-try:
- from jinja2 import Environment, FileSystemLoader
- from jinja2.exceptions import TemplateSyntaxError as jTemplateSyntaxError
-
- class JinjaRenderer(object):
- '''
- Defines the builtin ``Jinja`` renderer.
- '''
- def __init__(self, path, extra_vars):
- self.env = Environment(loader=FileSystemLoader(path))
- self.extra_vars = extra_vars
-
- def render(self, template_path, namespace):
- '''
- Implements ``Jinja`` rendering.
- '''
- template = self.env.get_template(template_path)
- return template.render(self.extra_vars.make_ns(namespace))
- _builtin_renderers['jinja'] = JinjaRenderer
-
- def format_jinja_error(exc_value):
- '''
- Implements ``Jinja`` renderer error formatting.
- '''
- retval = '<h4>Jinja2 error in \'%s\' on line %d</h4><div>%s</div>'
- if isinstance(exc_value, (jTemplateSyntaxError)):
- retval = retval % (
- exc_value.name,
- exc_value.lineno,
- exc_value.message
- )
- retval += format_line_context(exc_value.filename, exc_value.lineno)
- return retval
- error_formatters.append(format_jinja_error)
-except ImportError: # pragma no cover
- pass
-
-
-#
-# format helper function
-#
-def format_line_context(filename, lineno, context=10):
- '''
- Formats the the line context for error rendering.
-
- :param filename: the location of the file, within which the error occurred
- :param lineno: the offending line number
- :param context: number of lines of code to display before and after the
- offending line.
- '''
- lines = open(filename).readlines()
-
- lineno = lineno - 1 # files are indexed by 1 not 0
- if lineno > 0:
- start_lineno = max(lineno - context, 0)
- end_lineno = lineno + context
-
- lines = [escape(l, True) for l in lines[start_lineno:end_lineno]]
- i = lineno - start_lineno
- lines[i] = '<strong>%s</strong>' % lines[i]
-
- else:
- lines = [escape(l, True) for l in lines[:context]]
- msg = '<pre style="background-color:#ccc;padding:2em;">%s</pre>'
- return msg % ''.join(lines)
-
-
-#
-# Extra Vars Rendering
-#
-class ExtraNamespace(object):
- '''
- Extra variables for the template namespace to pass to the renderer as named
- parameters.
-
- :param extras: dictionary of extra parameters. Defaults to an empty dict.
- '''
- def __init__(self, extras={}):
- self.namespace = dict(extras)
-
- def update(self, d):
- '''
- Updates the extra variable dictionary for the namespace.
- '''
- self.namespace.update(d)
-
- def make_ns(self, ns):
- '''
- Returns the `lazily` created template namespace.
- '''
- if self.namespace:
- val = {}
- val.update(self.namespace)
- val.update(ns)
- return val
- else:
- return ns
-
-
-#
-# Rendering Factory
-#
-class RendererFactory(object):
- '''
- Manufactures known Renderer objects.
-
- :param custom_renderers: custom-defined renderers to manufacture
- :param extra_vars: extra vars for the template namespace
- '''
- def __init__(self, custom_renderers={}, extra_vars={}):
- self._renderers = {}
- self._renderer_classes = dict(_builtin_renderers)
- self.add_renderers(custom_renderers)
- self.extra_vars = ExtraNamespace(extra_vars)
-
- def add_renderers(self, custom_dict):
- '''
- Adds a custom renderer.
-
- :param custom_dict: a dictionary of custom renderers to add
- '''
- self._renderer_classes.update(custom_dict)
-
- def available(self, name):
- '''
- Returns true if queried renderer class is available.
-
- :param name: renderer name
- '''
- return name in self._renderer_classes
-
- def get(self, name, template_path):
- '''
- Returns the renderer object.
-
- :param name: name of the requested renderer
- :param template_path: path to the template
- '''
- if name not in self._renderers:
- cls = self._renderer_classes.get(name)
- if cls is None:
- return None
- else:
- self._renderers[name] = cls(template_path, self.extra_vars)
- return self._renderers[name]
diff --git a/pecan/testing.py b/pecan/testing.py
deleted file mode 100644
index 14986ff..0000000
--- a/pecan/testing.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from pecan import load_app
-from webtest import TestApp
-
-
-def load_test_app(config=None, **kwargs):
- """
- Used for functional tests where you need to test your
- literal application and its integration with the framework.
-
- :param config: Can be a dictionary containing configuration, a string which
- represents a (relative) configuration filename or ``None``
- which will fallback to get the ``PECAN_CONFIG`` env
- variable.
-
- returns a pecan.Pecan WSGI application wrapped in a webtest.TestApp
- instance.
-
- ::
- app = load_test_app('path/to/some/config.py')
-
- resp = app.get('/path/to/some/resource').status_int
- assert resp.status_int == 200
-
- resp = app.post('/path/to/some/resource', params={'param': 'value'})
- assert resp.status_int == 302
-
- Alternatively you could call ``load_test_app`` with no parameters if the
- environment variable is set ::
-
- app = load_test_app()
-
- resp = app.get('/path/to/some/resource').status_int
- assert resp.status_int == 200
- """
- return TestApp(load_app(config, **kwargs))
diff --git a/pecan/tests/__init__.py b/pecan/tests/__init__.py
deleted file mode 100644
index 4d2df74..0000000
--- a/pecan/tests/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import sys
-import os
-if sys.version_info < (2, 7):
- from unittest2 import TestCase # pragma: nocover
-else:
- from unittest import TestCase # pragma: nocover
-
-
-class PecanTestCase(TestCase):
-
- def setUp(self):
- self.addCleanup(self._reset_global_config)
-
- def _reset_global_config(self):
- from pecan import configuration
- configuration.set_config(
- dict(configuration.initconf()),
- overwrite=True
- )
- os.environ.pop('PECAN_CONFIG', None)
diff --git a/pecan/tests/config_fixtures/bad/importerror.py b/pecan/tests/config_fixtures/bad/importerror.py
deleted file mode 100644
index 592fa5b..0000000
--- a/pecan/tests/config_fixtures/bad/importerror.py
+++ /dev/null
@@ -1 +0,0 @@
-import pecan.thismoduledoesnotexist
diff --git a/pecan/tests/config_fixtures/bad/module_and_underscore.py b/pecan/tests/config_fixtures/bad/module_and_underscore.py
deleted file mode 100644
index e53f416..0000000
--- a/pecan/tests/config_fixtures/bad/module_and_underscore.py
+++ /dev/null
@@ -1,4 +0,0 @@
-import sys
-
-__badattr__ = True
-moduleattr = sys
diff --git a/pecan/tests/config_fixtures/config.py b/pecan/tests/config_fixtures/config.py
deleted file mode 100644
index bba299b..0000000
--- a/pecan/tests/config_fixtures/config.py
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-# Server Specific Configurations
-server = {
- 'port': '8081',
- 'host': '1.1.1.1',
- 'hostport': '{pecan.conf.server.host}:{pecan.conf.server.port}'
-}
-
-# Pecan Application Configurations
-app = {
- 'static_root': 'public',
- 'template_path': 'myproject/templates',
- 'debug': True
-}
-
-# Custom Configurations must be in Python dictionary format::
-#
-# foo = {'bar':'baz'}
-#
-# All configurations are accessible at::
-# pecan.conf
diff --git a/pecan/tests/config_fixtures/empty.py b/pecan/tests/config_fixtures/empty.py
deleted file mode 100644
index b4a9332..0000000
--- a/pecan/tests/config_fixtures/empty.py
+++ /dev/null
@@ -1,2 +0,0 @@
-app = {}
-server = {}
diff --git a/pecan/tests/config_fixtures/foobar.py b/pecan/tests/config_fixtures/foobar.py
deleted file mode 100644
index 5abc475..0000000
--- a/pecan/tests/config_fixtures/foobar.py
+++ /dev/null
@@ -1 +0,0 @@
-foo = "bar"
diff --git a/pecan/tests/config_fixtures/forcedict.py b/pecan/tests/config_fixtures/forcedict.py
deleted file mode 100644
index 4e2c83a..0000000
--- a/pecan/tests/config_fixtures/forcedict.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Pecan Application Configurations
-beaker = {
- 'session.key': 'key',
- 'session.type': 'cookie',
- 'session.validate_key': '1a971a7df182df3e1dec0af7c6913ec7',
- '__force_dict__': True
-}
-
-# Custom Configurations must be in Python dictionary format::
-#
-# foo = {'bar':'baz'}
-#
-# All configurations are accessible at::
-# pecan.conf
diff --git a/pecan/tests/middleware/__init__.py b/pecan/tests/middleware/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/tests/middleware/__init__.py
+++ /dev/null
diff --git a/pecan/tests/middleware/static_fixtures/self.png b/pecan/tests/middleware/static_fixtures/self.png
deleted file mode 100644
index 9b30321..0000000
--- a/pecan/tests/middleware/static_fixtures/self.png
+++ /dev/null
Binary files differ
diff --git a/pecan/tests/middleware/static_fixtures/text.txt b/pecan/tests/middleware/static_fixtures/text.txt
deleted file mode 100644
index c6defe5..0000000
--- a/pecan/tests/middleware/static_fixtures/text.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a test text file.
-
-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
-tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
-veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
-commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
-velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
-occaecat cupidatat non proident, sunt in culpa qui officia deserunt
-mollit anim id est laborum. \ No newline at end of file
diff --git a/pecan/tests/middleware/test_errordocument.py b/pecan/tests/middleware/test_errordocument.py
deleted file mode 100644
index 29f46e5..0000000
--- a/pecan/tests/middleware/test_errordocument.py
+++ /dev/null
@@ -1,92 +0,0 @@
-import json
-
-from webtest import TestApp
-from six import b as b_
-
-import pecan
-from pecan.middleware.errordocument import ErrorDocumentMiddleware
-from pecan.middleware.recursive import RecursiveMiddleware
-from pecan.tests import PecanTestCase
-
-
-def four_oh_four_app(environ, start_response):
- if environ['PATH_INFO'].startswith('/error'):
- code = environ['PATH_INFO'].split('/')[2]
- start_response("200 OK", [('Content-type', 'text/plain')])
-
- body = "Error: %s" % code
- if environ['QUERY_STRING']:
- body += "\nQS: %s" % environ['QUERY_STRING']
- return [b_(body)]
- start_response("404 Not Found", [('Content-type', 'text/plain')])
- return []
-
-
-class TestErrorDocumentMiddleware(PecanTestCase):
-
- def setUp(self):
- super(TestErrorDocumentMiddleware, self).setUp()
- self.app = TestApp(RecursiveMiddleware(ErrorDocumentMiddleware(
- four_oh_four_app, {404: '/error/404'}
- )))
-
- def test_hit_error_page(self):
- r = self.app.get('/error/404')
- assert r.status_int == 200
- assert r.body == b_('Error: 404')
-
- def test_middleware_routes_to_404_message(self):
- r = self.app.get('/', expect_errors=True)
- assert r.status_int == 404
- assert r.body == b_('Error: 404')
-
- def test_error_endpoint_with_query_string(self):
- app = TestApp(RecursiveMiddleware(ErrorDocumentMiddleware(
- four_oh_four_app, {404: '/error/404?foo=bar'}
- )))
- r = app.get('/', expect_errors=True)
- assert r.status_int == 404
- assert r.body == b_('Error: 404\nQS: foo=bar')
-
- def test_error_with_recursion_loop(self):
- app = TestApp(RecursiveMiddleware(ErrorDocumentMiddleware(
- four_oh_four_app, {404: '/'}
- )))
- r = app.get('/', expect_errors=True)
- assert r.status_int == 404
- assert r.body == b_(
- 'Error: 404 Not Found. (Error page could not be fetched)'
- )
-
- def test_original_exception(self):
-
- class RootController(object):
-
- @pecan.expose()
- def index(self):
- if pecan.request.method != 'POST':
- pecan.abort(405, 'You have to POST, dummy!')
- return 'Hello, World!'
-
- @pecan.expose('json')
- def error(self, status):
- return dict(
- status=int(status),
- reason=pecan.request.environ[
- 'pecan.original_exception'
- ].detail
- )
-
- app = pecan.Pecan(RootController())
- app = RecursiveMiddleware(ErrorDocumentMiddleware(app, {
- 405: '/error/405'
- }))
- app = TestApp(app)
-
- assert app.post('/').status_int == 200
- r = app.get('/', expect_errors=405)
- assert r.status_int == 405
-
- resp = json.loads(r.body.decode())
- assert resp['status'] == 405
- assert resp['reason'] == 'You have to POST, dummy!'
diff --git a/pecan/tests/middleware/test_recursive.py b/pecan/tests/middleware/test_recursive.py
deleted file mode 100644
index ed95d50..0000000
--- a/pecan/tests/middleware/test_recursive.py
+++ /dev/null
@@ -1,142 +0,0 @@
-from webtest import TestApp
-from six import b as b_
-
-from pecan.middleware.recursive import (RecursiveMiddleware,
- ForwardRequestException)
-from pecan.tests import PecanTestCase
-
-
-def simple_app(environ, start_response):
- start_response("200 OK", [('Content-type', 'text/plain')])
- return [b_('requested page returned')]
-
-
-def error_docs_app(environ, start_response):
- if environ['PATH_INFO'] == '/not_found':
- start_response("404 Not found", [('Content-type', 'text/plain')])
- return [b_('Not found')]
- elif environ['PATH_INFO'] == '/error':
- start_response("200 OK", [('Content-type', 'text/plain')])
- return [b_('Page not found')]
- elif environ['PATH_INFO'] == '/recurse':
- raise ForwardRequestException('/recurse')
- else:
- return simple_app(environ, start_response)
-
-
-class Middleware(object):
- def __init__(self, app, url='/error'):
- self.app = app
- self.url = url
-
- def __call__(self, environ, start_response):
- raise ForwardRequestException(self.url)
-
-
-def forward(app):
- app = TestApp(RecursiveMiddleware(app))
- res = app.get('')
-
- assert res.headers['content-type'] == 'text/plain'
- assert res.status == '200 OK'
- assert 'requested page returned' in res
- res = app.get('/error')
- assert res.headers['content-type'] == 'text/plain'
- assert res.status == '200 OK'
- assert 'Page not found' in res
- res = app.get('/not_found')
- assert res.headers['content-type'] == 'text/plain'
- assert res.status == '200 OK'
- assert 'Page not found' in res
- try:
- res = app.get('/recurse')
- except AssertionError as e:
- if str(e).startswith('Forwarding loop detected'):
- pass
- else:
- raise AssertionError('Failed to detect forwarding loop')
-
-
-class TestRecursiveMiddleware(PecanTestCase):
-
- def test_ForwardRequest_url(self):
- class TestForwardRequestMiddleware(Middleware):
- def __call__(self, environ, start_response):
- if environ['PATH_INFO'] != '/not_found':
- return self.app(environ, start_response)
- raise ForwardRequestException(self.url)
- forward(TestForwardRequestMiddleware(error_docs_app))
-
- def test_ForwardRequest_url_with_params(self):
- class TestForwardRequestMiddleware(Middleware):
- def __call__(self, environ, start_response):
- if environ['PATH_INFO'] != '/not_found':
- return self.app(environ, start_response)
- raise ForwardRequestException(self.url + '?q=1')
- forward(TestForwardRequestMiddleware(error_docs_app))
-
- def test_ForwardRequest_environ(self):
- class TestForwardRequestMiddleware(Middleware):
- def __call__(self, environ, start_response):
- if environ['PATH_INFO'] != '/not_found':
- return self.app(environ, start_response)
- environ['PATH_INFO'] = self.url
- raise ForwardRequestException(environ=environ)
- forward(TestForwardRequestMiddleware(error_docs_app))
-
- def test_ForwardRequest_factory(self):
-
- class TestForwardRequestMiddleware(Middleware):
- def __call__(self, environ, start_response):
- if environ['PATH_INFO'] != '/not_found':
- return self.app(environ, start_response)
- environ['PATH_INFO'] = self.url
-
- def factory(app):
-
- class WSGIApp(object):
-
- def __init__(self, app):
- self.app = app
-
- def __call__(self, e, start_response):
- def keep_status_start_response(status, headers,
- exc_info=None):
- return start_response(
- '404 Not Found', headers, exc_info
- )
- return self.app(e, keep_status_start_response)
-
- return WSGIApp(app)
-
- raise ForwardRequestException(factory=factory)
-
- app = TestForwardRequestMiddleware(error_docs_app)
- app = TestApp(RecursiveMiddleware(app))
- res = app.get('')
- assert res.headers['content-type'] == 'text/plain'
- assert res.status == '200 OK'
- assert 'requested page returned' in res
- res = app.get('/error')
- assert res.headers['content-type'] == 'text/plain'
- assert res.status == '200 OK'
- assert 'Page not found' in res
- res = app.get('/not_found', status=404)
- assert res.headers['content-type'] == 'text/plain'
- assert res.status == '404 Not Found' # Different status
- assert 'Page not found' in res
- try:
- res = app.get('/recurse')
- except AssertionError as e:
- if str(e).startswith('Forwarding loop detected'):
- pass
- else:
- raise AssertionError('Failed to detect forwarding loop')
-
- def test_ForwardRequestException(self):
- class TestForwardRequestExceptionMiddleware(Middleware):
- def __call__(self, environ, start_response):
- if environ['PATH_INFO'] != '/not_found':
- return self.app(environ, start_response)
- raise ForwardRequestException(path_info=self.url)
- forward(TestForwardRequestExceptionMiddleware(error_docs_app))
diff --git a/pecan/tests/middleware/test_static.py b/pecan/tests/middleware/test_static.py
deleted file mode 100644
index 9a0c08c..0000000
--- a/pecan/tests/middleware/test_static.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from pecan.middleware.static import (StaticFileMiddleware, FileWrapper,
- _dump_date)
-from pecan.tests import PecanTestCase
-
-import os
-
-
-class TestStaticFileMiddleware(PecanTestCase):
-
- def setUp(self):
- super(TestStaticFileMiddleware, self).setUp()
-
- def app(environ, start_response):
- response_headers = [('Content-type', 'text/plain')]
- start_response('200 OK', response_headers)
- return ['Hello world!\n']
-
- self.app = StaticFileMiddleware(
- app, os.path.dirname(__file__)
- )
-
- self._status = None
- self._response_headers = None
-
- def _request(self, path):
- def start_response(status, response_headers, exc_info=None):
- self._status = status
- self._response_headers = response_headers
- return self.app(
- dict(PATH_INFO=path),
- start_response
- )
-
- def _get_response_header(self, header):
- for k, v in self._response_headers:
- if k.upper() == header.upper():
- return v
- return None
-
- def test_file_can_be_found(self):
- result = self._request('/static_fixtures/text.txt')
- assert isinstance(result, FileWrapper)
-
- def test_no_file_found_causes_passthrough(self):
- result = self._request('/static_fixtures/nosuchfile.txt')
- assert not isinstance(result, FileWrapper)
- assert result == ['Hello world!\n']
-
- def test_mime_type_works_for_png_files(self):
- self._request('/static_fixtures/self.png')
- assert self._get_response_header('Content-Type') == 'image/png'
-
- def test_file_can_be_closed(self):
- result = self._request('/static_fixtures/text.txt')
- assert result.close() is None
-
- def test_file_can_be_iterated_over(self):
- result = self._request('/static_fixtures/text.txt')
- assert len([x for x in result])
-
- def test_date_dumping_on_unix_timestamps(self):
- result = _dump_date(1331755274.59, ' ')
- assert result == 'Wed, 14 Mar 2012 20:01:14 GMT'
-
- def test_separator_sanitization_still_finds_file(self):
- os.altsep = ':'
- result = self._request(':static_fixtures:text.txt')
- assert isinstance(result, FileWrapper)
diff --git a/pecan/tests/scaffold_builder.py b/pecan/tests/scaffold_builder.py
deleted file mode 100644
index dc039da..0000000
--- a/pecan/tests/scaffold_builder.py
+++ /dev/null
@@ -1,172 +0,0 @@
-import os
-import sys
-import subprocess
-import time
-
-from six import b as b_
-
-from pecan.compat import urlopen, URLError
-from pecan.tests import PecanTestCase
-
-if sys.version_info < (2, 7):
- import unittest2 as unittest
-else:
- import unittest # noqa
-
-
-if __name__ == '__main__':
-
- class TestTemplateBuilds(PecanTestCase):
- """
- Used to test the templated quickstart project(s).
- """
-
- @property
- def bin(self):
- return os.path.dirname(sys.executable)
-
- def poll(self, proc):
- limit = 30
- for i in range(limit):
- proc.poll()
-
- # Make sure it's running
- if proc.returncode is None:
- break
- elif i == limit: # pragma: no cover
- raise RuntimeError("Server process didn't start.")
- time.sleep(.1)
-
- def test_project_pecan_serve_command(self):
- # Start the server
- proc = subprocess.Popen([
- os.path.join(self.bin, 'pecan'),
- 'serve',
- 'testing123/config.py'
- ])
-
- try:
- self.poll(proc)
- retries = 30
- while True:
- retries -= 1
- if retries < 0: # pragma: nocover
- raise RuntimeError(
- "The HTTP server has not replied within 3 seconds."
- )
- try:
- # ...and that it's serving (valid) content...
- resp = urlopen('http://localhost:8080/')
- assert resp.getcode()
- assert len(resp.read().decode())
- except URLError:
- pass
- else:
- break
- time.sleep(.1)
- finally:
- proc.terminate()
-
- def test_project_pecan_shell_command(self):
- # Start the server
- proc = subprocess.Popen([
- os.path.join(self.bin, 'pecan'),
- 'shell',
- 'testing123/config.py'
- ],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- stdin=subprocess.PIPE
- )
-
- self.poll(proc)
-
- out, _ = proc.communicate(
- b_('{"model" : model, "conf" : conf, "app" : app}')
- )
- assert 'testing123.model' in out.decode(), out
- assert 'Config(' in out.decode(), out
- assert 'webtest.app.TestApp' in out.decode(), out
-
- try:
- # just in case stdin doesn't close
- proc.terminate()
- except:
- pass
-
- class TestThirdPartyServe(TestTemplateBuilds):
-
- def poll_http(self, name, proc, port):
- try:
- self.poll(proc)
- retries = 30
- while True:
- retries -= 1
- if retries < 0: # pragma: nocover
- raise RuntimeError(
- "The %s server has not replied within"
- " 3 seconds." % name
- )
- try:
- # ...and that it's serving (valid) content...
- resp = urlopen('http://localhost:%d/' % port)
- assert resp.getcode()
- assert len(resp.read().decode())
- except URLError:
- pass
- else:
- break
- time.sleep(.1)
- finally:
- proc.terminate()
-
- class TestGunicornServeCommand(TestThirdPartyServe):
-
- def test_serve_from_config(self):
- # Start the server
- proc = subprocess.Popen([
- os.path.join(self.bin, 'gunicorn_pecan'),
- 'testing123/config.py'
- ])
-
- self.poll_http('gunicorn', proc, 8080)
-
- def test_serve_with_custom_bind(self):
- # Start the server
- proc = subprocess.Popen([
- os.path.join(self.bin, 'gunicorn_pecan'),
- '--bind=0.0.0.0:9191',
- 'testing123/config.py'
- ])
-
- self.poll_http('gunicorn', proc, 9191)
-
- class TestUWSGIServiceCommand(TestThirdPartyServe):
-
- def test_serve_from_config(self):
- # Start the server
- proc = subprocess.Popen([
- os.path.join(self.bin, 'uwsgi'),
- '--http-socket',
- ':8080',
- '--venv',
- sys.prefix,
- '--pecan',
- 'testing123/config.py'
- ])
-
- self.poll_http('uwsgi', proc, 8080)
-
- # First, ensure that the `testing123` package has been installed
- args = [
- os.path.join(os.path.dirname(sys.executable), 'pip'),
- 'install',
- '-U',
- '-e',
- './testing123'
- ]
- process = subprocess.Popen(args)
- _, unused_err = process.communicate()
- assert not process.poll()
-
- unittest.main()
diff --git a/pecan/tests/scaffold_fixtures/__init__.py b/pecan/tests/scaffold_fixtures/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/tests/scaffold_fixtures/__init__.py
+++ /dev/null
diff --git a/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl b/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl
deleted file mode 100644
index 95a9c91..0000000
--- a/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl
+++ /dev/null
@@ -1 +0,0 @@
-Pecan ${package}
diff --git a/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl b/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl
deleted file mode 100644
index 25591f3..0000000
--- a/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl
+++ /dev/null
@@ -1 +0,0 @@
-YAR ${package}
diff --git a/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt b/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt
deleted file mode 100644
index 02c61ad..0000000
--- a/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt
+++ /dev/null
@@ -1 +0,0 @@
-Pecan
diff --git a/pecan/tests/scaffold_fixtures/file_sub/foo_+package+ b/pecan/tests/scaffold_fixtures/file_sub/foo_+package+
deleted file mode 100644
index 035599b..0000000
--- a/pecan/tests/scaffold_fixtures/file_sub/foo_+package+
+++ /dev/null
@@ -1 +0,0 @@
-YAR
diff --git a/pecan/tests/scaffold_fixtures/simple/bar/spam.txt b/pecan/tests/scaffold_fixtures/simple/bar/spam.txt
deleted file mode 100644
index 02c61ad..0000000
--- a/pecan/tests/scaffold_fixtures/simple/bar/spam.txt
+++ /dev/null
@@ -1 +0,0 @@
-Pecan
diff --git a/pecan/tests/scaffold_fixtures/simple/foo b/pecan/tests/scaffold_fixtures/simple/foo
deleted file mode 100644
index 035599b..0000000
--- a/pecan/tests/scaffold_fixtures/simple/foo
+++ /dev/null
@@ -1 +0,0 @@
-YAR
diff --git a/pecan/tests/templates/__init__.py b/pecan/tests/templates/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/tests/templates/__init__.py
+++ /dev/null
diff --git a/pecan/tests/templates/form_colors.html b/pecan/tests/templates/form_colors.html
deleted file mode 100644
index cd4fea9..0000000
--- a/pecan/tests/templates/form_colors.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<input type="text" id="colors-0" name="colors-0" value="${data['colors'][0] if data else 'A color'}" />
-<input type="text" id="colors-1" name="colors-1" value="${data['colors'][1] if data else 'A color'}" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_colors_invalid.html b/pecan/tests/templates/form_colors_invalid.html
deleted file mode 100644
index d2d4ed7..0000000
--- a/pecan/tests/templates/form_colors_invalid.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<input type="text" id="colors-0" name="colors-0" value="blue" />
-<input type="text" id="colors-1" name="colors-1" value="" class="error" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_colors_valid.html b/pecan/tests/templates/form_colors_valid.html
deleted file mode 100644
index adecfbf..0000000
--- a/pecan/tests/templates/form_colors_valid.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<input type="text" id="colors-0" name="colors-0" value="blue" />
-<input type="text" id="colors-1" name="colors-1" value="red" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_login_invalid.html b/pecan/tests/templates/form_login_invalid.html
deleted file mode 100644
index cede816..0000000
--- a/pecan/tests/templates/form_login_invalid.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<input type="text" id="username" name="username" value="ryan" />
-<input type="password" id="password" name="password" value="" class="error" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_login_valid.html b/pecan/tests/templates/form_login_valid.html
deleted file mode 100644
index 0587754..0000000
--- a/pecan/tests/templates/form_login_valid.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<input type="text" id="username" name="username" />
-<input type="password" id="password" name="password" value="" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_name.html b/pecan/tests/templates/form_name.html
deleted file mode 100644
index b46181d..0000000
--- a/pecan/tests/templates/form_name.html
+++ /dev/null
@@ -1 +0,0 @@
-<input type="text" id="name" name="name" value="${name if name else ''}" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_name_invalid.html b/pecan/tests/templates/form_name_invalid.html
deleted file mode 100644
index bcbd035..0000000
--- a/pecan/tests/templates/form_name_invalid.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<!-- for: name -->
-<span class="error-message">Please enter a value</span><br />
-<input type="text" id="name" name="name" value="" class="error" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_name_invalid_custom.html b/pecan/tests/templates/form_name_invalid_custom.html
deleted file mode 100644
index cbb631f..0000000
--- a/pecan/tests/templates/form_name_invalid_custom.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<!-- for: name -->
-<span class="error-message">Names must be unique</span><br />
-<input type="text" id="name" name="name" value="Yoann" class="error" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_name_valid.html b/pecan/tests/templates/form_name_valid.html
deleted file mode 100644
index fc7d693..0000000
--- a/pecan/tests/templates/form_name_valid.html
+++ /dev/null
@@ -1 +0,0 @@
-<input type="text" id="name" name="name" value="Yoann" /> \ No newline at end of file
diff --git a/pecan/tests/templates/genshi.html b/pecan/tests/templates/genshi.html
deleted file mode 100644
index a42aa32..0000000
--- a/pecan/tests/templates/genshi.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:py="http://genshi.edgewall.org/"
- xmlns:xi="http://www.w3.org/2001/XInclude">
-
-<head>
- <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
- <title>Hello, ${name}!</title>
-</head>
-
-<body>
- <h1>Hello, ${name}!</h1>
-</body>
-
-</html>
diff --git a/pecan/tests/templates/genshi_bad.html b/pecan/tests/templates/genshi_bad.html
deleted file mode 100644
index 557bc55..0000000
--- a/pecan/tests/templates/genshi_bad.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:py="http://genshi.edgewall.org/"
- xmlns:xi="http://www.w3.org/2001/XInclude">
-
-<head>
- <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
- <title>Hello, ${name}!</title>
-<!-- comment out close tag to cause error
-</head>
--->
-
-<body>
- <h1>Hello, ${name}!</h1>
-</body>
-
-</html>
diff --git a/pecan/tests/templates/jinja.html b/pecan/tests/templates/jinja.html
deleted file mode 100644
index e96835a..0000000
--- a/pecan/tests/templates/jinja.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<html>
-
-<head>
- <title>Hello, {{name}}!</title>
-</head>
-
-<body>
- <h1>Hello, {{name}}!</h1>
-</body>
-
-</html>
diff --git a/pecan/tests/templates/jinja_bad.html b/pecan/tests/templates/jinja_bad.html
deleted file mode 100644
index 3513871..0000000
--- a/pecan/tests/templates/jinja_bad.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<html>
-
-<head>
- <title>Hello, {{name}}!</title>
-</head>
-
-<body>
- <h1>Hello, {{name}}!</h1>
-</body>
-{# open a block without and name #}
-{% block %}
-
-</html>
diff --git a/pecan/tests/templates/kajiki.html b/pecan/tests/templates/kajiki.html
deleted file mode 100644
index 41c308c..0000000
--- a/pecan/tests/templates/kajiki.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<html>
-
-<head>
- <title>Hello, ${name}!</title>
-</head>
-
-<body>
- <h1>Hello, ${name}!</h1>
-</body>
-
-</html>
diff --git a/pecan/tests/templates/mako.html b/pecan/tests/templates/mako.html
deleted file mode 100644
index db60fec..0000000
--- a/pecan/tests/templates/mako.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<html>
-
-<head>
- <title>Hello, ${name}!</title>
-</head>
-
-<body>
- <h1>Hello, ${name}!</h1>
-</body>
-
-</html> \ No newline at end of file
diff --git a/pecan/tests/templates/mako_bad.html b/pecan/tests/templates/mako_bad.html
deleted file mode 100644
index e6caea2..0000000
--- a/pecan/tests/templates/mako_bad.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<%
-
- def bad_indentation:
-return None
-
-%>
diff --git a/pecan/tests/test_base.py b/pecan/tests/test_base.py
deleted file mode 100644
index 8558f82..0000000
--- a/pecan/tests/test_base.py
+++ /dev/null
@@ -1,2227 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import sys
-import os
-import json
-import traceback
-import warnings
-
-import webob
-from webob.exc import HTTPNotFound
-import mock
-from webtest import TestApp
-import six
-from six import b as b_
-from six import u as u_
-from six.moves import cStringIO as StringIO
-
-from pecan import (
- Pecan, Request, Response, expose, request, response, redirect,
- abort, make_app, override_template, render, route
-)
-from pecan.templating import (
- _builtin_renderers as builtin_renderers, error_formatters
-)
-from pecan.decorators import accept_noncanonical
-from pecan.tests import PecanTestCase
-
-if sys.version_info < (2, 7):
- import unittest2 as unittest # pragma: nocover
-else:
- import unittest # pragma: nocover
-
-
-class SampleRootController(object):
- pass
-
-
-class TestAppRoot(PecanTestCase):
-
- def test_controller_lookup_by_string_path(self):
- app = Pecan('pecan.tests.test_base.SampleRootController')
- assert app.root and isinstance(app.root, SampleRootController)
-
-
-class TestEmptyContent(PecanTestCase):
- @property
- def app_(self):
- class RootController(object):
- @expose()
- def index(self):
- pass
-
- @expose()
- def explicit_body(self):
- response.body = b_('Hello, World!')
-
- @expose()
- def empty_body(self):
- response.body = b_('')
-
- @expose()
- def explicit_text(self):
- response.text = six.text_type('Hello, World!')
-
- @expose()
- def empty_text(self):
- response.text = six.text_type('')
-
- @expose()
- def explicit_json(self):
- response.json = {'foo': 'bar'}
-
- @expose()
- def explicit_json_body(self):
- response.json_body = {'foo': 'bar'}
-
- @expose()
- def non_unicode(self):
- return chr(0xc0)
-
- return TestApp(Pecan(RootController()))
-
- def test_empty_index(self):
- r = self.app_.get('/')
- self.assertEqual(r.status_int, 204)
- self.assertNotIn('Content-Type', r.headers)
- self.assertEqual(r.headers['Content-Length'], '0')
- self.assertEqual(len(r.body), 0)
-
- def test_index_with_non_unicode(self):
- r = self.app_.get('/non_unicode/')
- self.assertEqual(r.status_int, 200)
-
- def test_explicit_body(self):
- r = self.app_.get('/explicit_body/')
- self.assertEqual(r.status_int, 200)
- self.assertEqual(r.body, b_('Hello, World!'))
-
- def test_empty_body(self):
- r = self.app_.get('/empty_body/')
- self.assertEqual(r.status_int, 204)
- self.assertEqual(r.body, b_(''))
-
- def test_explicit_text(self):
- r = self.app_.get('/explicit_text/')
- self.assertEqual(r.status_int, 200)
- self.assertEqual(r.body, b_('Hello, World!'))
-
- def test_empty_text(self):
- r = self.app_.get('/empty_text/')
- self.assertEqual(r.status_int, 204)
- self.assertEqual(r.body, b_(''))
-
- def test_explicit_json(self):
- r = self.app_.get('/explicit_json/')
- self.assertEqual(r.status_int, 200)
- json_resp = json.loads(r.body.decode())
- assert json_resp == {'foo': 'bar'}
-
- def test_explicit_json_body(self):
- r = self.app_.get('/explicit_json_body/')
- self.assertEqual(r.status_int, 200)
- json_resp = json.loads(r.body.decode())
- assert json_resp == {'foo': 'bar'}
-
-
-class TestAppIterFile(PecanTestCase):
- @property
- def app_(self):
- class RootController(object):
- @expose()
- def index(self):
- body = six.BytesIO(b_('Hello, World!'))
- response.body_file = body
-
- @expose()
- def empty(self):
- body = six.BytesIO(b_(''))
- response.body_file = body
-
- return TestApp(Pecan(RootController()))
-
- def test_body_generator(self):
- r = self.app_.get('/')
- self.assertEqual(r.status_int, 200)
- assert r.body == b_('Hello, World!')
-
- def test_empty_body_generator(self):
- r = self.app_.get('/empty')
- self.assertEqual(r.status_int, 204)
- assert len(r.body) == 0
-
-
-class TestInvalidURLEncoding(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
-
- @expose()
- def _route(self, args, request):
- assert request.path
-
- return TestApp(Pecan(RootController()))
-
- def test_rest_with_non_utf_8_body(self):
- r = self.app_.get('/%aa/', expect_errors=True)
- assert r.status_int == 400
-
-
-class TestIndexRouting(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- return TestApp(Pecan(RootController()))
-
- def test_empty_root(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- def test_index(self):
- r = self.app_.get('/index')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- def test_index_html(self):
- r = self.app_.get('/index.html')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
-
-class TestObjectDispatch(PecanTestCase):
-
- @property
- def app_(self):
- class SubSubController(object):
- @expose()
- def index(self):
- return '/sub/sub/'
-
- @expose()
- def deeper(self):
- return '/sub/sub/deeper'
-
- class SubController(object):
- @expose()
- def index(self):
- return '/sub/'
-
- @expose()
- def deeper(self):
- return '/sub/deeper'
-
- sub = SubSubController()
-
- class RootController(object):
- @expose()
- def index(self):
- return '/'
-
- @expose()
- def deeper(self):
- return '/deeper'
-
- sub = SubController()
-
- return TestApp(Pecan(RootController()))
-
- def test_index(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.body == b_('/')
-
- def test_one_level(self):
- r = self.app_.get('/deeper')
- assert r.status_int == 200
- assert r.body == b_('/deeper')
-
- def test_one_level_with_trailing(self):
- r = self.app_.get('/sub/')
- assert r.status_int == 200
- assert r.body == b_('/sub/')
-
- def test_two_levels(self):
- r = self.app_.get('/sub/deeper')
- assert r.status_int == 200
- assert r.body == b_('/sub/deeper')
-
- def test_two_levels_with_trailing(self):
- r = self.app_.get('/sub/sub/')
- assert r.status_int == 200
-
- def test_three_levels(self):
- r = self.app_.get('/sub/sub/deeper')
- assert r.status_int == 200
- assert r.body == b_('/sub/sub/deeper')
-
-
-@unittest.skipIf(not six.PY3, "tests are Python3 specific")
-class TestUnicodePathSegments(PecanTestCase):
-
- def test_unicode_methods(self):
- class RootController(object):
- pass
- setattr(RootController, '🌰', expose()(lambda self: 'Hello, World!'))
- app = TestApp(Pecan(RootController()))
-
- resp = app.get('/%F0%9F%8C%B0/')
- assert resp.status_int == 200
- assert resp.body == b_('Hello, World!')
-
- def test_unicode_child(self):
- class ChildController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- class RootController(object):
- pass
- setattr(RootController, '🌰', ChildController())
- app = TestApp(Pecan(RootController()))
-
- resp = app.get('/%F0%9F%8C%B0/')
- assert resp.status_int == 200
- assert resp.body == b_('Hello, World!')
-
-
-class TestLookups(PecanTestCase):
-
- @property
- def app_(self):
- class LookupController(object):
- def __init__(self, someID):
- self.someID = someID
-
- @expose()
- def index(self):
- return '/%s' % self.someID
-
- @expose()
- def name(self):
- return '/%s/name' % self.someID
-
- class RootController(object):
- @expose()
- def index(self):
- return '/'
-
- @expose()
- def _lookup(self, someID, *remainder):
- return LookupController(someID), remainder
-
- return TestApp(Pecan(RootController()))
-
- def test_index(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.body == b_('/')
-
- def test_lookup(self):
- r = self.app_.get('/100/')
- assert r.status_int == 200
- assert r.body == b_('/100')
-
- def test_lookup_with_method(self):
- r = self.app_.get('/100/name')
- assert r.status_int == 200
- assert r.body == b_('/100/name')
-
- def test_lookup_with_wrong_argspec(self):
- class RootController(object):
- @expose()
- def _lookup(self, someID):
- return 'Bad arg spec' # pragma: nocover
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- app = TestApp(Pecan(RootController()))
- r = app.get('/foo/bar', expect_errors=True)
- assert r.status_int == 404
-
-
-class TestCanonicalLookups(PecanTestCase):
-
- @property
- def app_(self):
- class LookupController(object):
- def __init__(self, someID):
- self.someID = someID
-
- @expose()
- def index(self):
- return self.someID
-
- class UserController(object):
- @expose()
- def _lookup(self, someID, *remainder):
- return LookupController(someID), remainder
-
- class RootController(object):
- users = UserController()
-
- return TestApp(Pecan(RootController()))
-
- def test_canonical_lookup(self):
- assert self.app_.get('/users', expect_errors=404).status_int == 404
- assert self.app_.get('/users/', expect_errors=404).status_int == 404
- assert self.app_.get('/users/100').status_int == 302
- assert self.app_.get('/users/100/').body == b_('100')
-
-
-class TestControllerArguments(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
- @expose()
- def index(self, id):
- return 'index: %s' % id
-
- @expose()
- def multiple(self, one, two):
- return 'multiple: %s, %s' % (one, two)
-
- @expose()
- def optional(self, id=None):
- return 'optional: %s' % str(id)
-
- @expose()
- def multiple_optional(self, one=None, two=None, three=None):
- return 'multiple_optional: %s, %s, %s' % (one, two, three)
-
- @expose()
- def variable_args(self, *args):
- return 'variable_args: %s' % ', '.join(args)
-
- @expose()
- def variable_kwargs(self, **kwargs):
- data = [
- '%s=%s' % (key, kwargs[key])
- for key in sorted(kwargs.keys())
- ]
- return 'variable_kwargs: %s' % ', '.join(data)
-
- @expose()
- def variable_all(self, *args, **kwargs):
- data = [
- '%s=%s' % (key, kwargs[key])
- for key in sorted(kwargs.keys())
- ]
- return 'variable_all: %s' % ', '.join(list(args) + data)
-
- @expose()
- def eater(self, id, dummy=None, *args, **kwargs):
- data = [
- '%s=%s' % (key, kwargs[key])
- for key in sorted(kwargs.keys())
- ]
- return 'eater: %s, %s, %s' % (
- id,
- dummy,
- ', '.join(list(args) + data)
- )
-
- @staticmethod
- @expose()
- def static(id):
- return "id is %s" % id
-
- @expose()
- def _route(self, args, request):
- if hasattr(self, args[0]):
- return getattr(self, args[0]), args[1:]
- else:
- return self.index, args
-
- return TestApp(Pecan(RootController()))
-
- def test_required_argument(self):
- try:
- r = self.app_.get('/')
- assert r.status_int != 200 # pragma: nocover
- except Exception as ex:
- assert type(ex) == TypeError
- assert ex.args[0] in (
- "index() takes exactly 2 arguments (1 given)",
- "index() missing 1 required positional argument: 'id'"
- ) # this messaging changed in Python 3.3
-
- def test_single_argument(self):
- r = self.app_.get('/1')
- assert r.status_int == 200
- assert r.body == b_('index: 1')
-
- def test_single_argument_with_encoded_url(self):
- r = self.app_.get('/This%20is%20a%20test%21')
- assert r.status_int == 200
- assert r.body == b_('index: This is a test!')
-
- def test_single_argument_with_plus(self):
- r = self.app_.get('/foo+bar')
- assert r.status_int == 200
- assert r.body == b_('index: foo+bar')
-
- def test_single_argument_with_encoded_plus(self):
- r = self.app_.get('/foo%2Bbar')
- assert r.status_int == 200
- assert r.body == b_('index: foo+bar')
-
- def test_two_arguments(self):
- r = self.app_.get('/1/dummy', status=404)
- assert r.status_int == 404
-
- def test_keyword_argument(self):
- r = self.app_.get('/?id=2')
- assert r.status_int == 200
- assert r.body == b_('index: 2')
-
- def test_keyword_argument_with_encoded_url(self):
- r = self.app_.get('/?id=This%20is%20a%20test%21')
- assert r.status_int == 200
- assert r.body == b_('index: This is a test!')
-
- def test_keyword_argument_with_plus(self):
- r = self.app_.get('/?id=foo+bar')
- assert r.status_int == 200
- assert r.body == b_('index: foo bar')
-
- def test_keyword_argument_with_encoded_plus(self):
- r = self.app_.get('/?id=foo%2Bbar')
- assert r.status_int == 200
- assert r.body == b_('index: foo+bar')
-
- def test_argument_and_keyword_argument(self):
- r = self.app_.get('/3?id=three')
- assert r.status_int == 200
- assert r.body == b_('index: 3')
-
- def test_encoded_argument_and_keyword_argument(self):
- r = self.app_.get('/This%20is%20a%20test%21?id=three')
- assert r.status_int == 200
- assert r.body == b_('index: This is a test!')
-
- def test_explicit_kwargs(self):
- r = self.app_.post('/', {'id': '4'})
- assert r.status_int == 200
- assert r.body == b_('index: 4')
-
- def test_path_with_explicit_kwargs(self):
- r = self.app_.post('/4', {'id': 'four'})
- assert r.status_int == 200
- assert r.body == b_('index: 4')
-
- def test_explicit_json_kwargs(self):
- r = self.app_.post_json('/', {'id': '4'})
- assert r.status_int == 200
- assert r.body == b_('index: 4')
-
- def test_path_with_explicit_json_kwargs(self):
- r = self.app_.post_json('/4', {'id': 'four'})
- assert r.status_int == 200
- assert r.body == b_('index: 4')
-
- def test_multiple_kwargs(self):
- r = self.app_.get('/?id=5&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('index: 5')
-
- def test_kwargs_from_root(self):
- r = self.app_.post('/', {'id': '6', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('index: 6')
-
- def test_json_kwargs_from_root(self):
- r = self.app_.post_json('/', {'id': '6', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('index: 6')
-
- # multiple args
-
- def test_multiple_positional_arguments(self):
- r = self.app_.get('/multiple/one/two')
- assert r.status_int == 200
- assert r.body == b_('multiple: one, two')
-
- def test_multiple_positional_arguments_with_url_encode(self):
- r = self.app_.get('/multiple/One%20/Two%21')
- assert r.status_int == 200
- assert r.body == b_('multiple: One , Two!')
-
- def test_multiple_positional_arguments_with_kwargs(self):
- r = self.app_.get('/multiple?one=three&two=four')
- assert r.status_int == 200
- assert r.body == b_('multiple: three, four')
-
- def test_multiple_positional_arguments_with_url_encoded_kwargs(self):
- r = self.app_.get('/multiple?one=Three%20&two=Four%20%21')
- assert r.status_int == 200
- assert r.body == b_('multiple: Three , Four !')
-
- def test_positional_args_with_dictionary_kwargs(self):
- r = self.app_.post('/multiple', {'one': 'five', 'two': 'six'})
- assert r.status_int == 200
- assert r.body == b_('multiple: five, six')
-
- def test_positional_args_with_json_kwargs(self):
- r = self.app_.post_json('/multiple', {'one': 'five', 'two': 'six'})
- assert r.status_int == 200
- assert r.body == b_('multiple: five, six')
-
- def test_positional_args_with_url_encoded_dictionary_kwargs(self):
- r = self.app_.post('/multiple', {'one': 'Five%20', 'two': 'Six%20%21'})
- assert r.status_int == 200
- assert r.body == b_('multiple: Five%20, Six%20%21')
-
- # optional arg
- def test_optional_arg(self):
- r = self.app_.get('/optional')
- assert r.status_int == 200
- assert r.body == b_('optional: None')
-
- def test_multiple_optional(self):
- r = self.app_.get('/optional/1')
- assert r.status_int == 200
- assert r.body == b_('optional: 1')
-
- def test_multiple_optional_url_encoded(self):
- r = self.app_.get('/optional/Some%20Number')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_multiple_optional_missing(self):
- r = self.app_.get('/optional/2/dummy', status=404)
- assert r.status_int == 404
-
- def test_multiple_with_kwargs(self):
- r = self.app_.get('/optional?id=2')
- assert r.status_int == 200
- assert r.body == b_('optional: 2')
-
- def test_multiple_with_url_encoded_kwargs(self):
- r = self.app_.get('/optional?id=Some%20Number')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_multiple_args_with_url_encoded_kwargs(self):
- r = self.app_.get('/optional/3?id=three')
- assert r.status_int == 200
- assert r.body == b_('optional: 3')
-
- def test_url_encoded_positional_args(self):
- r = self.app_.get('/optional/Some%20Number?id=three')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_optional_arg_with_kwargs(self):
- r = self.app_.post('/optional', {'id': '4'})
- assert r.status_int == 200
- assert r.body == b_('optional: 4')
-
- def test_optional_arg_with_json_kwargs(self):
- r = self.app_.post_json('/optional', {'id': '4'})
- assert r.status_int == 200
- assert r.body == b_('optional: 4')
-
- def test_optional_arg_with_url_encoded_kwargs(self):
- r = self.app_.post('/optional', {'id': 'Some%20Number'})
- assert r.status_int == 200
- assert r.body == b_('optional: Some%20Number')
-
- def test_multiple_positional_arguments_with_dictionary_kwargs(self):
- r = self.app_.post('/optional/5', {'id': 'five'})
- assert r.status_int == 200
- assert r.body == b_('optional: 5')
-
- def test_multiple_positional_arguments_with_json_kwargs(self):
- r = self.app_.post_json('/optional/5', {'id': 'five'})
- assert r.status_int == 200
- assert r.body == b_('optional: 5')
-
- def test_multiple_positional_url_encoded_arguments_with_kwargs(self):
- r = self.app_.post('/optional/Some%20Number', {'id': 'five'})
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_optional_arg_with_multiple_kwargs(self):
- r = self.app_.get('/optional?id=6&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('optional: 6')
-
- def test_optional_arg_with_multiple_url_encoded_kwargs(self):
- r = self.app_.get('/optional?id=Some%20Number&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_optional_arg_with_multiple_dictionary_kwargs(self):
- r = self.app_.post('/optional', {'id': '7', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('optional: 7')
-
- def test_optional_arg_with_multiple_json_kwargs(self):
- r = self.app_.post_json('/optional', {'id': '7', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('optional: 7')
-
- def test_optional_arg_with_multiple_url_encoded_dictionary_kwargs(self):
- r = self.app_.post('/optional', {
- 'id': 'Some%20Number',
- 'dummy': 'dummy'
- })
- assert r.status_int == 200
- assert r.body == b_('optional: Some%20Number')
-
- # multiple optional args
-
- def test_multiple_optional_positional_args(self):
- r = self.app_.get('/multiple_optional')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, None, None')
-
- def test_multiple_optional_positional_args_one_arg(self):
- r = self.app_.get('/multiple_optional/1')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_one_url_encoded_arg(self):
- r = self.app_.get('/multiple_optional/One%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_positional_args_all_args(self):
- r = self.app_.get('/multiple_optional/1/2/3')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_positional_args_all_url_encoded_args(self):
- r = self.app_.get('/multiple_optional/One%21/Two%21/Three%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, Two!, Three!')
-
- def test_multiple_optional_positional_args_too_many_args(self):
- r = self.app_.get('/multiple_optional/1/2/3/dummy', status=404)
- assert r.status_int == 404
-
- def test_multiple_optional_positional_args_with_kwargs(self):
- r = self.app_.get('/multiple_optional?one=1')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_url_encoded_kwargs(self):
- r = self.app_.get('/multiple_optional?one=One%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_positional_args_with_string_kwargs(self):
- r = self.app_.get('/multiple_optional/1?one=one')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_encoded_str_kwargs(self):
- r = self.app_.get('/multiple_optional/One%21?one=one')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_positional_args_with_dict_kwargs(self):
- r = self.app_.post('/multiple_optional', {'one': '1'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_json_kwargs(self):
- r = self.app_.post_json('/multiple_optional', {'one': '1'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_encoded_dict_kwargs(self):
- r = self.app_.post('/multiple_optional', {'one': 'One%21'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One%21, None, None')
-
- def test_multiple_optional_positional_args_and_dict_kwargs(self):
- r = self.app_.post('/multiple_optional/1', {'one': 'one'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_and_json_kwargs(self):
- r = self.app_.post_json('/multiple_optional/1', {'one': 'one'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_encoded_positional_args_and_dict_kwargs(self):
- r = self.app_.post('/multiple_optional/One%21', {'one': 'one'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_args_with_multiple_kwargs(self):
- r = self.app_.get('/multiple_optional?one=1&two=2&three=3&four=4')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_args_with_multiple_encoded_kwargs(self):
- r = self.app_.get(
- '/multiple_optional?one=One%21&two=Two%21&three=Three%21&four=4'
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, Two!, Three!')
-
- def test_multiple_optional_args_with_multiple_dict_kwargs(self):
- r = self.app_.post(
- '/multiple_optional',
- {'one': '1', 'two': '2', 'three': '3', 'four': '4'}
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_args_with_multiple_json_kwargs(self):
- r = self.app_.post_json(
- '/multiple_optional',
- {'one': '1', 'two': '2', 'three': '3', 'four': '4'}
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_args_with_multiple_encoded_dict_kwargs(self):
- r = self.app_.post(
- '/multiple_optional',
- {
- 'one': 'One%21',
- 'two': 'Two%21',
- 'three': 'Three%21',
- 'four': '4'
- }
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One%21, Two%21, Three%21')
-
- def test_multiple_optional_args_with_last_kwarg(self):
- r = self.app_.get('/multiple_optional?three=3')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, None, 3')
-
- def test_multiple_optional_args_with_last_encoded_kwarg(self):
- r = self.app_.get('/multiple_optional?three=Three%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, None, Three!')
-
- def test_multiple_optional_args_with_middle_arg(self):
- r = self.app_.get('/multiple_optional', {'two': '2'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, 2, None')
-
- def test_variable_args(self):
- r = self.app_.get('/variable_args')
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_multiple_variable_args(self):
- r = self.app_.get('/variable_args/1/dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_args: 1, dummy')
-
- def test_multiple_encoded_variable_args(self):
- r = self.app_.get('/variable_args/Testing%20One%20Two/Three%21')
- assert r.status_int == 200
- assert r.body == b_('variable_args: Testing One Two, Three!')
-
- def test_variable_args_with_kwargs(self):
- r = self.app_.get('/variable_args?id=2&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_variable_args_with_dict_kwargs(self):
- r = self.app_.post('/variable_args', {'id': '3', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_variable_args_with_json_kwargs(self):
- r = self.app_.post_json(
- '/variable_args',
- {'id': '3', 'dummy': 'dummy'}
- )
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_variable_kwargs(self):
- r = self.app_.get('/variable_kwargs')
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: ')
-
- def test_multiple_variable_kwargs(self):
- r = self.app_.get('/variable_kwargs/1/dummy', status=404)
- assert r.status_int == 404
-
- def test_multiple_variable_kwargs_with_explicit_kwargs(self):
- r = self.app_.get('/variable_kwargs?id=2&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=dummy, id=2')
-
- def test_multiple_variable_kwargs_with_explicit_encoded_kwargs(self):
- r = self.app_.get(
- '/variable_kwargs?id=Two%21&dummy=This%20is%20a%20test'
- )
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=This is a test, id=Two!')
-
- def test_multiple_variable_kwargs_with_dict_kwargs(self):
- r = self.app_.post('/variable_kwargs', {'id': '3', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=dummy, id=3')
-
- def test_multiple_variable_kwargs_with_json_kwargs(self):
- r = self.app_.post_json(
- '/variable_kwargs',
- {'id': '3', 'dummy': 'dummy'}
- )
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=dummy, id=3')
-
- def test_multiple_variable_kwargs_with_encoded_dict_kwargs(self):
- r = self.app_.post(
- '/variable_kwargs',
- {'id': 'Three%21', 'dummy': 'This%20is%20a%20test'}
- )
- assert r.status_int == 200
- result = 'variable_kwargs: dummy=This%20is%20a%20test, id=Three%21'
- assert r.body == b_(result)
-
- def test_variable_all(self):
- r = self.app_.get('/variable_all')
- assert r.status_int == 200
- assert r.body == b_('variable_all: ')
-
- def test_variable_all_with_one_extra(self):
- r = self.app_.get('/variable_all/1')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 1')
-
- def test_variable_all_with_two_extras(self):
- r = self.app_.get('/variable_all/2/dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 2, dummy')
-
- def test_variable_mixed(self):
- r = self.app_.get('/variable_all/3?month=1&day=12')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 3, day=12, month=1')
-
- def test_variable_mixed_explicit(self):
- r = self.app_.get('/variable_all/4?id=four&month=1&day=12')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 4, day=12, id=four, month=1')
-
- def test_variable_post(self):
- r = self.app_.post('/variable_all/5/dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 5, dummy')
-
- def test_variable_post_with_kwargs(self):
- r = self.app_.post('/variable_all/6', {'month': '1', 'day': '12'})
- assert r.status_int == 200
- assert r.body == b_('variable_all: 6, day=12, month=1')
-
- def test_variable_post_with_json_kwargs(self):
- r = self.app_.post_json(
- '/variable_all/6',
- {'month': '1', 'day': '12'}
- )
- assert r.status_int == 200
- assert r.body == b_('variable_all: 6, day=12, month=1')
-
- def test_variable_post_mixed(self):
- r = self.app_.post(
- '/variable_all/7',
- {'id': 'seven', 'month': '1', 'day': '12'}
- )
- assert r.status_int == 200
- assert r.body == b_('variable_all: 7, day=12, id=seven, month=1')
-
- def test_variable_post_mixed_with_json(self):
- r = self.app_.post_json(
- '/variable_all/7',
- {'id': 'seven', 'month': '1', 'day': '12'}
- )
- assert r.status_int == 200
- assert r.body == b_('variable_all: 7, day=12, id=seven, month=1')
-
- def test_duplicate_query_parameters_GET(self):
- r = self.app_.get('/variable_kwargs?list=1&list=2')
- l = [u_('1'), u_('2')]
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: list=%s' % l)
-
- def test_duplicate_query_parameters_POST(self):
- r = self.app_.post('/variable_kwargs',
- {'list': ['1', '2']})
- l = [u_('1'), u_('2')]
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: list=%s' % l)
-
- def test_duplicate_query_parameters_POST_mixed(self):
- r = self.app_.post('/variable_kwargs?list=1&list=2',
- {'list': ['3', '4']})
- l = [u_('1'), u_('2'), u_('3'), u_('4')]
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: list=%s' % l)
-
- def test_duplicate_query_parameters_POST_mixed_json(self):
- r = self.app_.post('/variable_kwargs?list=1&list=2',
- {'list': 3})
- l = [u_('1'), u_('2'), u_('3')]
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: list=%s' % l)
-
- def test_staticmethod(self):
- r = self.app_.get('/static/foobar')
- assert r.status_int == 200
- assert r.body == b_('id is foobar')
-
- def test_no_remainder(self):
- try:
- r = self.app_.get('/eater')
- assert r.status_int != 200 # pragma: nocover
- except Exception as ex:
- assert type(ex) == TypeError
- assert ex.args[0] in (
- "eater() takes at least 2 arguments (1 given)",
- "eater() missing 1 required positional argument: 'id'"
- ) # this messaging changed in Python 3.3
-
- def test_one_remainder(self):
- r = self.app_.get('/eater/1')
- assert r.status_int == 200
- assert r.body == b_('eater: 1, None, ')
-
- def test_two_remainders(self):
- r = self.app_.get('/eater/2/dummy')
- assert r.status_int == 200
- assert r.body == b_('eater: 2, dummy, ')
-
- def test_many_remainders(self):
- r = self.app_.get('/eater/3/dummy/foo/bar')
- assert r.status_int == 200
- assert r.body == b_('eater: 3, dummy, foo, bar')
-
- def test_remainder_with_kwargs(self):
- r = self.app_.get('/eater/4?month=1&day=12')
- assert r.status_int == 200
- assert r.body == b_('eater: 4, None, day=12, month=1')
-
- def test_remainder_with_many_kwargs(self):
- r = self.app_.get('/eater/5?id=five&month=1&day=12&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('eater: 5, dummy, day=12, month=1')
-
- def test_post_remainder(self):
- r = self.app_.post('/eater/6')
- assert r.status_int == 200
- assert r.body == b_('eater: 6, None, ')
-
- def test_post_three_remainders(self):
- r = self.app_.post('/eater/7/dummy')
- assert r.status_int == 200
- assert r.body == b_('eater: 7, dummy, ')
-
- def test_post_many_remainders(self):
- r = self.app_.post('/eater/8/dummy/foo/bar')
- assert r.status_int == 200
- assert r.body == b_('eater: 8, dummy, foo, bar')
-
- def test_post_remainder_with_kwargs(self):
- r = self.app_.post('/eater/9', {'month': '1', 'day': '12'})
- assert r.status_int == 200
- assert r.body == b_('eater: 9, None, day=12, month=1')
-
- def test_post_empty_remainder_with_json_kwargs(self):
- r = self.app_.post_json('/eater/9/', {'month': '1', 'day': '12'})
- assert r.status_int == 200
- assert r.body == b_('eater: 9, None, day=12, month=1')
-
- def test_post_remainder_with_json_kwargs(self):
- r = self.app_.post_json('/eater/9', {'month': '1', 'day': '12'})
- assert r.status_int == 200
- assert r.body == b_('eater: 9, None, day=12, month=1')
-
- def test_post_many_remainders_with_many_kwargs(self):
- r = self.app_.post(
- '/eater/10',
- {'id': 'ten', 'month': '1', 'day': '12', 'dummy': 'dummy'}
- )
- assert r.status_int == 200
- assert r.body == b_('eater: 10, dummy, day=12, month=1')
-
- def test_post_many_remainders_with_many_json_kwargs(self):
- r = self.app_.post_json(
- '/eater/10',
- {'id': 'ten', 'month': '1', 'day': '12', 'dummy': 'dummy'}
- )
- assert r.status_int == 200
- assert r.body == b_('eater: 10, dummy, day=12, month=1')
-
-
-class TestDefaultErrorRendering(PecanTestCase):
-
- def test_plain_error(self):
- class RootController(object):
- pass
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/', status=404)
- assert r.status_int == 404
- assert r.content_type == 'text/plain'
- assert r.body == b_(HTTPNotFound().plain_body({}))
-
- def test_html_error(self):
- class RootController(object):
- pass
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/', headers={'Accept': 'text/html'}, status=404)
- assert r.status_int == 404
- assert r.content_type == 'text/html'
- assert r.body == b_(HTTPNotFound().html_body({}))
-
- def test_json_error(self):
- class RootController(object):
- pass
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/', headers={'Accept': 'application/json'}, status=404)
- assert r.status_int == 404
- json_resp = json.loads(r.body.decode())
- assert json_resp['code'] == 404
- assert json_resp['description'] is None
- assert json_resp['title'] == 'Not Found'
- assert r.content_type == 'application/json'
-
-
-class TestAbort(PecanTestCase):
-
- def test_abort(self):
- class RootController(object):
- @expose()
- def index(self):
- abort(404)
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/', status=404)
- assert r.status_int == 404
-
- def test_abort_with_detail(self):
- class RootController(object):
- @expose()
- def index(self):
- abort(status_code=401, detail='Not Authorized')
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/', status=401)
- assert r.status_int == 401
-
- def test_abort_keeps_traceback(self):
- last_exc, last_traceback = None, None
-
- try:
- try:
- raise Exception('Bottom Exception')
- except:
- abort(404)
- except Exception:
- last_exc, _, last_traceback = sys.exc_info()
-
- assert last_exc is HTTPNotFound
- assert 'Bottom Exception' in traceback.format_tb(last_traceback)[-1]
-
-
-class TestScriptName(PecanTestCase):
-
- def setUp(self):
- super(TestScriptName, self).setUp()
- self.environ = {'SCRIPT_NAME': '/foo'}
-
- def test_handle_script_name(self):
- class RootController(object):
- @expose()
- def index(self):
- return 'Root Index'
-
- app = TestApp(Pecan(RootController()), extra_environ=self.environ)
- r = app.get('/foo/')
- assert r.status_int == 200
-
-
-class TestRedirect(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
- @expose()
- def index(self):
- redirect('/testing')
-
- @expose()
- def internal(self):
- redirect('/testing', internal=True)
-
- @expose()
- def bad_internal(self):
- redirect('/testing', internal=True, code=301)
-
- @expose()
- def permanent(self):
- redirect('/testing', code=301)
-
- @expose()
- def testing(self):
- return 'it worked!'
-
- return TestApp(make_app(RootController(), debug=False))
-
- def test_index(self):
- r = self.app_.get('/')
- assert r.status_int == 302
- r = r.follow()
- assert r.status_int == 200
- assert r.body == b_('it worked!')
-
- def test_internal(self):
- r = self.app_.get('/internal')
- assert r.status_int == 200
- assert r.body == b_('it worked!')
-
- def test_internal_with_301(self):
- self.assertRaises(ValueError, self.app_.get, '/bad_internal')
-
- def test_permanent_redirect(self):
- r = self.app_.get('/permanent')
- assert r.status_int == 301
- r = r.follow()
- assert r.status_int == 200
- assert r.body == b_('it worked!')
-
- def test_x_forward_proto(self):
- class ChildController(object):
- @expose()
- def index(self):
- redirect('/testing') # pragma: nocover
-
- class RootController(object):
- @expose()
- def index(self):
- redirect('/testing') # pragma: nocover
-
- @expose()
- def testing(self):
- return 'it worked!' # pragma: nocover
- child = ChildController()
-
- app = TestApp(make_app(RootController(), debug=True))
- res = app.get(
- '/child', extra_environ=dict(HTTP_X_FORWARDED_PROTO='https')
- )
- # non-canonical url will redirect, so we won't get a 301
- assert res.status_int == 302
- # should add trailing / and changes location to https
- assert res.location == 'https://localhost/child/'
- assert res.request.environ['HTTP_X_FORWARDED_PROTO'] == 'https'
-
-
-class TestInternalRedirectContext(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
-
- @expose()
- def redirect_with_context(self):
- request.context['foo'] = 'bar'
- redirect('/testing')
-
- @expose()
- def internal_with_context(self):
- request.context['foo'] = 'bar'
- redirect('/testing', internal=True)
-
- @expose('json')
- def testing(self):
- return request.context
-
- return TestApp(make_app(RootController(), debug=False))
-
- def test_internal_with_request_context(self):
- r = self.app_.get('/internal_with_context')
- assert r.status_int == 200
- assert json.loads(r.body.decode()) == {'foo': 'bar'}
-
- def test_context_does_not_bleed(self):
- r = self.app_.get('/redirect_with_context').follow()
- assert r.status_int == 200
- assert json.loads(r.body.decode()) == {}
-
-
-class TestStreamedResponse(PecanTestCase):
-
- def test_streaming_response(self):
-
- class RootController(object):
- @expose(content_type='text/plain')
- def test(self, foo):
- if foo == 'stream':
- # mimic large file
- contents = six.BytesIO(b_('stream'))
- response.content_type = 'application/octet-stream'
- contents.seek(0, os.SEEK_END)
- response.content_length = contents.tell()
- contents.seek(0, os.SEEK_SET)
- response.app_iter = contents
- return response
- else:
- return 'plain text'
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/test/stream')
- assert r.content_type == 'application/octet-stream'
- assert r.body == b_('stream')
-
- r = app.get('/test/plain')
- assert r.content_type == 'text/plain'
- assert r.body == b_('plain text')
-
-
-class TestManualResponse(PecanTestCase):
-
- def test_manual_response(self):
-
- class RootController(object):
- @expose()
- def index(self):
- resp = webob.Response(response.environ)
- resp.body = b_('Hello, World!')
- return resp
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/')
- assert r.body == b_('Hello, World!')
-
-
-class TestCustomResponseandRequest(PecanTestCase):
-
- def test_custom_objects(self):
-
- class CustomRequest(Request):
-
- @property
- def headers(self):
- headers = super(CustomRequest, self).headers
- headers['X-Custom-Request'] = 'ABC'
- return headers
-
- class CustomResponse(Response):
-
- @property
- def headers(self):
- headers = super(CustomResponse, self).headers
- headers['X-Custom-Response'] = 'XYZ'
- return headers
-
- class RootController(object):
- @expose()
- def index(self):
- return request.headers.get('X-Custom-Request')
-
- app = TestApp(Pecan(
- RootController(),
- request_cls=CustomRequest,
- response_cls=CustomResponse
- ))
- r = app.get('/')
- assert r.body == b_('ABC')
- assert r.headers.get('X-Custom-Response') == 'XYZ'
-
-
-class TestThreadLocalState(PecanTestCase):
-
- def test_thread_local_dir(self):
- """
- Threadlocal proxies for request and response should properly
- proxy ``dir()`` calls to the underlying webob class.
- """
- class RootController(object):
- @expose()
- def index(self):
- assert 'method' in dir(request)
- assert 'status' in dir(response)
- return '/'
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_('/')
-
- def test_request_state_cleanup(self):
- """
- After a request, the state local() should be totally clean
- except for state.app (so that objects don't leak between requests)
- """
- from pecan.core import state
-
- class RootController(object):
- @expose()
- def index(self):
- return '/'
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_('/')
-
- assert state.__dict__ == {}
-
-
-class TestFileTypeExtensions(PecanTestCase):
-
- @property
- def app_(self):
- """
- Test extension splits
- """
- class RootController(object):
- @expose(content_type=None)
- def _default(self, *args):
- ext = request.pecan['extension']
- assert len(args) == 1
- if ext:
- assert ext not in args[0]
- return ext or ''
-
- return TestApp(Pecan(RootController()))
-
- def test_html_extension(self):
- r = self.app_.get('/index.html')
- assert r.status_int == 200
- assert r.body == b_('.html')
-
- def test_image_extension(self):
- r = self.app_.get('/image.png')
- assert r.status_int == 200
- assert r.body == b_('.png')
-
- def test_hidden_file(self):
- r = self.app_.get('/.vimrc')
- assert r.status_int == 204
- assert r.body == b_('')
-
- def test_multi_dot_extension(self):
- r = self.app_.get('/gradient.min.js')
- assert r.status_int == 200
- assert r.body == b_('.js')
-
- def test_bad_content_type(self):
- class RootController(object):
- @expose()
- def index(self):
- return '/'
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_('/')
-
- r = app.get('/index.html', expect_errors=True)
- assert r.status_int == 200
- assert r.body == b_('/')
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = app.get('/index.txt', expect_errors=True)
- assert r.status_int == 404
-
- def test_unknown_file_extension(self):
- class RootController(object):
- @expose(content_type=None)
- def _default(self, *args):
- assert 'example:x.tiny' in args
- assert request.pecan['extension'] is None
- return 'SOME VALUE'
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/example:x.tiny')
- assert r.status_int == 200
- assert r.body == b_('SOME VALUE')
-
- def test_guessing_disabled(self):
- class RootController(object):
- @expose(content_type=None)
- def _default(self, *args):
- assert 'index.html' in args
- assert request.pecan['extension'] is None
- return 'SOME VALUE'
-
- app = TestApp(Pecan(RootController(),
- guess_content_type_from_ext=False))
-
- r = app.get('/index.html')
- assert r.status_int == 200
- assert r.body == b_('SOME VALUE')
-
-
-class TestContentTypeByAcceptHeaders(PecanTestCase):
-
- @property
- def app_(self):
- """
- Test that content type is set appropriately based on Accept headers.
- """
- class RootController(object):
-
- @expose(content_type='text/html')
- @expose(content_type='application/json')
- def index(self, *args):
- return 'Foo'
-
- return TestApp(Pecan(RootController()))
-
- def test_quality(self):
- r = self.app_.get('/', headers={
- 'Accept': 'text/html,application/json;q=0.9,*/*;q=0.8'
- })
- assert r.status_int == 200
- assert r.content_type == 'text/html'
-
- r = self.app_.get('/', headers={
- 'Accept': 'application/json,text/html;q=0.9,*/*;q=0.8'
- })
- assert r.status_int == 200
- assert r.content_type == 'application/json'
-
- def test_file_extension_has_higher_precedence(self):
- r = self.app_.get('/index.html', headers={
- 'Accept': 'application/json,text/html;q=0.9,*/*;q=0.8'
- })
- assert r.status_int == 200
- assert r.content_type == 'text/html'
-
- def test_not_acceptable(self):
- r = self.app_.get('/', headers={
- 'Accept': 'application/xml',
- }, status=406)
- assert r.status_int == 406
-
- def test_accept_header_missing(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.content_type == 'text/html'
-
-
-class TestCanonicalRouting(PecanTestCase):
-
- @property
- def app_(self):
- class ArgSubController(object):
- @expose()
- def index(self, arg):
- return arg
-
- class AcceptController(object):
- @accept_noncanonical
- @expose()
- def index(self):
- return 'accept'
-
- class SubController(object):
- @expose()
- def index(self, **kw):
- return 'subindex'
-
- class RootController(object):
- @expose()
- def index(self):
- return 'index'
-
- sub = SubController()
- arg = ArgSubController()
- accept = AcceptController()
-
- return TestApp(Pecan(RootController()))
-
- def test_root(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert b_('index') in r.body
-
- def test_index(self):
- r = self.app_.get('/index')
- assert r.status_int == 200
- assert b_('index') in r.body
-
- def test_broken_clients(self):
- # for broken clients
- r = self.app_.get('', status=302)
- assert r.status_int == 302
- assert r.location == 'http://localhost/'
-
- def test_sub_controller_with_trailing(self):
- r = self.app_.get('/sub/')
- assert r.status_int == 200
- assert b_('subindex') in r.body
-
- def test_sub_controller_redirect(self):
- r = self.app_.get('/sub', status=302)
- assert r.status_int == 302
- assert r.location == 'http://localhost/sub/'
-
- def test_with_query_string(self):
- # try with query string
- r = self.app_.get('/sub?foo=bar', status=302)
- assert r.status_int == 302
- assert r.location == 'http://localhost/sub/?foo=bar'
-
- def test_posts_fail(self):
- try:
- self.app_.post('/sub', dict(foo=1))
- raise Exception("Post should fail") # pragma: nocover
- except Exception as e:
- assert isinstance(e, RuntimeError)
-
- def test_with_args(self):
- r = self.app_.get('/arg/index/foo')
- assert r.status_int == 200
- assert r.body == b_('foo')
-
- def test_accept_noncanonical(self):
- r = self.app_.get('/accept/')
- assert r.status_int == 200
- assert r.body == b_('accept')
-
- def test_accept_noncanonical_no_trailing_slash(self):
- r = self.app_.get('/accept')
- assert r.status_int == 200
- assert r.body == b_('accept')
-
-
-class TestNonCanonical(PecanTestCase):
-
- @property
- def app_(self):
- class ArgSubController(object):
- @expose()
- def index(self, arg):
- return arg # pragma: nocover
-
- class AcceptController(object):
- @accept_noncanonical
- @expose()
- def index(self):
- return 'accept' # pragma: nocover
-
- class SubController(object):
- @expose()
- def index(self, **kw):
- return 'subindex'
-
- class RootController(object):
- @expose()
- def index(self):
- return 'index'
-
- sub = SubController()
- arg = ArgSubController()
- accept = AcceptController()
-
- return TestApp(Pecan(RootController(), force_canonical=False))
-
- def test_index(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert b_('index') in r.body
-
- def test_subcontroller(self):
- r = self.app_.get('/sub')
- assert r.status_int == 200
- assert b_('subindex') in r.body
-
- def test_subcontroller_with_kwargs(self):
- r = self.app_.post('/sub', dict(foo=1))
- assert r.status_int == 200
- assert b_('subindex') in r.body
-
- def test_sub_controller_with_trailing(self):
- r = self.app_.get('/sub/')
- assert r.status_int == 200
- assert b_('subindex') in r.body
-
- def test_proxy(self):
- class RootController(object):
- @expose()
- def index(self):
- request.testing = True
- assert request.testing is True
- del request.testing
- assert hasattr(request, 'testing') is False
- return '/'
-
- app = TestApp(make_app(RootController(), debug=True))
- r = app.get('/')
- assert r.status_int == 200
-
- def test_app_wrap(self):
- class RootController(object):
- pass
-
- wrapped_apps = []
-
- def wrap(app):
- wrapped_apps.append(app)
- return app
-
- make_app(RootController(), wrap_app=wrap, debug=True)
- assert len(wrapped_apps) == 1
-
-
-class TestLogging(PecanTestCase):
-
- def test_logging_setup(self):
- class RootController(object):
- @expose()
- def index(self):
- import logging
- logging.getLogger('pecantesting').info('HELLO WORLD')
- return "HELLO WORLD"
-
- f = StringIO()
-
- app = TestApp(make_app(RootController(), logging={
- 'loggers': {
- 'pecantesting': {
- 'level': 'INFO', 'handlers': ['memory']
- }
- },
- 'handlers': {
- 'memory': {
- 'level': 'INFO',
- 'class': 'logging.StreamHandler',
- 'stream': f
- }
- }
- }))
-
- app.get('/')
- assert f.getvalue() == 'HELLO WORLD\n'
-
- def test_logging_setup_with_config_obj(self):
- class RootController(object):
- @expose()
- def index(self):
- import logging
- logging.getLogger('pecantesting').info('HELLO WORLD')
- return "HELLO WORLD"
-
- f = StringIO()
-
- from pecan.configuration import conf_from_dict
- app = TestApp(make_app(RootController(), logging=conf_from_dict({
- 'loggers': {
- 'pecantesting': {
- 'level': 'INFO', 'handlers': ['memory']
- }
- },
- 'handlers': {
- 'memory': {
- 'level': 'INFO',
- 'class': 'logging.StreamHandler',
- 'stream': f
- }
- }
- })))
-
- app.get('/')
- assert f.getvalue() == 'HELLO WORLD\n'
-
-
-class TestEngines(PecanTestCase):
-
- template_path = os.path.join(os.path.dirname(__file__), 'templates')
-
- @unittest.skipIf('genshi' not in builtin_renderers, 'Genshi not installed')
- def test_genshi(self):
-
- class RootController(object):
- @expose('genshi:genshi.html')
- def index(self, name='Jonathan'):
- return dict(name=name)
-
- @expose('genshi:genshi_bad.html')
- def badtemplate(self):
- return dict()
-
- app = TestApp(
- Pecan(RootController(), template_path=self.template_path)
- )
- r = app.get('/')
- assert r.status_int == 200
- assert b_("<h1>Hello, Jonathan!</h1>") in r.body
-
- r = app.get('/index.html?name=World')
- assert r.status_int == 200
- assert b_("<h1>Hello, World!</h1>") in r.body
-
- error_msg = None
- try:
- r = app.get('/badtemplate.html')
- except Exception as e:
- for error_f in error_formatters:
- error_msg = error_f(e)
- if error_msg:
- break
- assert error_msg is not None
-
- @unittest.skipIf('kajiki' not in builtin_renderers, 'Kajiki not installed')
- def test_kajiki(self):
-
- class RootController(object):
- @expose('kajiki:kajiki.html')
- def index(self, name='Jonathan'):
- return dict(name=name)
-
- app = TestApp(
- Pecan(RootController(), template_path=self.template_path)
- )
- r = app.get('/')
- assert r.status_int == 200
- assert b_("<h1>Hello, Jonathan!</h1>") in r.body
-
- r = app.get('/index.html?name=World')
- assert r.status_int == 200
- assert b_("<h1>Hello, World!</h1>") in r.body
-
- @unittest.skipIf('jinja' not in builtin_renderers, 'Jinja not installed')
- def test_jinja(self):
-
- class RootController(object):
- @expose('jinja:jinja.html')
- def index(self, name='Jonathan'):
- return dict(name=name)
-
- @expose('jinja:jinja_bad.html')
- def badtemplate(self):
- return dict()
-
- app = TestApp(
- Pecan(RootController(), template_path=self.template_path)
- )
- r = app.get('/')
- assert r.status_int == 200
- assert b_("<h1>Hello, Jonathan!</h1>") in r.body
-
- error_msg = None
- try:
- r = app.get('/badtemplate.html')
- except Exception as e:
- for error_f in error_formatters:
- error_msg = error_f(e)
- if error_msg:
- break
- assert error_msg is not None
-
- @unittest.skipIf('mako' not in builtin_renderers, 'Mako not installed')
- def test_mako(self):
-
- class RootController(object):
- @expose('mako:mako.html')
- def index(self, name='Jonathan'):
- return dict(name=name)
-
- @expose('mako:mako_bad.html')
- def badtemplate(self):
- return dict()
-
- app = TestApp(
- Pecan(RootController(), template_path=self.template_path)
- )
- r = app.get('/')
- assert r.status_int == 200
- assert b_("<h1>Hello, Jonathan!</h1>") in r.body
-
- r = app.get('/index.html?name=World')
- assert r.status_int == 200
- assert b_("<h1>Hello, World!</h1>") in r.body
-
- error_msg = None
- try:
- r = app.get('/badtemplate.html')
- except Exception as e:
- for error_f in error_formatters:
- error_msg = error_f(e)
- if error_msg:
- break
- assert error_msg is not None
-
- def test_json(self):
- try:
- from simplejson import loads
- except:
- from json import loads # noqa
-
- expected_result = dict(
- name='Jonathan',
- age=30, nested=dict(works=True)
- )
-
- class RootController(object):
- @expose('json')
- def index(self):
- return expected_result
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/')
- assert r.status_int == 200
- result = dict(loads(r.body.decode()))
- assert result == expected_result
-
- def test_override_template(self):
- class RootController(object):
- @expose('foo.html')
- def index(self):
- override_template(None, content_type='text/plain')
- return 'Override'
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/')
- assert r.status_int == 200
- assert b_('Override') in r.body
- assert r.content_type == 'text/plain'
-
- def test_render(self):
- class RootController(object):
- @expose()
- def index(self, name='Jonathan'):
- return render('mako.html', dict(name=name))
-
- app = TestApp(
- Pecan(RootController(), template_path=self.template_path)
- )
- r = app.get('/')
- assert r.status_int == 200
- assert b_("<h1>Hello, Jonathan!</h1>") in r.body
-
- def test_default_json_renderer(self):
-
- class RootController(object):
- @expose()
- def index(self, name='Bill'):
- return dict(name=name)
-
- app = TestApp(Pecan(RootController(), default_renderer='json'))
- r = app.get('/')
- assert r.status_int == 200
- result = dict(json.loads(r.body.decode()))
- assert result == {'name': 'Bill'}
-
- def test_default_json_renderer_with_explicit_content_type(self):
-
- class RootController(object):
- @expose(content_type='text/plain')
- def index(self, name='Bill'):
- return name
-
- app = TestApp(Pecan(RootController(), default_renderer='json'))
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_("Bill")
-
-
-class TestDeprecatedRouteMethod(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
-
- @expose()
- def index(self, *args):
- return ', '.join(args)
-
- @expose()
- def _route(self, args):
- return self.index, args
-
- return TestApp(Pecan(RootController()))
-
- def test_required_argument(self):
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = self.app_.get('/foo/bar/')
- assert r.status_int == 200
- assert b_('foo, bar') in r.body
-
-
-class TestExplicitRoute(PecanTestCase):
-
- def test_alternate_route(self):
-
- class RootController(object):
-
- @expose(route='some-path')
- def some_path(self):
- return 'Hello, World!'
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/some-path/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- r = app.get('/some_path/', expect_errors=True)
- assert r.status_int == 404
-
- def test_manual_route(self):
-
- class SubController(object):
-
- @expose(route='some-path')
- def some_path(self):
- return 'Hello, World!'
-
- class RootController(object):
- pass
-
- route(RootController, 'some-controller', SubController())
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/some-controller/some-path/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- r = app.get('/some-controller/some_path/', expect_errors=True)
- assert r.status_int == 404
-
- def test_manual_route_conflict(self):
-
- class SubController(object):
- pass
-
- class RootController(object):
-
- @expose()
- def hello(self):
- return 'Hello, World!'
-
- self.assertRaises(
- RuntimeError,
- route,
- RootController,
- 'hello',
- SubController()
- )
-
- def test_custom_route_on_index(self):
-
- class RootController(object):
-
- @expose(route='some-path')
- def index(self):
- return 'Hello, World!'
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/some-path/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- r = app.get('/index/', expect_errors=True)
- assert r.status_int == 404
-
- def test_custom_route_with_attribute_conflict(self):
-
- class RootController(object):
-
- @expose(route='mock')
- def greet(self):
- return 'Hello, World!'
-
- @expose()
- def mock(self):
- return 'You are not worthy!'
-
- app = TestApp(Pecan(RootController()))
-
- self.assertRaises(
- RuntimeError,
- app.get,
- '/mock/'
- )
-
- def test_conflicting_custom_routes(self):
-
- class RootController(object):
-
- @expose(route='testing')
- def foo(self):
- return 'Foo!'
-
- @expose(route='testing')
- def bar(self):
- return 'Bar!'
-
- app = TestApp(Pecan(RootController()))
-
- self.assertRaises(
- RuntimeError,
- app.get,
- '/testing/'
- )
-
- def test_conflicting_custom_routes_in_subclass(self):
-
- class BaseController(object):
-
- @expose(route='testing')
- def foo(self):
- return request.path
-
- class ChildController(BaseController):
- pass
-
- class RootController(BaseController):
- child = ChildController()
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/testing/')
- assert r.body == b_('/testing/')
-
- r = app.get('/child/testing/')
- assert r.body == b_('/child/testing/')
-
- def test_custom_route_prohibited_on_lookup(self):
- try:
- class RootController(object):
-
- @expose(route='some-path')
- def _lookup(self):
- return 'Hello, World!'
- except ValueError:
- pass
- else:
- raise AssertionError(
- '_lookup cannot be used with a custom path segment'
- )
-
- def test_custom_route_prohibited_on_default(self):
- try:
- class RootController(object):
-
- @expose(route='some-path')
- def _default(self):
- return 'Hello, World!'
- except ValueError:
- pass
- else:
- raise AssertionError(
- '_default cannot be used with a custom path segment'
- )
-
- def test_custom_route_prohibited_on_route(self):
- try:
- class RootController(object):
-
- @expose(route='some-path')
- def _route(self):
- return 'Hello, World!'
- except ValueError:
- pass
- else:
- raise AssertionError(
- '_route cannot be used with a custom path segment'
- )
-
- def test_custom_route_with_generic_controllers(self):
-
- class RootController(object):
-
- @expose(route='some-path', generic=True)
- def foo(self):
- return 'Hello, World!'
-
- @foo.when(method='POST')
- def handle_post(self):
- return 'POST!'
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/some-path/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- r = app.get('/foo/', expect_errors=True)
- assert r.status_int == 404
-
- r = app.post('/some-path/')
- assert r.status_int == 200
- assert r.body == b_('POST!')
-
- r = app.post('/foo/', expect_errors=True)
- assert r.status_int == 404
-
- def test_custom_route_prohibited_on_generic_controllers(self):
- try:
- class RootController(object):
-
- @expose(generic=True)
- def foo(self):
- return 'Hello, World!'
-
- @foo.when(method='POST', route='some-path')
- def handle_post(self):
- return 'POST!'
- except ValueError:
- pass
- else:
- raise AssertionError(
- 'generic controllers cannot be used with a custom path segment'
- )
-
- def test_invalid_route_arguments(self):
- class C(object):
-
- def secret(self):
- return {}
-
- self.assertRaises(TypeError, route)
- self.assertRaises(TypeError, route, 'some-path', lambda x: x)
- self.assertRaises(TypeError, route, 'some-path', C.secret)
- self.assertRaises(TypeError, route, C, {}, C())
-
- for path in (
- 'VARIED-case-PATH',
- 'this,custom,path',
- '123-path',
- 'path(with-parens)',
- 'path;with;semicolons',
- 'path:with:colons',
- 'v2.0',
- '~username',
- 'somepath!',
- 'four*four',
- 'one+two',
- '@twitterhandle',
- 'package=pecan'
- ):
- handler = C()
- route(C, path, handler)
- assert getattr(C, path, handler)
-
- self.assertRaises(ValueError, route, C, '/path/', C())
- self.assertRaises(ValueError, route, C, '.', C())
- self.assertRaises(ValueError, route, C, '..', C())
- self.assertRaises(ValueError, route, C, 'path?', C())
- self.assertRaises(ValueError, route, C, 'percent%20encoded', C())
diff --git a/pecan/tests/test_commands.py b/pecan/tests/test_commands.py
deleted file mode 100644
index e9d6ead..0000000
--- a/pecan/tests/test_commands.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from pecan.tests import PecanTestCase
-
-
-class TestCommandManager(PecanTestCase):
-
- def test_commands(self):
- from pecan.commands import ServeCommand, ShellCommand, CreateCommand
- from pecan.commands.base import CommandManager
- m = CommandManager()
- assert m.commands['serve'] == ServeCommand
- assert m.commands['shell'] == ShellCommand
- assert m.commands['create'] == CreateCommand
-
-
-class TestCommandRunner(PecanTestCase):
-
- def test_commands(self):
- from pecan.commands import (
- ServeCommand, ShellCommand, CreateCommand, CommandRunner
- )
- runner = CommandRunner()
- assert runner.commands['serve'] == ServeCommand
- assert runner.commands['shell'] == ShellCommand
- assert runner.commands['create'] == CreateCommand
-
- def test_run(self):
- from pecan.commands import CommandRunner
- runner = CommandRunner()
- self.assertRaises(
- RuntimeError,
- runner.run,
- ['serve', 'missing_file.py']
- )
-
-
-class TestCreateCommand(PecanTestCase):
-
- def test_run(self):
- from pecan.commands import CreateCommand
-
- class FakeArg(object):
- project_name = 'default'
- template_name = 'default'
-
- class FakeScaffold(object):
- def copy_to(self, project_name):
- assert project_name == 'default'
-
- class FakeManager(object):
- scaffolds = {
- 'default': FakeScaffold
- }
-
- c = CreateCommand()
- c.manager = FakeManager()
- c.run(FakeArg())
diff --git a/pecan/tests/test_conf.py b/pecan/tests/test_conf.py
deleted file mode 100644
index e682885..0000000
--- a/pecan/tests/test_conf.py
+++ /dev/null
@@ -1,346 +0,0 @@
-import os
-import sys
-import tempfile
-
-from pecan.tests import PecanTestCase
-from six import b as b_
-
-
-__here__ = os.path.dirname(__file__)
-
-
-class TestConf(PecanTestCase):
-
- def test_update_config_fail_identifier(self):
- """Fail when naming does not pass correctness"""
- from pecan import configuration
- bad_dict = {'bad name': 'value'}
- self.assertRaises(ValueError, configuration.Config, bad_dict)
-
- def test_update_set_config(self):
- """Update an empty configuration with the default values"""
- from pecan import configuration
-
- conf = configuration.initconf()
- conf.update(configuration.conf_from_file(os.path.join(
- __here__,
- 'config_fixtures/config.py'
- )))
-
- self.assertEqual(conf.app.root, None)
- self.assertEqual(conf.app.template_path, 'myproject/templates')
- self.assertEqual(conf.app.static_root, 'public')
-
- self.assertEqual(conf.server.host, '1.1.1.1')
- self.assertEqual(conf.server.port, '8081')
-
- def test_update_set_default_config(self):
- """Update an empty configuration with the default values"""
- from pecan import configuration
-
- conf = configuration.initconf()
- conf.update(configuration.conf_from_file(os.path.join(
- __here__,
- 'config_fixtures/empty.py'
- )))
-
- self.assertEqual(conf.app.root, None)
- self.assertEqual(conf.app.template_path, '')
- self.assertEqual(conf.app.static_root, 'public')
-
- self.assertEqual(conf.server.host, '0.0.0.0')
- self.assertEqual(conf.server.port, '8080')
-
- def test_update_force_dict(self):
- """Update an empty configuration with the default values"""
- from pecan import configuration
- conf = configuration.initconf()
- conf.update(configuration.conf_from_file(os.path.join(
- __here__,
- 'config_fixtures/forcedict.py'
- )))
-
- self.assertEqual(conf.app.root, None)
- self.assertEqual(conf.app.template_path, '')
- self.assertEqual(conf.app.static_root, 'public')
-
- self.assertEqual(conf.server.host, '0.0.0.0')
- self.assertEqual(conf.server.port, '8080')
-
- self.assertTrue(isinstance(conf.beaker, dict))
- self.assertEqual(conf.beaker['session.key'], 'key')
- self.assertEqual(conf.beaker['session.type'], 'cookie')
- self.assertEqual(
- conf.beaker['session.validate_key'],
- '1a971a7df182df3e1dec0af7c6913ec7'
- )
- self.assertEqual(conf.beaker.get('__force_dict__'), None)
-
- def test_update_config_with_dict(self):
- from pecan import configuration
- conf = configuration.initconf()
- d = {'attr': True}
- conf['attr'] = d
- self.assertTrue(conf.attr.attr)
-
- def test_config_repr(self):
- from pecan import configuration
- conf = configuration.Config({'a': 1})
- self.assertEqual(repr(conf), "Config({'a': 1})")
-
- def test_config_from_dict(self):
- from pecan import configuration
- conf = configuration.conf_from_dict({})
- conf['path'] = '%(confdir)s'
- self.assertTrue(os.path.samefile(conf['path'], os.getcwd()))
-
- def test_config_from_file(self):
- from pecan import configuration
- path = os.path.join(
- os.path.dirname(__file__), 'config_fixtures', 'config.py'
- )
- configuration.conf_from_file(path)
-
- def test_config_illegal_ids(self):
- from pecan import configuration
- conf = configuration.Config({})
- conf.update(configuration.conf_from_file(os.path.join(
- __here__,
- 'config_fixtures/bad/module_and_underscore.py'
- )))
- self.assertEqual([], list(conf))
-
- def test_config_missing_file(self):
- from pecan import configuration
- path = ('doesnotexist.py',)
- configuration.Config({})
- self.assertRaises(
- RuntimeError,
- configuration.conf_from_file,
- os.path.join(__here__, 'config_fixtures', *path)
- )
-
- def test_config_missing_file_on_path(self):
- from pecan import configuration
- path = ('bad', 'bad', 'doesnotexist.py',)
- configuration.Config({})
-
- self.assertRaises(
- RuntimeError,
- configuration.conf_from_file,
- os.path.join(__here__, 'config_fixtures', *path)
- )
-
- def test_config_with_syntax_error(self):
- from pecan import configuration
- with tempfile.NamedTemporaryFile('wb') as f:
- f.write(b_('\n'.join(['if false', 'var = 3'])))
- f.flush()
- configuration.Config({})
-
- self.assertRaises(
- SyntaxError,
- configuration.conf_from_file,
- f.name
- )
-
- def test_config_with_non_package_relative_import(self):
- from pecan import configuration
- with tempfile.NamedTemporaryFile('wb', suffix='.py') as f:
- f.write(b_('\n'.join(['from . import variables'])))
- f.flush()
- configuration.Config({})
-
- try:
- configuration.conf_from_file(f.name)
- except (ValueError, SystemError) as e:
- assert 'relative import' in str(e)
- else:
- raise AssertionError(
- "A relative import-related error should have been raised"
- )
-
- def test_config_with_bad_import(self):
- from pecan import configuration
- path = ('bad', 'importerror.py')
- configuration.Config({})
-
- self.assertRaises(
- ImportError,
- configuration.conf_from_file,
- os.path.join(
- __here__,
- 'config_fixtures',
- *path
- )
- )
-
- def test_config_dir(self):
- from pecan import configuration
- if sys.version_info >= (2, 6):
- conf = configuration.Config({})
- self.assertEqual([], dir(conf))
- conf = configuration.Config({'a': 1})
- self.assertEqual(['a'], dir(conf))
-
- def test_config_bad_key(self):
- from pecan import configuration
- conf = configuration.Config({'a': 1})
- assert conf.a == 1
- self.assertRaises(AttributeError, getattr, conf, 'b')
-
- def test_config_get_valid_key(self):
- from pecan import configuration
- conf = configuration.Config({'a': 1})
- assert conf.get('a') == 1
-
- def test_config_get_invalid_key(self):
- from pecan import configuration
- conf = configuration.Config({'a': 1})
- assert conf.get('b') is None
-
- def test_config_get_invalid_key_return_default(self):
- from pecan import configuration
- conf = configuration.Config({'a': 1})
- assert conf.get('b', True) is True
-
- def test_config_to_dict(self):
- from pecan import configuration
- conf = configuration.initconf()
-
- assert isinstance(conf, configuration.Config)
-
- to_dict = conf.to_dict()
-
- assert isinstance(to_dict, dict)
- assert to_dict['server']['host'] == '0.0.0.0'
- assert to_dict['server']['port'] == '8080'
- assert to_dict['app']['modules'] == []
- assert to_dict['app']['root'] is None
- assert to_dict['app']['static_root'] == 'public'
- assert to_dict['app']['template_path'] == ''
-
- def test_config_to_dict_nested(self):
- from pecan import configuration
- """have more than one level nesting and convert to dict"""
- conf = configuration.initconf()
- nested = {'one': {'two': 2}}
- conf['nested'] = nested
-
- to_dict = conf.to_dict()
-
- assert isinstance(to_dict, dict)
- assert to_dict['server']['host'] == '0.0.0.0'
- assert to_dict['server']['port'] == '8080'
- assert to_dict['app']['modules'] == []
- assert to_dict['app']['root'] is None
- assert to_dict['app']['static_root'] == 'public'
- assert to_dict['app']['template_path'] == ''
- assert to_dict['nested']['one']['two'] == 2
-
- def test_config_to_dict_prefixed(self):
- from pecan import configuration
- """Add a prefix for keys"""
- conf = configuration.initconf()
-
- assert isinstance(conf, configuration.Config)
-
- to_dict = conf.to_dict('prefix_')
-
- assert isinstance(to_dict, dict)
- assert to_dict['prefix_server']['prefix_host'] == '0.0.0.0'
- assert to_dict['prefix_server']['prefix_port'] == '8080'
- assert to_dict['prefix_app']['prefix_modules'] == []
- assert to_dict['prefix_app']['prefix_root'] is None
- assert to_dict['prefix_app']['prefix_static_root'] == 'public'
- assert to_dict['prefix_app']['prefix_template_path'] == ''
-
-
-class TestGlobalConfig(PecanTestCase):
-
- def tearDown(self):
- from pecan import configuration
- configuration.set_config(
- dict(configuration.initconf()),
- overwrite=True
- )
-
- def test_paint_from_dict(self):
- from pecan import configuration
- configuration.set_config({'foo': 'bar'})
- assert dict(configuration._runtime_conf) != {'foo': 'bar'}
- self.assertEqual(configuration._runtime_conf.foo, 'bar')
-
- def test_overwrite_from_dict(self):
- from pecan import configuration
- configuration.set_config({'foo': 'bar'}, overwrite=True)
- assert dict(configuration._runtime_conf) == {'foo': 'bar'}
-
- def test_paint_from_file(self):
- from pecan import configuration
- configuration.set_config(os.path.join(
- __here__,
- 'config_fixtures/foobar.py'
- ))
- assert dict(configuration._runtime_conf) != {'foo': 'bar'}
- assert configuration._runtime_conf.foo == 'bar'
-
- def test_overwrite_from_file(self):
- from pecan import configuration
- configuration.set_config(
- os.path.join(
- __here__,
- 'config_fixtures/foobar.py',
- ),
- overwrite=True
- )
- assert dict(configuration._runtime_conf) == {'foo': 'bar'}
-
- def test_set_config_none_type(self):
- from pecan import configuration
- self.assertRaises(RuntimeError, configuration.set_config, None)
-
- def test_set_config_to_dir(self):
- from pecan import configuration
- self.assertRaises(RuntimeError, configuration.set_config, '/')
-
-
-class TestConfFromEnv(PecanTestCase):
- #
- # Note that there is a good chance of pollution if ``tearDown`` does not
- # reset the configuration like this class does. If implementing new classes
- # for configuration this tearDown **needs to be implemented**
- #
-
- def setUp(self):
- super(TestConfFromEnv, self).setUp()
- self.addCleanup(self._remove_config_key)
-
- from pecan import configuration
- self.get_conf_path_from_env = configuration.get_conf_path_from_env
-
- def _remove_config_key(self):
- os.environ.pop('PECAN_CONFIG', None)
-
- def test_invalid_path(self):
- os.environ['PECAN_CONFIG'] = '/'
- msg = "PECAN_CONFIG was set to an invalid path: /"
- self.assertRaisesRegexp(
- RuntimeError,
- msg,
- self.get_conf_path_from_env
- )
-
- def test_is_not_set(self):
- msg = "PECAN_CONFIG is not set and " \
- "no config file was passed as an argument."
- self.assertRaisesRegexp(
- RuntimeError,
- msg,
- self.get_conf_path_from_env
- )
-
- def test_return_valid_path(self):
- __here__ = os.path.abspath(__file__)
- os.environ['PECAN_CONFIG'] = __here__
- assert self.get_conf_path_from_env() == __here__
diff --git a/pecan/tests/test_generic.py b/pecan/tests/test_generic.py
deleted file mode 100644
index 0478799..0000000
--- a/pecan/tests/test_generic.py
+++ /dev/null
@@ -1,111 +0,0 @@
-from webtest import TestApp
-try:
- from simplejson import dumps
-except:
- from json import dumps # noqa
-
-from six import b as b_
-
-from pecan import Pecan, expose, abort
-from pecan.tests import PecanTestCase
-
-
-class TestGeneric(PecanTestCase):
-
- def test_simple_generic(self):
- class RootController(object):
- @expose(generic=True)
- def index(self):
- pass
-
- @index.when(method='POST', template='json')
- def do_post(self):
- return dict(result='POST')
-
- @index.when(method='GET')
- def do_get(self):
- return 'GET'
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_('GET')
-
- r = app.post('/')
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(result='POST')))
-
- r = app.get('/do_get', status=404)
- assert r.status_int == 404
-
- def test_generic_allow_header(self):
- class RootController(object):
- @expose(generic=True)
- def index(self):
- abort(405)
-
- @index.when(method='POST', template='json')
- def do_post(self):
- return dict(result='POST')
-
- @index.when(method='GET')
- def do_get(self):
- return 'GET'
-
- @index.when(method='PATCH')
- def do_patch(self):
- return 'PATCH'
-
- app = TestApp(Pecan(RootController()))
- r = app.delete('/', expect_errors=True)
- assert r.status_int == 405
- assert r.headers['Allow'] == 'GET, PATCH, POST'
-
- def test_nested_generic(self):
-
- class SubSubController(object):
- @expose(generic=True)
- def index(self):
- return 'GET'
-
- @index.when(method='DELETE', template='json')
- def do_delete(self, name, *args):
- return dict(result=name, args=', '.join(args))
-
- class SubController(object):
- sub = SubSubController()
-
- class RootController(object):
- sub = SubController()
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/sub/sub/')
- assert r.status_int == 200
- assert r.body == b_('GET')
-
- r = app.delete('/sub/sub/joe/is/cool')
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(result='joe', args='is, cool')))
-
-
-class TestGenericWithSpecialMethods(PecanTestCase):
-
- def test_generics_not_allowed(self):
-
- class C(object):
-
- def _default(self):
- pass
-
- def _lookup(self):
- pass
-
- def _route(self):
- pass
-
- for method in (C._default, C._lookup, C._route):
- self.assertRaises(
- ValueError,
- expose(generic=True),
- getattr(method, '__func__', method)
- )
diff --git a/pecan/tests/test_hooks.py b/pecan/tests/test_hooks.py
deleted file mode 100644
index a15368e..0000000
--- a/pecan/tests/test_hooks.py
+++ /dev/null
@@ -1,1711 +0,0 @@
-import inspect
-import operator
-
-from webtest import TestApp
-from six import PY3
-from six import b as b_
-from six import u as u_
-from six.moves import cStringIO as StringIO
-
-from pecan import make_app, expose, redirect, abort, rest, Request, Response
-from pecan.hooks import (
- PecanHook, TransactionHook, HookController, RequestViewerHook
-)
-from pecan.configuration import Config
-from pecan.decorators import transactional, after_commit, after_rollback
-from pecan.tests import PecanTestCase
-
-# The `inspect.Arguments` namedtuple is different between PY2/3
-kwargs = operator.attrgetter('varkw' if PY3 else 'keywords')
-
-
-class TestHooks(PecanTestCase):
-
- def test_basic_single_hook(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello, World!'
-
- class SimpleHook(PecanHook):
- def on_route(self, state):
- run_hook.append('on_route')
-
- def before(self, state):
- run_hook.append('before')
-
- def after(self, state):
- run_hook.append('after')
-
- def on_error(self, state, e):
- run_hook.append('error')
-
- app = TestApp(make_app(RootController(), hooks=[SimpleHook()]))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 4
- assert run_hook[0] == 'on_route'
- assert run_hook[1] == 'before'
- assert run_hook[2] == 'inside'
- assert run_hook[3] == 'after'
-
- def test_basic_multi_hook(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello, World!'
-
- class SimpleHook(PecanHook):
- def __init__(self, id):
- self.id = str(id)
-
- def on_route(self, state):
- run_hook.append('on_route' + self.id)
-
- def before(self, state):
- run_hook.append('before' + self.id)
-
- def after(self, state):
- run_hook.append('after' + self.id)
-
- def on_error(self, state, e):
- run_hook.append('error' + self.id)
-
- app = TestApp(make_app(RootController(), hooks=[
- SimpleHook(1), SimpleHook(2), SimpleHook(3)
- ]))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 10
- assert run_hook[0] == 'on_route1'
- assert run_hook[1] == 'on_route2'
- assert run_hook[2] == 'on_route3'
- assert run_hook[3] == 'before1'
- assert run_hook[4] == 'before2'
- assert run_hook[5] == 'before3'
- assert run_hook[6] == 'inside'
- assert run_hook[7] == 'after3'
- assert run_hook[8] == 'after2'
- assert run_hook[9] == 'after1'
-
- def test_partial_hooks(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello World!'
-
- @expose()
- def causeerror(self):
- return [][1]
-
- class ErrorHook(PecanHook):
- def on_error(self, state, e):
- run_hook.append('error')
-
- class OnRouteHook(PecanHook):
- def on_route(self, state):
- run_hook.append('on_route')
-
- app = TestApp(make_app(RootController(), hooks=[
- ErrorHook(), OnRouteHook()
- ]))
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello World!')
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'on_route'
- assert run_hook[1] == 'inside'
-
- run_hook = []
- try:
- response = app.get('/causeerror')
- except Exception as e:
- assert isinstance(e, IndexError)
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'on_route'
- assert run_hook[1] == 'error'
-
- def test_on_error_response_hook(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def causeerror(self):
- return [][1]
-
- class ErrorHook(PecanHook):
- def on_error(self, state, e):
- run_hook.append('error')
-
- r = Response()
- r.text = u_('on_error')
-
- return r
-
- app = TestApp(make_app(RootController(), hooks=[
- ErrorHook()
- ]))
-
- response = app.get('/causeerror')
-
- assert len(run_hook) == 1
- assert run_hook[0] == 'error'
- assert response.text == 'on_error'
-
- def test_prioritized_hooks(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello, World!'
-
- class SimpleHook(PecanHook):
- def __init__(self, id, priority=None):
- self.id = str(id)
- if priority:
- self.priority = priority
-
- def on_route(self, state):
- run_hook.append('on_route' + self.id)
-
- def before(self, state):
- run_hook.append('before' + self.id)
-
- def after(self, state):
- run_hook.append('after' + self.id)
-
- def on_error(self, state, e):
- run_hook.append('error' + self.id)
-
- papp = make_app(RootController(), hooks=[
- SimpleHook(1, 3), SimpleHook(2, 2), SimpleHook(3, 1)
- ])
- app = TestApp(papp)
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 10
- assert run_hook[0] == 'on_route3'
- assert run_hook[1] == 'on_route2'
- assert run_hook[2] == 'on_route1'
- assert run_hook[3] == 'before3'
- assert run_hook[4] == 'before2'
- assert run_hook[5] == 'before1'
- assert run_hook[6] == 'inside'
- assert run_hook[7] == 'after1'
- assert run_hook[8] == 'after2'
- assert run_hook[9] == 'after3'
-
- def test_basic_isolated_hook(self):
- run_hook = []
-
- class SimpleHook(PecanHook):
- def on_route(self, state):
- run_hook.append('on_route')
-
- def before(self, state):
- run_hook.append('before')
-
- def after(self, state):
- run_hook.append('after')
-
- def on_error(self, state, e):
- run_hook.append('error')
-
- class SubSubController(object):
- @expose()
- def index(self):
- run_hook.append('inside_sub_sub')
- return 'Deep inside here!'
-
- class SubController(HookController):
- __hooks__ = [SimpleHook()]
-
- @expose()
- def index(self):
- run_hook.append('inside_sub')
- return 'Inside here!'
-
- sub = SubSubController()
-
- class RootController(object):
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello, World!'
-
- sub = SubController()
-
- app = TestApp(make_app(RootController()))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 1
- assert run_hook[0] == 'inside'
-
- run_hook = []
-
- response = app.get('/sub/')
- assert response.status_int == 200
- assert response.body == b_('Inside here!')
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'before'
- assert run_hook[1] == 'inside_sub'
- assert run_hook[2] == 'after'
-
- run_hook = []
- response = app.get('/sub/sub/')
- assert response.status_int == 200
- assert response.body == b_('Deep inside here!')
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'before'
- assert run_hook[1] == 'inside_sub_sub'
- assert run_hook[2] == 'after'
-
- def test_isolated_hook_with_global_hook(self):
- run_hook = []
-
- class SimpleHook(PecanHook):
- def __init__(self, id):
- self.id = str(id)
-
- def on_route(self, state):
- run_hook.append('on_route' + self.id)
-
- def before(self, state):
- run_hook.append('before' + self.id)
-
- def after(self, state):
- run_hook.append('after' + self.id)
-
- def on_error(self, state, e):
- run_hook.append('error' + self.id)
-
- class SubController(HookController):
- __hooks__ = [SimpleHook(2)]
-
- @expose()
- def index(self):
- run_hook.append('inside_sub')
- return 'Inside here!'
-
- class RootController(object):
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello, World!'
-
- sub = SubController()
-
- app = TestApp(make_app(RootController(), hooks=[SimpleHook(1)]))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 4
- assert run_hook[0] == 'on_route1'
- assert run_hook[1] == 'before1'
- assert run_hook[2] == 'inside'
- assert run_hook[3] == 'after1'
-
- run_hook = []
-
- response = app.get('/sub/')
- assert response.status_int == 200
- assert response.body == b_('Inside here!')
-
- assert len(run_hook) == 6
- assert run_hook[0] == 'on_route1'
- assert run_hook[1] == 'before2'
- assert run_hook[2] == 'before1'
- assert run_hook[3] == 'inside_sub'
- assert run_hook[4] == 'after1'
- assert run_hook[5] == 'after2'
-
- def test_mixin_hooks(self):
- run_hook = []
-
- class HelperHook(PecanHook):
- priority = 2
-
- def before(self, state):
- run_hook.append('helper - before hook')
-
- # we'll use the same hook instance to avoid duplicate calls
- helper_hook = HelperHook()
-
- class LastHook(PecanHook):
- priority = 200
-
- def before(self, state):
- run_hook.append('last - before hook')
-
- class SimpleHook(PecanHook):
- priority = 1
-
- def before(self, state):
- run_hook.append('simple - before hook')
-
- class HelperMixin(object):
- __hooks__ = [helper_hook]
-
- class LastMixin(object):
- __hooks__ = [LastHook()]
-
- class SubController(HookController, HelperMixin):
- __hooks__ = [LastHook()]
-
- @expose()
- def index(self):
- return "This is sub controller!"
-
- class RootController(HookController, LastMixin):
- __hooks__ = [SimpleHook(), helper_hook]
-
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello, World!'
-
- sub = SubController()
-
- papp = make_app(RootController())
- app = TestApp(papp)
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 4
- assert run_hook[0] == 'simple - before hook', run_hook[0]
- assert run_hook[1] == 'helper - before hook', run_hook[1]
- assert run_hook[2] == 'last - before hook', run_hook[2]
- assert run_hook[3] == 'inside', run_hook[3]
-
- run_hook = []
- response = app.get('/sub/')
- assert response.status_int == 200
- assert response.body == b_('This is sub controller!')
-
- assert len(run_hook) == 4, run_hook
- assert run_hook[0] == 'simple - before hook', run_hook[0]
- assert run_hook[1] == 'helper - before hook', run_hook[1]
- assert run_hook[2] == 'last - before hook', run_hook[2]
- # LastHook is invoked once again -
- # for each different instance of the Hook in the two Controllers
- assert run_hook[3] == 'last - before hook', run_hook[3]
-
-
-class TestStateAccess(PecanTestCase):
-
- def setUp(self):
- super(TestStateAccess, self).setUp()
- self.args = None
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- @expose()
- def greet(self, name):
- return 'Hello, %s!' % name
-
- @expose()
- def greetmore(self, *args):
- return 'Hello, %s!' % args[0]
-
- @expose()
- def kwargs(self, **kw):
- return 'Hello, %s!' % kw['name']
-
- @expose()
- def mixed(self, first, second, *args):
- return 'Mixed'
-
- class SimpleHook(PecanHook):
- def before(inself, state):
- self.args = (state.controller, state.arguments)
-
- self.root = RootController()
- self.app = TestApp(make_app(self.root, hooks=[SimpleHook()]))
-
- def test_no_args(self):
- self.app.get('/')
- assert self.args[0] == self.root.index
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_single_arg(self):
- self.app.get('/greet/joe')
- assert self.args[0] == self.root.greet
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['joe']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_single_vararg(self):
- self.app.get('/greetmore/joe')
- assert self.args[0] == self.root.greetmore
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == ['joe']
- assert kwargs(self.args[1]) == {}
-
- def test_single_kw(self):
- self.app.get('/kwargs/?name=joe')
- assert self.args[0] == self.root.kwargs
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'name': 'joe'}
-
- def test_single_kw_post(self):
- self.app.post('/kwargs/', params={'name': 'joe'})
- assert self.args[0] == self.root.kwargs
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'name': 'joe'}
-
- def test_mixed_args(self):
- self.app.get('/mixed/foo/bar/spam/eggs')
- assert self.args[0] == self.root.mixed
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['foo', 'bar']
- assert self.args[1].varargs == ['spam', 'eggs']
-
-
-class TestStateAccessWithoutThreadLocals(PecanTestCase):
-
- def setUp(self):
- super(TestStateAccessWithoutThreadLocals, self).setUp()
- self.args = None
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- return 'Hello, World!'
-
- @expose()
- def greet(self, req, resp, name):
- return 'Hello, %s!' % name
-
- @expose()
- def greetmore(self, req, resp, *args):
- return 'Hello, %s!' % args[0]
-
- @expose()
- def kwargs(self, req, resp, **kw):
- return 'Hello, %s!' % kw['name']
-
- @expose()
- def mixed(self, req, resp, first, second, *args):
- return 'Mixed'
-
- class SimpleHook(PecanHook):
- def before(inself, state):
- self.args = (state.controller, state.arguments)
-
- self.root = RootController()
- self.app = TestApp(make_app(
- self.root,
- hooks=[SimpleHook()],
- use_context_locals=False
- ))
-
- def test_no_args(self):
- self.app.get('/')
- assert self.args[0] == self.root.index
- assert isinstance(self.args[1], inspect.Arguments)
- assert len(self.args[1].args) == 2
- assert isinstance(self.args[1].args[0], Request)
- assert isinstance(self.args[1].args[1], Response)
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_single_arg(self):
- self.app.get('/greet/joe')
- assert self.args[0] == self.root.greet
- assert isinstance(self.args[1], inspect.Arguments)
- assert len(self.args[1].args) == 3
- assert isinstance(self.args[1].args[0], Request)
- assert isinstance(self.args[1].args[1], Response)
- assert self.args[1].args[2] == 'joe'
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_single_vararg(self):
- self.app.get('/greetmore/joe')
- assert self.args[0] == self.root.greetmore
- assert isinstance(self.args[1], inspect.Arguments)
- assert len(self.args[1].args) == 2
- assert isinstance(self.args[1].args[0], Request)
- assert isinstance(self.args[1].args[1], Response)
- assert self.args[1].varargs == ['joe']
- assert kwargs(self.args[1]) == {}
-
- def test_single_kw(self):
- self.app.get('/kwargs/?name=joe')
- assert self.args[0] == self.root.kwargs
- assert isinstance(self.args[1], inspect.Arguments)
- assert len(self.args[1].args) == 2
- assert isinstance(self.args[1].args[0], Request)
- assert isinstance(self.args[1].args[1], Response)
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'name': 'joe'}
-
- def test_single_kw_post(self):
- self.app.post('/kwargs/', params={'name': 'joe'})
- assert self.args[0] == self.root.kwargs
- assert isinstance(self.args[1], inspect.Arguments)
- assert len(self.args[1].args) == 2
- assert isinstance(self.args[1].args[0], Request)
- assert isinstance(self.args[1].args[1], Response)
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'name': 'joe'}
-
- def test_mixed_args(self):
- self.app.get('/mixed/foo/bar/spam/eggs')
- assert self.args[0] == self.root.mixed
- assert isinstance(self.args[1], inspect.Arguments)
- assert len(self.args[1].args) == 4
- assert isinstance(self.args[1].args[0], Request)
- assert isinstance(self.args[1].args[1], Response)
- assert self.args[1].args[2:] == ['foo', 'bar']
- assert self.args[1].varargs == ['spam', 'eggs']
-
-
-class TestRestControllerStateAccess(PecanTestCase):
-
- def setUp(self):
- super(TestRestControllerStateAccess, self).setUp()
- self.args = None
-
- class RootController(rest.RestController):
-
- @expose()
- def _default(self, _id, *args, **kw):
- return 'Default'
-
- @expose()
- def get_all(self, **kw):
- return 'All'
-
- @expose()
- def get_one(self, _id, *args, **kw):
- return 'One'
-
- @expose()
- def post(self, *args, **kw):
- return 'POST'
-
- @expose()
- def put(self, _id, *args, **kw):
- return 'PUT'
-
- @expose()
- def delete(self, _id, *args, **kw):
- return 'DELETE'
-
- class SimpleHook(PecanHook):
- def before(inself, state):
- self.args = (state.controller, state.arguments)
-
- self.root = RootController()
- self.app = TestApp(make_app(self.root, hooks=[SimpleHook()]))
-
- def test_get_all(self):
- self.app.get('/')
- assert self.args[0] == self.root.get_all
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_get_all_with_kwargs(self):
- self.app.get('/?foo=bar')
- assert self.args[0] == self.root.get_all
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'foo': 'bar'}
-
- def test_get_one(self):
- self.app.get('/1')
- assert self.args[0] == self.root.get_one
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_get_one_with_varargs(self):
- self.app.get('/1/2/3')
- assert self.args[0] == self.root.get_one
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == ['2', '3']
- assert kwargs(self.args[1]) == {}
-
- def test_get_one_with_kwargs(self):
- self.app.get('/1?foo=bar')
- assert self.args[0] == self.root.get_one
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'foo': 'bar'}
-
- def test_post(self):
- self.app.post('/')
- assert self.args[0] == self.root.post
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_post_with_varargs(self):
- self.app.post('/foo/bar')
- assert self.args[0] == self.root.post
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == ['foo', 'bar']
- assert kwargs(self.args[1]) == {}
-
- def test_post_with_kwargs(self):
- self.app.post('/', params={'foo': 'bar'})
- assert self.args[0] == self.root.post
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'foo': 'bar'}
-
- def test_put(self):
- self.app.put('/1')
- assert self.args[0] == self.root.put
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_put_with_method_argument(self):
- self.app.post('/1?_method=put')
- assert self.args[0] == self.root.put
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'_method': 'put'}
-
- def test_put_with_varargs(self):
- self.app.put('/1/2/3')
- assert self.args[0] == self.root.put
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == ['2', '3']
- assert kwargs(self.args[1]) == {}
-
- def test_put_with_kwargs(self):
- self.app.put('/1?foo=bar')
- assert self.args[0] == self.root.put
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'foo': 'bar'}
-
- def test_delete(self):
- self.app.delete('/1')
- assert self.args[0] == self.root.delete
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_delete_with_method_argument(self):
- self.app.post('/1?_method=delete')
- assert self.args[0] == self.root.delete
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'_method': 'delete'}
-
- def test_delete_with_varargs(self):
- self.app.delete('/1/2/3')
- assert self.args[0] == self.root.delete
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == ['2', '3']
- assert kwargs(self.args[1]) == {}
-
- def test_delete_with_kwargs(self):
- self.app.delete('/1?foo=bar')
- assert self.args[0] == self.root.delete
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'foo': 'bar'}
-
- def test_post_with_invalid_method_kwarg(self):
- self.app.post('/1?_method=invalid')
- assert self.args[0] == self.root._default
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'_method': 'invalid'}
-
-
-class TestTransactionHook(PecanTestCase):
- def test_transaction_hook(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello, World!'
-
- @expose()
- def redirect(self):
- redirect('/')
-
- @expose()
- def error(self):
- return [][1]
-
- def gen(event):
- return lambda: run_hook.append(event)
-
- app = TestApp(make_app(RootController(), hooks=[
- TransactionHook(
- start=gen('start'),
- start_ro=gen('start_ro'),
- commit=gen('commit'),
- rollback=gen('rollback'),
- clear=gen('clear')
- )
- ]))
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'inside'
- assert run_hook[2] == 'clear'
-
- run_hook = []
-
- response = app.post('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 4
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'inside'
- assert run_hook[2] == 'commit'
- assert run_hook[3] == 'clear'
-
- #
- # test hooks for GET /redirect
- # This controller should always be non-transactional
- #
-
- run_hook = []
-
- response = app.get('/redirect')
- assert response.status_int == 302
- assert len(run_hook) == 2
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
-
- #
- # test hooks for POST /redirect
- # This controller should always be transactional,
- # even in the case of redirects
- #
-
- run_hook = []
-
- response = app.post('/redirect')
- assert response.status_int == 302
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'commit'
- assert run_hook[2] == 'clear'
-
- run_hook = []
- try:
- response = app.post('/error')
- except IndexError:
- pass
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'rollback'
- assert run_hook[2] == 'clear'
-
- def test_transaction_hook_with_after_actions(self):
- run_hook = []
-
- def action(name):
- def action_impl():
- run_hook.append(name)
- return action_impl
-
- class RootController(object):
- @expose()
- @after_commit(action('action-one'))
- def index(self):
- run_hook.append('inside')
- return 'Index Method!'
-
- @expose()
- @transactional()
- @after_commit(action('action-two'))
- def decorated(self):
- run_hook.append('inside')
- return 'Decorated Method!'
-
- @expose()
- @after_rollback(action('action-three'))
- def rollback(self):
- abort(500)
-
- @expose()
- @transactional()
- @after_rollback(action('action-four'))
- def rollback_decorated(self):
- abort(500)
-
- def gen(event):
- return lambda: run_hook.append(event)
-
- app = TestApp(make_app(RootController(), hooks=[
- TransactionHook(
- start=gen('start'),
- start_ro=gen('start_ro'),
- commit=gen('commit'),
- rollback=gen('rollback'),
- clear=gen('clear')
- )
- ]))
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Index Method!')
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'inside'
- assert run_hook[2] == 'clear'
-
- run_hook = []
-
- response = app.post('/')
- assert response.status_int == 200
- assert response.body == b_('Index Method!')
-
- assert len(run_hook) == 5
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'inside'
- assert run_hook[2] == 'commit'
- assert run_hook[3] == 'action-one'
- assert run_hook[4] == 'clear'
-
- run_hook = []
-
- response = app.get('/decorated')
- assert response.status_int == 200
- assert response.body == b_('Decorated Method!')
-
- assert len(run_hook) == 7
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'inside'
- assert run_hook[4] == 'commit'
- assert run_hook[5] == 'action-two'
- assert run_hook[6] == 'clear'
-
- run_hook = []
-
- response = app.get('/rollback', expect_errors=True)
- assert response.status_int == 500
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
-
- run_hook = []
-
- response = app.post('/rollback', expect_errors=True)
- assert response.status_int == 500
-
- assert len(run_hook) == 4
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'rollback'
- assert run_hook[2] == 'action-three'
- assert run_hook[3] == 'clear'
-
- run_hook = []
-
- response = app.get('/rollback_decorated', expect_errors=True)
- assert response.status_int == 500
-
- assert len(run_hook) == 6
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'rollback'
- assert run_hook[4] == 'action-four'
- assert run_hook[5] == 'clear'
-
- run_hook = []
-
- response = app.get('/fourohfour', status=404)
- assert response.status_int == 404
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
-
- def test_transaction_hook_with_transactional_decorator(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello, World!'
-
- @expose()
- def redirect(self):
- redirect('/')
-
- @expose()
- @transactional()
- def redirect_transactional(self):
- redirect('/')
-
- @expose()
- @transactional(False)
- def redirect_rollback(self):
- redirect('/')
-
- @expose()
- def error(self):
- return [][1]
-
- @expose()
- @transactional(False)
- def error_rollback(self):
- return [][1]
-
- @expose()
- @transactional()
- def error_transactional(self):
- return [][1]
-
- def gen(event):
- return lambda: run_hook.append(event)
-
- app = TestApp(make_app(RootController(), hooks=[
- TransactionHook(
- start=gen('start'),
- start_ro=gen('start_ro'),
- commit=gen('commit'),
- rollback=gen('rollback'),
- clear=gen('clear')
- )
- ]))
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'inside'
- assert run_hook[2] == 'clear'
-
- run_hook = []
-
- # test hooks for /
-
- response = app.post('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 4
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'inside'
- assert run_hook[2] == 'commit'
- assert run_hook[3] == 'clear'
-
- #
- # test hooks for GET /redirect
- # This controller should always be non-transactional
- #
-
- run_hook = []
-
- response = app.get('/redirect')
- assert response.status_int == 302
- assert len(run_hook) == 2
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
-
- #
- # test hooks for POST /redirect
- # This controller should always be transactional,
- # even in the case of redirects
- #
-
- run_hook = []
-
- response = app.post('/redirect')
- assert response.status_int == 302
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'commit'
- assert run_hook[2] == 'clear'
-
- #
- # test hooks for GET /redirect_transactional
- # This controller should always be transactional,
- # even in the case of redirects
- #
-
- run_hook = []
-
- response = app.get('/redirect_transactional')
- assert response.status_int == 302
- assert len(run_hook) == 5
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'commit'
- assert run_hook[4] == 'clear'
-
- #
- # test hooks for POST /redirect_transactional
- # This controller should always be transactional,
- # even in the case of redirects
- #
-
- run_hook = []
-
- response = app.post('/redirect_transactional')
- assert response.status_int == 302
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'commit'
- assert run_hook[2] == 'clear'
-
- #
- # test hooks for GET /redirect_rollback
- # This controller should always be transactional,
- # *except* in the case of redirects
- #
- run_hook = []
-
- response = app.get('/redirect_rollback')
- assert response.status_int == 302
- assert len(run_hook) == 5
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'rollback'
- assert run_hook[4] == 'clear'
-
- #
- # test hooks for POST /redirect_rollback
- # This controller should always be transactional,
- # *except* in the case of redirects
- #
-
- run_hook = []
-
- response = app.post('/redirect_rollback')
- assert response.status_int == 302
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'rollback'
- assert run_hook[2] == 'clear'
-
- #
- # Exceptions (other than HTTPFound) should *always*
- # rollback no matter what
- #
- run_hook = []
-
- try:
- response = app.post('/error')
- except IndexError:
- pass
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'rollback'
- assert run_hook[2] == 'clear'
-
- run_hook = []
-
- try:
- response = app.get('/error')
- except IndexError:
- pass
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
-
- run_hook = []
-
- try:
- response = app.post('/error_transactional')
- except IndexError:
- pass
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'rollback'
- assert run_hook[2] == 'clear'
-
- run_hook = []
-
- try:
- response = app.get('/error_transactional')
- except IndexError:
- pass
-
- assert len(run_hook) == 5
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'rollback'
- assert run_hook[4] == 'clear'
-
- run_hook = []
-
- try:
- response = app.post('/error_rollback')
- except IndexError:
- pass
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'rollback'
- assert run_hook[2] == 'clear'
-
- run_hook = []
-
- try:
- response = app.get('/error_rollback')
- except IndexError:
- pass
-
- assert len(run_hook) == 5
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'rollback'
- assert run_hook[4] == 'clear'
-
- def test_transaction_hook_with_transactional_class_decorator(self):
- run_hook = []
-
- @transactional()
- class RootController(object):
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello, World!'
-
- @expose()
- def redirect(self):
- redirect('/')
-
- @expose()
- @transactional(False)
- def redirect_rollback(self):
- redirect('/')
-
- @expose()
- def error(self):
- return [][1]
-
- @expose(generic=True)
- def generic(self):
- pass
-
- @generic.when(method='GET')
- def generic_get(self):
- run_hook.append('inside')
- return 'generic get'
-
- @generic.when(method='POST')
- def generic_post(self):
- run_hook.append('inside')
- return 'generic post'
-
- def gen(event):
- return lambda: run_hook.append(event)
-
- app = TestApp(make_app(RootController(), hooks=[
- TransactionHook(
- start=gen('start'),
- start_ro=gen('start_ro'),
- commit=gen('commit'),
- rollback=gen('rollback'),
- clear=gen('clear')
- )
- ]))
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 6
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'inside'
- assert run_hook[4] == 'commit'
- assert run_hook[5] == 'clear'
-
- run_hook = []
-
- # test hooks for /
-
- response = app.post('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 4
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'inside'
- assert run_hook[2] == 'commit'
- assert run_hook[3] == 'clear'
-
- #
- # test hooks for GET /redirect
- # This controller should always be transactional,
- # even in the case of redirects
- #
-
- run_hook = []
- response = app.get('/redirect')
- assert response.status_int == 302
- assert len(run_hook) == 5
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'commit'
- assert run_hook[4] == 'clear'
-
- #
- # test hooks for POST /redirect
- # This controller should always be transactional,
- # even in the case of redirects
- #
-
- run_hook = []
-
- response = app.post('/redirect')
- assert response.status_int == 302
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'commit'
- assert run_hook[2] == 'clear'
-
- #
- # test hooks for GET /redirect_rollback
- # This controller should always be transactional,
- # *except* in the case of redirects
- #
- run_hook = []
-
- response = app.get('/redirect_rollback')
- assert response.status_int == 302
- assert len(run_hook) == 5
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'rollback'
- assert run_hook[4] == 'clear'
-
- #
- # test hooks for POST /redirect_rollback
- # This controller should always be transactional,
- # *except* in the case of redirects
- #
-
- run_hook = []
-
- response = app.post('/redirect_rollback')
- assert response.status_int == 302
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'rollback'
- assert run_hook[2] == 'clear'
-
- #
- # Exceptions (other than HTTPFound) should *always*
- # rollback no matter what
- #
- run_hook = []
-
- try:
- response = app.post('/error')
- except IndexError:
- pass
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'rollback'
- assert run_hook[2] == 'clear'
-
- run_hook = []
-
- try:
- response = app.get('/error')
- except IndexError:
- pass
-
- assert len(run_hook) == 5
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'rollback'
- assert run_hook[4] == 'clear'
-
- #
- # test hooks for GET /generic
- # This controller should always be transactional,
- #
-
- run_hook = []
-
- response = app.get('/generic')
- assert response.status_int == 200
- assert response.body == b_('generic get')
- assert len(run_hook) == 6
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'inside'
- assert run_hook[4] == 'commit'
- assert run_hook[5] == 'clear'
-
- #
- # test hooks for POST /generic
- # This controller should always be transactional,
- #
-
- run_hook = []
-
- response = app.post('/generic')
- assert response.status_int == 200
- assert response.body == b_('generic post')
- assert len(run_hook) == 4
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'inside'
- assert run_hook[2] == 'commit'
- assert run_hook[3] == 'clear'
-
- def test_transaction_hook_with_broken_hook(self):
- """
- In a scenario where a preceding hook throws an exception,
- ensure that TransactionHook still rolls back properly.
- """
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- def gen(event):
- return lambda: run_hook.append(event)
-
- class MyCustomException(Exception):
- pass
-
- class MyHook(PecanHook):
-
- def on_route(self, state):
- raise MyCustomException('BROKEN!')
-
- app = TestApp(make_app(RootController(), hooks=[
- MyHook(),
- TransactionHook(
- start=gen('start'),
- start_ro=gen('start_ro'),
- commit=gen('commit'),
- rollback=gen('rollback'),
- clear=gen('clear')
- )
- ]))
-
- self.assertRaises(
- MyCustomException,
- app.get,
- '/'
- )
-
- assert len(run_hook) == 1
- assert run_hook[0] == 'clear'
-
-
-class TestRequestViewerHook(PecanTestCase):
-
- def test_basic_single_default_hook(self):
-
- _stdout = StringIO()
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- app = TestApp(
- make_app(
- RootController(), hooks=lambda: [
- RequestViewerHook(writer=_stdout)
- ]
- )
- )
- response = app.get('/')
-
- out = _stdout.getvalue()
-
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
- assert 'path' in out
- assert 'method' in out
- assert 'status' in out
- assert 'method' in out
- assert 'params' in out
- assert 'hooks' in out
- assert '200 OK' in out
- assert "['RequestViewerHook']" in out
- assert '/' in out
-
- def test_bad_response_from_app(self):
- """When exceptions are raised the hook deals with them properly"""
-
- _stdout = StringIO()
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- app = TestApp(
- make_app(
- RootController(), hooks=lambda: [
- RequestViewerHook(writer=_stdout)
- ]
- )
- )
- response = app.get('/404', expect_errors=True)
-
- out = _stdout.getvalue()
-
- assert response.status_int == 404
- assert 'path' in out
- assert 'method' in out
- assert 'status' in out
- assert 'method' in out
- assert 'params' in out
- assert 'hooks' in out
- assert '404 Not Found' in out
- assert "['RequestViewerHook']" in out
- assert '/' in out
-
- def test_single_item(self):
-
- _stdout = StringIO()
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- app = TestApp(
- make_app(
- RootController(),
- hooks=lambda: [
- RequestViewerHook(
- config={'items': ['path']}, writer=_stdout
- )
- ]
- )
- )
- response = app.get('/')
-
- out = _stdout.getvalue()
-
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
- assert '/' in out
- assert 'path' in out
- assert 'method' not in out
- assert 'status' not in out
- assert 'method' not in out
- assert 'params' not in out
- assert 'hooks' not in out
- assert '200 OK' not in out
- assert "['RequestViewerHook']" not in out
-
- def test_single_blacklist_item(self):
-
- _stdout = StringIO()
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- app = TestApp(
- make_app(
- RootController(),
- hooks=lambda: [
- RequestViewerHook(
- config={'blacklist': ['/']}, writer=_stdout
- )
- ]
- )
- )
- response = app.get('/')
-
- out = _stdout.getvalue()
-
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
- assert out == ''
-
- def test_item_not_in_defaults(self):
-
- _stdout = StringIO()
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- app = TestApp(
- make_app(
- RootController(),
- hooks=lambda: [
- RequestViewerHook(
- config={'items': ['date']}, writer=_stdout
- )
- ]
- )
- )
- response = app.get('/')
-
- out = _stdout.getvalue()
-
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
- assert 'date' in out
- assert 'method' not in out
- assert 'status' not in out
- assert 'method' not in out
- assert 'params' not in out
- assert 'hooks' not in out
- assert '200 OK' not in out
- assert "['RequestViewerHook']" not in out
- assert '/' not in out
-
- def test_hook_formatting(self):
- hooks = ['<pecan.hooks.RequestViewerHook object at 0x103a5f910>']
- viewer = RequestViewerHook()
- formatted = viewer.format_hooks(hooks)
-
- assert formatted == ['RequestViewerHook']
-
- def test_deal_with_pecan_configs(self):
- """If config comes from pecan.conf convert it to dict"""
- conf = Config(conf_dict={'items': ['url']})
- viewer = RequestViewerHook(conf)
-
- assert viewer.items == ['url']
-
-
-class TestRestControllerWithHooks(PecanTestCase):
-
- def test_restcontroller_with_hooks(self):
-
- class SomeHook(PecanHook):
-
- def before(self, state):
- state.response.headers['X-Testing'] = 'XYZ'
-
- class BaseController(rest.RestController):
-
- @expose()
- def delete(self, _id):
- return 'Deleting %s' % _id
-
- class RootController(BaseController, HookController):
-
- __hooks__ = [SomeHook()]
-
- @expose()
- def get_all(self):
- return 'Hello, World!'
-
- @staticmethod
- def static(cls):
- return 'static'
-
- @property
- def foo(self):
- return 'bar'
-
- def testing123(self):
- return 'bar'
-
- unhashable = [1, 'two', 3]
-
- app = TestApp(
- make_app(
- RootController()
- )
- )
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
- assert response.headers['X-Testing'] == 'XYZ'
-
- response = app.delete('/100/')
- assert response.status_int == 200
- assert response.body == b_('Deleting 100')
- assert response.headers['X-Testing'] == 'XYZ'
diff --git a/pecan/tests/test_jsonify.py b/pecan/tests/test_jsonify.py
deleted file mode 100644
index 2dcd663..0000000
--- a/pecan/tests/test_jsonify.py
+++ /dev/null
@@ -1,233 +0,0 @@
-from datetime import datetime, date
-from decimal import Decimal
-try:
- from simplejson import loads
-except:
- from json import loads # noqa
-try:
- from sqlalchemy import orm, schema, types
- from sqlalchemy.engine import create_engine
-except ImportError:
- create_engine = None # noqa
-
-from webtest import TestApp
-from webob.multidict import MultiDict
-
-from pecan.jsonify import jsonify, encode, ResultProxy, RowProxy
-from pecan import Pecan, expose
-from pecan.tests import PecanTestCase
-
-
-def make_person():
- class Person(object):
- def __init__(self, first_name, last_name):
- self.first_name = first_name
- self.last_name = last_name
-
- @property
- def name(self):
- return '%s %s' % (self.first_name, self.last_name)
- return Person
-
-
-def test_simple_rule():
- Person = make_person()
-
- # create a Person instance
- p = Person('Jonathan', 'LaCour')
-
- # register a generic JSON rule
- @jsonify.when_type(Person)
- def jsonify_person(obj):
- return dict(
- name=obj.name
- )
-
- # encode the object using our new rule
- result = loads(encode(p))
- assert result['name'] == 'Jonathan LaCour'
- assert len(result) == 1
-
-
-class TestJsonify(PecanTestCase):
-
- def test_simple_jsonify(self):
- Person = make_person()
-
- # register a generic JSON rule
- @jsonify.when_type(Person)
- def jsonify_person(obj):
- return dict(
- name=obj.name
- )
-
- class RootController(object):
- @expose('json')
- def index(self):
- # create a Person instance
- p = Person('Jonathan', 'LaCour')
- return p
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/')
- assert r.status_int == 200
- assert loads(r.body.decode()) == {'name': 'Jonathan LaCour'}
-
-
-class TestJsonifyGenericEncoder(PecanTestCase):
- def test_json_callable(self):
- class JsonCallable(object):
- def __init__(self, arg):
- self.arg = arg
-
- def __json__(self):
- return {"arg": self.arg}
-
- result = encode(JsonCallable('foo'))
- assert loads(result) == {'arg': 'foo'}
-
- def test_datetime(self):
- today = date.today()
- now = datetime.now()
-
- result = encode(today)
- assert loads(result) == str(today)
-
- result = encode(now)
- assert loads(result) == str(now)
-
- def test_decimal(self):
- # XXX Testing for float match which is inexact
-
- d = Decimal('1.1')
- result = encode(d)
- assert loads(result) == float(d)
-
- def test_multidict(self):
- md = MultiDict()
- md.add('arg', 'foo')
- md.add('arg', 'bar')
- result = encode(md)
- assert loads(result) == {'arg': ['foo', 'bar']}
-
- def test_fallback_to_builtin_encoder(self):
- class Foo(object):
- pass
-
- self.assertRaises(TypeError, encode, Foo())
-
-
-class TestJsonifySQLAlchemyGenericEncoder(PecanTestCase):
-
- def setUp(self):
- super(TestJsonifySQLAlchemyGenericEncoder, self).setUp()
- if not create_engine:
- self.create_fake_proxies()
- else:
- self.create_sa_proxies()
-
- def create_fake_proxies(self):
-
- # create a fake SA object
- class FakeSAObject(object):
- def __init__(self):
- self._sa_class_manager = object()
- self._sa_instance_state = 'awesome'
- self.id = 1
- self.first_name = 'Jonathan'
- self.last_name = 'LaCour'
-
- # create a fake result proxy
- class FakeResultProxy(ResultProxy):
- def __init__(self):
- self.rowcount = -1
- self.rows = []
-
- def __iter__(self):
- return iter(self.rows)
-
- def append(self, row):
- self.rows.append(row)
-
- # create a fake row proxy
- class FakeRowProxy(RowProxy):
- def __init__(self, arg=None):
- self.row = dict(arg)
-
- def __getitem__(self, key):
- return self.row.__getitem__(key)
-
- def keys(self):
- return self.row.keys()
-
- # get the SA objects
- self.sa_object = FakeSAObject()
- self.result_proxy = FakeResultProxy()
- self.result_proxy.append(
- FakeRowProxy([
- ('id', 1),
- ('first_name', 'Jonathan'),
- ('last_name', 'LaCour')
- ])
- )
- self.result_proxy.append(
- FakeRowProxy([
- ('id', 2), ('first_name', 'Yoann'), ('last_name', 'Roman')
- ]))
- self.row_proxy = FakeRowProxy([
- ('id', 1), ('first_name', 'Jonathan'), ('last_name', 'LaCour')
- ])
-
- def create_sa_proxies(self):
-
- # create the table and mapper
- metadata = schema.MetaData()
- user_table = schema.Table(
- 'user',
- metadata,
- schema.Column('id', types.Integer, primary_key=True),
- schema.Column('first_name', types.Unicode(25)),
- schema.Column('last_name', types.Unicode(25))
- )
-
- class User(object):
- pass
- orm.mapper(User, user_table)
-
- # create the session
- engine = create_engine('sqlite:///:memory:')
- metadata.bind = engine
- metadata.create_all()
- session = orm.sessionmaker(bind=engine)()
-
- # add some dummy data
- user_table.insert().execute([
- {'first_name': 'Jonathan', 'last_name': 'LaCour'},
- {'first_name': 'Yoann', 'last_name': 'Roman'}
- ])
-
- # get the SA objects
- self.sa_object = session.query(User).first()
- select = user_table.select()
- self.result_proxy = select.execute()
- self.row_proxy = select.execute().fetchone()
-
- def test_sa_object(self):
- result = encode(self.sa_object)
- assert loads(result) == {
- 'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour'
- }
-
- def test_result_proxy(self):
- result = encode(self.result_proxy)
- assert loads(result) == {'count': 2, 'rows': [
- {'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour'},
- {'id': 2, 'first_name': 'Yoann', 'last_name': 'Roman'}
- ]}
-
- def test_row_proxy(self):
- result = encode(self.row_proxy)
- assert loads(result) == {
- 'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour'
- }
diff --git a/pecan/tests/test_no_thread_locals.py b/pecan/tests/test_no_thread_locals.py
deleted file mode 100644
index 1ca418e..0000000
--- a/pecan/tests/test_no_thread_locals.py
+++ /dev/null
@@ -1,1440 +0,0 @@
-import time
-from json import dumps, loads
-import warnings
-
-from webtest import TestApp
-from six import b as b_
-from six import u as u_
-import webob
-import mock
-
-from pecan import Pecan, expose, abort, Request, Response
-from pecan.rest import RestController
-from pecan.hooks import PecanHook, HookController
-from pecan.tests import PecanTestCase
-
-
-class TestThreadingLocalUsage(PecanTestCase):
-
- @property
- def root(self):
- class RootController(object):
- @expose()
- def index(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return 'Hello, World!'
-
- @expose()
- def warning(self):
- return ("This should be unroutable because (req, resp) are not"
- " arguments. It should raise a TypeError.")
-
- @expose(generic=True)
- def generic(self):
- return ("This should be unroutable because (req, resp) are not"
- " arguments. It should raise a TypeError.")
-
- @generic.when(method='PUT')
- def generic_put(self, _id):
- return ("This should be unroutable because (req, resp) are not"
- " arguments. It should raise a TypeError.")
-
- return RootController
-
- def test_locals_are_not_used(self):
- with mock.patch('threading.local', side_effect=AssertionError()):
-
- app = TestApp(Pecan(self.root(), use_context_locals=False))
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- self.assertRaises(AssertionError, Pecan, self.root)
-
- def test_threadlocal_argument_warning(self):
- with mock.patch('threading.local', side_effect=AssertionError()):
-
- app = TestApp(Pecan(self.root(), use_context_locals=False))
- self.assertRaises(
- TypeError,
- app.get,
- '/warning/'
- )
-
- def test_threadlocal_argument_warning_on_generic(self):
- with mock.patch('threading.local', side_effect=AssertionError()):
-
- app = TestApp(Pecan(self.root(), use_context_locals=False))
- self.assertRaises(
- TypeError,
- app.get,
- '/generic/'
- )
-
- def test_threadlocal_argument_warning_on_generic_delegate(self):
- with mock.patch('threading.local', side_effect=AssertionError()):
-
- app = TestApp(Pecan(self.root(), use_context_locals=False))
- self.assertRaises(
- TypeError,
- app.put,
- '/generic/'
- )
-
-
-class TestIndexRouting(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
- @expose()
- def index(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return 'Hello, World!'
-
- return TestApp(Pecan(RootController(), use_context_locals=False))
-
- def test_empty_root(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- def test_index(self):
- r = self.app_.get('/index')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- def test_index_html(self):
- r = self.app_.get('/index.html')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
-
-class TestManualResponse(PecanTestCase):
-
- def test_manual_response(self):
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- resp = webob.Response(resp.environ)
- resp.body = b_('Hello, World!')
- return resp
-
- app = TestApp(Pecan(RootController(), use_context_locals=False))
- r = app.get('/')
- assert r.body == b_('Hello, World!'), r.body
-
-
-class TestDispatch(PecanTestCase):
-
- @property
- def app_(self):
- class SubSubController(object):
- @expose()
- def index(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return '/sub/sub/'
-
- @expose()
- def deeper(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return '/sub/sub/deeper'
-
- class SubController(object):
- @expose()
- def index(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return '/sub/'
-
- @expose()
- def deeper(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return '/sub/deeper'
-
- sub = SubSubController()
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return '/'
-
- @expose()
- def deeper(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return '/deeper'
-
- sub = SubController()
-
- return TestApp(Pecan(RootController(), use_context_locals=False))
-
- def test_index(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.body == b_('/')
-
- def test_one_level(self):
- r = self.app_.get('/deeper')
- assert r.status_int == 200
- assert r.body == b_('/deeper')
-
- def test_one_level_with_trailing(self):
- r = self.app_.get('/sub/')
- assert r.status_int == 200
- assert r.body == b_('/sub/')
-
- def test_two_levels(self):
- r = self.app_.get('/sub/deeper')
- assert r.status_int == 200
- assert r.body == b_('/sub/deeper')
-
- def test_two_levels_with_trailing(self):
- r = self.app_.get('/sub/sub/')
- assert r.status_int == 200
-
- def test_three_levels(self):
- r = self.app_.get('/sub/sub/deeper')
- assert r.status_int == 200
- assert r.body == b_('/sub/sub/deeper')
-
-
-class TestLookups(PecanTestCase):
-
- @property
- def app_(self):
- class LookupController(object):
- def __init__(self, someID):
- self.someID = someID
-
- @expose()
- def index(self, req, resp):
- return '/%s' % self.someID
-
- @expose()
- def name(self, req, resp):
- return '/%s/name' % self.someID
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- return '/'
-
- @expose()
- def _lookup(self, someID, *remainder):
- return LookupController(someID), remainder
-
- return TestApp(Pecan(RootController(), use_context_locals=False))
-
- def test_index(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.body == b_('/')
-
- def test_lookup(self):
- r = self.app_.get('/100/')
- assert r.status_int == 200
- assert r.body == b_('/100')
-
- def test_lookup_with_method(self):
- r = self.app_.get('/100/name')
- assert r.status_int == 200
- assert r.body == b_('/100/name')
-
- def test_lookup_with_wrong_argspec(self):
- class RootController(object):
- @expose()
- def _lookup(self, someID):
- return 'Bad arg spec' # pragma: nocover
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- app = TestApp(Pecan(RootController(), use_context_locals=False))
- r = app.get('/foo/bar', expect_errors=True)
- assert r.status_int == 404
-
-
-class TestCanonicalLookups(PecanTestCase):
-
- @property
- def app_(self):
- class LookupController(object):
- def __init__(self, someID):
- self.someID = someID
-
- @expose()
- def index(self, req, resp):
- return self.someID
-
- class UserController(object):
- @expose()
- def _lookup(self, someID, *remainder):
- return LookupController(someID), remainder
-
- class RootController(object):
- users = UserController()
-
- return TestApp(Pecan(RootController(), use_context_locals=False))
-
- def test_canonical_lookup(self):
- assert self.app_.get('/users', expect_errors=404).status_int == 404
- assert self.app_.get('/users/', expect_errors=404).status_int == 404
- assert self.app_.get('/users/100').status_int == 302
- assert self.app_.get('/users/100/').body == b_('100')
-
-
-class TestControllerArguments(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
- @expose()
- def index(self, req, resp, id):
- return 'index: %s' % id
-
- @expose()
- def multiple(self, req, resp, one, two):
- return 'multiple: %s, %s' % (one, two)
-
- @expose()
- def optional(self, req, resp, id=None):
- return 'optional: %s' % str(id)
-
- @expose()
- def multiple_optional(self, req, resp, one=None, two=None,
- three=None):
- return 'multiple_optional: %s, %s, %s' % (one, two, three)
-
- @expose()
- def variable_args(self, req, resp, *args):
- return 'variable_args: %s' % ', '.join(args)
-
- @expose()
- def variable_kwargs(self, req, resp, **kwargs):
- data = [
- '%s=%s' % (key, kwargs[key])
- for key in sorted(kwargs.keys())
- ]
- return 'variable_kwargs: %s' % ', '.join(data)
-
- @expose()
- def variable_all(self, req, resp, *args, **kwargs):
- data = [
- '%s=%s' % (key, kwargs[key])
- for key in sorted(kwargs.keys())
- ]
- return 'variable_all: %s' % ', '.join(list(args) + data)
-
- @expose()
- def eater(self, req, resp, id, dummy=None, *args, **kwargs):
- data = [
- '%s=%s' % (key, kwargs[key])
- for key in sorted(kwargs.keys())
- ]
- return 'eater: %s, %s, %s' % (
- id,
- dummy,
- ', '.join(list(args) + data)
- )
-
- @expose()
- def _route(self, args, request):
- if hasattr(self, args[0]):
- return getattr(self, args[0]), args[1:]
- else:
- return self.index, args
-
- return TestApp(Pecan(RootController(), use_context_locals=False))
-
- def test_required_argument(self):
- try:
- r = self.app_.get('/')
- assert r.status_int != 200 # pragma: nocover
- except Exception as ex:
- assert type(ex) == TypeError
- assert ex.args[0] in (
- "index() takes exactly 4 arguments (3 given)",
- "index() missing 1 required positional argument: 'id'"
- ) # this messaging changed in Python 3.3
-
- def test_single_argument(self):
- r = self.app_.get('/1')
- assert r.status_int == 200
- assert r.body == b_('index: 1')
-
- def test_single_argument_with_encoded_url(self):
- r = self.app_.get('/This%20is%20a%20test%21')
- assert r.status_int == 200
- assert r.body == b_('index: This is a test!')
-
- def test_two_arguments(self):
- r = self.app_.get('/1/dummy', status=404)
- assert r.status_int == 404
-
- def test_keyword_argument(self):
- r = self.app_.get('/?id=2')
- assert r.status_int == 200
- assert r.body == b_('index: 2')
-
- def test_keyword_argument_with_encoded_url(self):
- r = self.app_.get('/?id=This%20is%20a%20test%21')
- assert r.status_int == 200
- assert r.body == b_('index: This is a test!')
-
- def test_argument_and_keyword_argument(self):
- r = self.app_.get('/3?id=three')
- assert r.status_int == 200
- assert r.body == b_('index: 3')
-
- def test_encoded_argument_and_keyword_argument(self):
- r = self.app_.get('/This%20is%20a%20test%21?id=three')
- assert r.status_int == 200
- assert r.body == b_('index: This is a test!')
-
- def test_explicit_kwargs(self):
- r = self.app_.post('/', {'id': '4'})
- assert r.status_int == 200
- assert r.body == b_('index: 4')
-
- def test_path_with_explicit_kwargs(self):
- r = self.app_.post('/4', {'id': 'four'})
- assert r.status_int == 200
- assert r.body == b_('index: 4')
-
- def test_multiple_kwargs(self):
- r = self.app_.get('/?id=5&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('index: 5')
-
- def test_kwargs_from_root(self):
- r = self.app_.post('/', {'id': '6', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('index: 6')
-
- # multiple args
-
- def test_multiple_positional_arguments(self):
- r = self.app_.get('/multiple/one/two')
- assert r.status_int == 200
- assert r.body == b_('multiple: one, two')
-
- def test_multiple_positional_arguments_with_url_encode(self):
- r = self.app_.get('/multiple/One%20/Two%21')
- assert r.status_int == 200
- assert r.body == b_('multiple: One , Two!')
-
- def test_multiple_positional_arguments_with_kwargs(self):
- r = self.app_.get('/multiple?one=three&two=four')
- assert r.status_int == 200
- assert r.body == b_('multiple: three, four')
-
- def test_multiple_positional_arguments_with_url_encoded_kwargs(self):
- r = self.app_.get('/multiple?one=Three%20&two=Four%20%21')
- assert r.status_int == 200
- assert r.body == b_('multiple: Three , Four !')
-
- def test_positional_args_with_dictionary_kwargs(self):
- r = self.app_.post('/multiple', {'one': 'five', 'two': 'six'})
- assert r.status_int == 200
- assert r.body == b_('multiple: five, six')
-
- def test_positional_args_with_url_encoded_dictionary_kwargs(self):
- r = self.app_.post('/multiple', {'one': 'Five%20', 'two': 'Six%20%21'})
- assert r.status_int == 200
- assert r.body == b_('multiple: Five%20, Six%20%21')
-
- # optional arg
- def test_optional_arg(self):
- r = self.app_.get('/optional')
- assert r.status_int == 200
- assert r.body == b_('optional: None')
-
- def test_multiple_optional(self):
- r = self.app_.get('/optional/1')
- assert r.status_int == 200
- assert r.body == b_('optional: 1')
-
- def test_multiple_optional_url_encoded(self):
- r = self.app_.get('/optional/Some%20Number')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_multiple_optional_missing(self):
- r = self.app_.get('/optional/2/dummy', status=404)
- assert r.status_int == 404
-
- def test_multiple_with_kwargs(self):
- r = self.app_.get('/optional?id=2')
- assert r.status_int == 200
- assert r.body == b_('optional: 2')
-
- def test_multiple_with_url_encoded_kwargs(self):
- r = self.app_.get('/optional?id=Some%20Number')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_multiple_args_with_url_encoded_kwargs(self):
- r = self.app_.get('/optional/3?id=three')
- assert r.status_int == 200
- assert r.body == b_('optional: 3')
-
- def test_url_encoded_positional_args(self):
- r = self.app_.get('/optional/Some%20Number?id=three')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_optional_arg_with_kwargs(self):
- r = self.app_.post('/optional', {'id': '4'})
- assert r.status_int == 200
- assert r.body == b_('optional: 4')
-
- def test_optional_arg_with_url_encoded_kwargs(self):
- r = self.app_.post('/optional', {'id': 'Some%20Number'})
- assert r.status_int == 200
- assert r.body == b_('optional: Some%20Number')
-
- def test_multiple_positional_arguments_with_dictionary_kwargs(self):
- r = self.app_.post('/optional/5', {'id': 'five'})
- assert r.status_int == 200
- assert r.body == b_('optional: 5')
-
- def test_multiple_positional_url_encoded_arguments_with_kwargs(self):
- r = self.app_.post('/optional/Some%20Number', {'id': 'five'})
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_optional_arg_with_multiple_kwargs(self):
- r = self.app_.get('/optional?id=6&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('optional: 6')
-
- def test_optional_arg_with_multiple_url_encoded_kwargs(self):
- r = self.app_.get('/optional?id=Some%20Number&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_optional_arg_with_multiple_dictionary_kwargs(self):
- r = self.app_.post('/optional', {'id': '7', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('optional: 7')
-
- def test_optional_arg_with_multiple_url_encoded_dictionary_kwargs(self):
- r = self.app_.post('/optional', {
- 'id': 'Some%20Number',
- 'dummy': 'dummy'
- })
- assert r.status_int == 200
- assert r.body == b_('optional: Some%20Number')
-
- # multiple optional args
-
- def test_multiple_optional_positional_args(self):
- r = self.app_.get('/multiple_optional')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, None, None')
-
- def test_multiple_optional_positional_args_one_arg(self):
- r = self.app_.get('/multiple_optional/1')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_one_url_encoded_arg(self):
- r = self.app_.get('/multiple_optional/One%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_positional_args_all_args(self):
- r = self.app_.get('/multiple_optional/1/2/3')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_positional_args_all_url_encoded_args(self):
- r = self.app_.get('/multiple_optional/One%21/Two%21/Three%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, Two!, Three!')
-
- def test_multiple_optional_positional_args_too_many_args(self):
- r = self.app_.get('/multiple_optional/1/2/3/dummy', status=404)
- assert r.status_int == 404
-
- def test_multiple_optional_positional_args_with_kwargs(self):
- r = self.app_.get('/multiple_optional?one=1')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_url_encoded_kwargs(self):
- r = self.app_.get('/multiple_optional?one=One%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_positional_args_with_string_kwargs(self):
- r = self.app_.get('/multiple_optional/1?one=one')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_encoded_str_kwargs(self):
- r = self.app_.get('/multiple_optional/One%21?one=one')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_positional_args_with_dict_kwargs(self):
- r = self.app_.post('/multiple_optional', {'one': '1'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_encoded_dict_kwargs(self):
- r = self.app_.post('/multiple_optional', {'one': 'One%21'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One%21, None, None')
-
- def test_multiple_optional_positional_args_and_dict_kwargs(self):
- r = self.app_.post('/multiple_optional/1', {'one': 'one'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_encoded_positional_args_and_dict_kwargs(self):
- r = self.app_.post('/multiple_optional/One%21', {'one': 'one'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_args_with_multiple_kwargs(self):
- r = self.app_.get('/multiple_optional?one=1&two=2&three=3&four=4')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_args_with_multiple_encoded_kwargs(self):
- r = self.app_.get(
- '/multiple_optional?one=One%21&two=Two%21&three=Three%21&four=4'
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, Two!, Three!')
-
- def test_multiple_optional_args_with_multiple_dict_kwargs(self):
- r = self.app_.post(
- '/multiple_optional',
- {'one': '1', 'two': '2', 'three': '3', 'four': '4'}
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_args_with_multiple_encoded_dict_kwargs(self):
- r = self.app_.post(
- '/multiple_optional',
- {
- 'one': 'One%21',
- 'two': 'Two%21',
- 'three': 'Three%21',
- 'four': '4'
- }
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One%21, Two%21, Three%21')
-
- def test_multiple_optional_args_with_last_kwarg(self):
- r = self.app_.get('/multiple_optional?three=3')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, None, 3')
-
- def test_multiple_optional_args_with_last_encoded_kwarg(self):
- r = self.app_.get('/multiple_optional?three=Three%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, None, Three!')
-
- def test_multiple_optional_args_with_middle_arg(self):
- r = self.app_.get('/multiple_optional', {'two': '2'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, 2, None')
-
- def test_variable_args(self):
- r = self.app_.get('/variable_args')
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_multiple_variable_args(self):
- r = self.app_.get('/variable_args/1/dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_args: 1, dummy')
-
- def test_multiple_encoded_variable_args(self):
- r = self.app_.get('/variable_args/Testing%20One%20Two/Three%21')
- assert r.status_int == 200
- assert r.body == b_('variable_args: Testing One Two, Three!')
-
- def test_variable_args_with_kwargs(self):
- r = self.app_.get('/variable_args?id=2&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_variable_args_with_dict_kwargs(self):
- r = self.app_.post('/variable_args', {'id': '3', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_variable_kwargs(self):
- r = self.app_.get('/variable_kwargs')
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: ')
-
- def test_multiple_variable_kwargs(self):
- r = self.app_.get('/variable_kwargs/1/dummy', status=404)
- assert r.status_int == 404
-
- def test_multiple_variable_kwargs_with_explicit_kwargs(self):
- r = self.app_.get('/variable_kwargs?id=2&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=dummy, id=2')
-
- def test_multiple_variable_kwargs_with_explicit_encoded_kwargs(self):
- r = self.app_.get(
- '/variable_kwargs?id=Two%21&dummy=This%20is%20a%20test'
- )
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=This is a test, id=Two!')
-
- def test_multiple_variable_kwargs_with_dict_kwargs(self):
- r = self.app_.post('/variable_kwargs', {'id': '3', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=dummy, id=3')
-
- def test_multiple_variable_kwargs_with_encoded_dict_kwargs(self):
- r = self.app_.post(
- '/variable_kwargs',
- {'id': 'Three%21', 'dummy': 'This%20is%20a%20test'}
- )
- assert r.status_int == 200
- result = 'variable_kwargs: dummy=This%20is%20a%20test, id=Three%21'
- assert r.body == b_(result)
-
- def test_variable_all(self):
- r = self.app_.get('/variable_all')
- assert r.status_int == 200
- assert r.body == b_('variable_all: ')
-
- def test_variable_all_with_one_extra(self):
- r = self.app_.get('/variable_all/1')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 1')
-
- def test_variable_all_with_two_extras(self):
- r = self.app_.get('/variable_all/2/dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 2, dummy')
-
- def test_variable_mixed(self):
- r = self.app_.get('/variable_all/3?month=1&day=12')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 3, day=12, month=1')
-
- def test_variable_mixed_explicit(self):
- r = self.app_.get('/variable_all/4?id=four&month=1&day=12')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 4, day=12, id=four, month=1')
-
- def test_variable_post(self):
- r = self.app_.post('/variable_all/5/dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 5, dummy')
-
- def test_variable_post_with_kwargs(self):
- r = self.app_.post('/variable_all/6', {'month': '1', 'day': '12'})
- assert r.status_int == 200
- assert r.body == b_('variable_all: 6, day=12, month=1')
-
- def test_variable_post_mixed(self):
- r = self.app_.post(
- '/variable_all/7',
- {'id': 'seven', 'month': '1', 'day': '12'}
- )
- assert r.status_int == 200
- assert r.body == b_('variable_all: 7, day=12, id=seven, month=1')
-
- def test_no_remainder(self):
- try:
- r = self.app_.get('/eater')
- assert r.status_int != 200 # pragma: nocover
- except Exception as ex:
- assert type(ex) == TypeError
- assert ex.args[0] in (
- "eater() takes at least 4 arguments (3 given)",
- "eater() missing 1 required positional argument: 'id'"
- ) # this messaging changed in Python 3.3
-
- def test_one_remainder(self):
- r = self.app_.get('/eater/1')
- assert r.status_int == 200
- assert r.body == b_('eater: 1, None, ')
-
- def test_two_remainders(self):
- r = self.app_.get('/eater/2/dummy')
- assert r.status_int == 200
- assert r.body == b_('eater: 2, dummy, ')
-
- def test_many_remainders(self):
- r = self.app_.get('/eater/3/dummy/foo/bar')
- assert r.status_int == 200
- assert r.body == b_('eater: 3, dummy, foo, bar')
-
- def test_remainder_with_kwargs(self):
- r = self.app_.get('/eater/4?month=1&day=12')
- assert r.status_int == 200
- assert r.body == b_('eater: 4, None, day=12, month=1')
-
- def test_remainder_with_many_kwargs(self):
- r = self.app_.get('/eater/5?id=five&month=1&day=12&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('eater: 5, dummy, day=12, month=1')
-
- def test_post_remainder(self):
- r = self.app_.post('/eater/6')
- assert r.status_int == 200
- assert r.body == b_('eater: 6, None, ')
-
- def test_post_three_remainders(self):
- r = self.app_.post('/eater/7/dummy')
- assert r.status_int == 200
- assert r.body == b_('eater: 7, dummy, ')
-
- def test_post_many_remainders(self):
- r = self.app_.post('/eater/8/dummy/foo/bar')
- assert r.status_int == 200
- assert r.body == b_('eater: 8, dummy, foo, bar')
-
- def test_post_remainder_with_kwargs(self):
- r = self.app_.post('/eater/9', {'month': '1', 'day': '12'})
- assert r.status_int == 200
- assert r.body == b_('eater: 9, None, day=12, month=1')
-
- def test_post_many_remainders_with_many_kwargs(self):
- r = self.app_.post(
- '/eater/10',
- {'id': 'ten', 'month': '1', 'day': '12', 'dummy': 'dummy'}
- )
- assert r.status_int == 200
- assert r.body == b_('eater: 10, dummy, day=12, month=1')
-
-
-class TestRestController(PecanTestCase):
-
- @property
- def app_(self):
-
- class OthersController(object):
-
- @expose()
- def index(self, req, resp):
- return 'OTHERS'
-
- @expose()
- def echo(self, req, resp, value):
- return str(value)
-
- class ThingsController(RestController):
- data = ['zero', 'one', 'two', 'three']
-
- _custom_actions = {'count': ['GET'], 'length': ['GET', 'POST']}
-
- others = OthersController()
-
- @expose()
- def get_one(self, req, resp, id):
- return self.data[int(id)]
-
- @expose('json')
- def get_all(self, req, resp):
- return dict(items=self.data)
-
- @expose()
- def length(self, req, resp, id, value=None):
- length = len(self.data[int(id)])
- if value:
- length += len(value)
- return str(length)
-
- @expose()
- def post(self, req, resp, value):
- self.data.append(value)
- resp.status = 302
- return 'CREATED'
-
- @expose()
- def edit(self, req, resp, id):
- return 'EDIT %s' % self.data[int(id)]
-
- @expose()
- def put(self, req, resp, id, value):
- self.data[int(id)] = value
- return 'UPDATED'
-
- @expose()
- def get_delete(self, req, resp, id):
- return 'DELETE %s' % self.data[int(id)]
-
- @expose()
- def delete(self, req, resp, id):
- del self.data[int(id)]
- return 'DELETED'
-
- @expose()
- def reset(self, req, resp):
- return 'RESET'
-
- @expose()
- def post_options(self, req, resp):
- return 'OPTIONS'
-
- @expose()
- def options(self, req, resp):
- abort(500)
-
- @expose()
- def other(self, req, resp):
- abort(500)
-
- class RootController(object):
- things = ThingsController()
-
- # create the app
- return TestApp(Pecan(RootController(), use_context_locals=False))
-
- def test_get_all(self):
- r = self.app_.get('/things')
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(items=['zero', 'one', 'two', 'three'])))
-
- def test_get_one(self):
- for i, value in enumerate(['zero', 'one', 'two', 'three']):
- r = self.app_.get('/things/%d' % i)
- assert r.status_int == 200
- assert r.body == b_(value)
-
- def test_post(self):
- r = self.app_.post('/things', {'value': 'four'})
- assert r.status_int == 302
- assert r.body == b_('CREATED')
-
- def test_custom_action(self):
- r = self.app_.get('/things/3/edit')
- assert r.status_int == 200
- assert r.body == b_('EDIT three')
-
- def test_put(self):
- r = self.app_.put('/things/3', {'value': 'THREE!'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- def test_put_with_method_parameter_and_get(self):
- r = self.app_.get('/things/3?_method=put', {'value': 'X'}, status=405)
- assert r.status_int == 405
-
- def test_put_with_method_parameter_and_post(self):
- r = self.app_.post('/things/3?_method=put', {'value': 'THREE!'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- def test_get_delete(self):
- r = self.app_.get('/things/3/delete')
- assert r.status_int == 200
- assert r.body == b_('DELETE three')
-
- def test_delete_method(self):
- r = self.app_.delete('/things/3')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- def test_delete_with_method_parameter(self):
- r = self.app_.get('/things/3?_method=DELETE', status=405)
- assert r.status_int == 405
-
- def test_delete_with_method_parameter_and_post(self):
- r = self.app_.post('/things/3?_method=DELETE')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- def test_custom_method_type(self):
- r = self.app_.request('/things', method='RESET')
- assert r.status_int == 200
- assert r.body == b_('RESET')
-
- def test_custom_method_type_with_method_parameter(self):
- r = self.app_.get('/things?_method=RESET')
- assert r.status_int == 200
- assert r.body == b_('RESET')
-
- def test_options(self):
- r = self.app_.request('/things', method='OPTIONS')
- assert r.status_int == 200
- assert r.body == b_('OPTIONS')
-
- def test_options_with_method_parameter(self):
- r = self.app_.post('/things', {'_method': 'OPTIONS'})
- assert r.status_int == 200
- assert r.body == b_('OPTIONS')
-
- def test_other_custom_action(self):
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = self.app_.request('/things/other', method='MISC', status=405)
- assert r.status_int == 405
-
- def test_other_custom_action_with_method_parameter(self):
- r = self.app_.post('/things/other', {'_method': 'MISC'}, status=405)
- assert r.status_int == 405
-
- def test_nested_controller_with_trailing_slash(self):
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = self.app_.request('/things/others/', method='MISC')
- assert r.status_int == 200
- assert r.body == b_('OTHERS')
-
- def test_nested_controller_without_trailing_slash(self):
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = self.app_.request('/things/others', method='MISC', status=302)
- assert r.status_int == 302
-
- def test_invalid_custom_action(self):
- r = self.app_.get('/things?_method=BAD', status=405)
- assert r.status_int == 405
-
- def test_named_action(self):
- # test custom "GET" request "length"
- r = self.app_.get('/things/1/length')
- assert r.status_int == 200
- assert r.body == b_(str(len('one')))
-
- def test_named_nested_action(self):
- # test custom "GET" request through subcontroller
- r = self.app_.get('/things/others/echo?value=test')
- assert r.status_int == 200
- assert r.body == b_('test')
-
- def test_nested_post(self):
- # test custom "POST" request through subcontroller
- r = self.app_.post('/things/others/echo', {'value': 'test'})
- assert r.status_int == 200
- assert r.body == b_('test')
-
-
-class TestHooks(PecanTestCase):
-
- def test_basic_single_hook(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- run_hook.append('inside')
- return 'Hello, World!'
-
- class SimpleHook(PecanHook):
- def on_route(self, state):
- run_hook.append('on_route')
-
- def before(self, state):
- run_hook.append('before')
-
- def after(self, state):
- run_hook.append('after')
-
- def on_error(self, state, e):
- run_hook.append('error')
-
- app = TestApp(Pecan(
- RootController(),
- hooks=[SimpleHook()],
- use_context_locals=False
- ))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 4
- assert run_hook[0] == 'on_route'
- assert run_hook[1] == 'before'
- assert run_hook[2] == 'inside'
- assert run_hook[3] == 'after'
-
- def test_basic_multi_hook(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- run_hook.append('inside')
- return 'Hello, World!'
-
- class SimpleHook(PecanHook):
- def __init__(self, id):
- self.id = str(id)
-
- def on_route(self, state):
- run_hook.append('on_route' + self.id)
-
- def before(self, state):
- run_hook.append('before' + self.id)
-
- def after(self, state):
- run_hook.append('after' + self.id)
-
- def on_error(self, state, e):
- run_hook.append('error' + self.id)
-
- app = TestApp(Pecan(RootController(), hooks=[
- SimpleHook(1), SimpleHook(2), SimpleHook(3)
- ], use_context_locals=False))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 10
- assert run_hook[0] == 'on_route1'
- assert run_hook[1] == 'on_route2'
- assert run_hook[2] == 'on_route3'
- assert run_hook[3] == 'before1'
- assert run_hook[4] == 'before2'
- assert run_hook[5] == 'before3'
- assert run_hook[6] == 'inside'
- assert run_hook[7] == 'after3'
- assert run_hook[8] == 'after2'
- assert run_hook[9] == 'after1'
-
- def test_partial_hooks(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- run_hook.append('inside')
- return 'Hello World!'
-
- @expose()
- def causeerror(self, req, resp):
- return [][1]
-
- class ErrorHook(PecanHook):
- def on_error(self, state, e):
- run_hook.append('error')
-
- class OnRouteHook(PecanHook):
- def on_route(self, state):
- run_hook.append('on_route')
-
- app = TestApp(Pecan(RootController(), hooks=[
- ErrorHook(), OnRouteHook()
- ], use_context_locals=False))
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello World!')
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'on_route'
- assert run_hook[1] == 'inside'
-
- run_hook = []
- try:
- response = app.get('/causeerror')
- except Exception as e:
- assert isinstance(e, IndexError)
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'on_route'
- assert run_hook[1] == 'error'
-
- def test_on_error_response_hook(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def causeerror(self, req, resp):
- return [][1]
-
- class ErrorHook(PecanHook):
- def on_error(self, state, e):
- run_hook.append('error')
-
- r = webob.Response()
- r.text = u_('on_error')
-
- return r
-
- app = TestApp(Pecan(RootController(), hooks=[
- ErrorHook()
- ], use_context_locals=False))
-
- response = app.get('/causeerror')
-
- assert len(run_hook) == 1
- assert run_hook[0] == 'error'
- assert response.text == 'on_error'
-
- def test_prioritized_hooks(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- run_hook.append('inside')
- return 'Hello, World!'
-
- class SimpleHook(PecanHook):
- def __init__(self, id, priority=None):
- self.id = str(id)
- if priority:
- self.priority = priority
-
- def on_route(self, state):
- run_hook.append('on_route' + self.id)
-
- def before(self, state):
- run_hook.append('before' + self.id)
-
- def after(self, state):
- run_hook.append('after' + self.id)
-
- def on_error(self, state, e):
- run_hook.append('error' + self.id)
-
- papp = Pecan(RootController(), hooks=[
- SimpleHook(1, 3), SimpleHook(2, 2), SimpleHook(3, 1)
- ], use_context_locals=False)
- app = TestApp(papp)
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 10
- assert run_hook[0] == 'on_route3'
- assert run_hook[1] == 'on_route2'
- assert run_hook[2] == 'on_route1'
- assert run_hook[3] == 'before3'
- assert run_hook[4] == 'before2'
- assert run_hook[5] == 'before1'
- assert run_hook[6] == 'inside'
- assert run_hook[7] == 'after1'
- assert run_hook[8] == 'after2'
- assert run_hook[9] == 'after3'
-
- def test_basic_isolated_hook(self):
- run_hook = []
-
- class SimpleHook(PecanHook):
- def on_route(self, state):
- run_hook.append('on_route')
-
- def before(self, state):
- run_hook.append('before')
-
- def after(self, state):
- run_hook.append('after')
-
- def on_error(self, state, e):
- run_hook.append('error')
-
- class SubSubController(object):
- @expose()
- def index(self, req, resp):
- run_hook.append('inside_sub_sub')
- return 'Deep inside here!'
-
- class SubController(HookController):
- __hooks__ = [SimpleHook()]
-
- @expose()
- def index(self, req, resp):
- run_hook.append('inside_sub')
- return 'Inside here!'
-
- sub = SubSubController()
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- run_hook.append('inside')
- return 'Hello, World!'
-
- sub = SubController()
-
- app = TestApp(Pecan(RootController(), use_context_locals=False))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 1
- assert run_hook[0] == 'inside'
-
- run_hook = []
-
- response = app.get('/sub/')
- assert response.status_int == 200
- assert response.body == b_('Inside here!')
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'before'
- assert run_hook[1] == 'inside_sub'
- assert run_hook[2] == 'after'
-
- run_hook = []
- response = app.get('/sub/sub/')
- assert response.status_int == 200
- assert response.body == b_('Deep inside here!')
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'before'
- assert run_hook[1] == 'inside_sub_sub'
- assert run_hook[2] == 'after'
-
- def test_isolated_hook_with_global_hook(self):
- run_hook = []
-
- class SimpleHook(PecanHook):
- def __init__(self, id):
- self.id = str(id)
-
- def on_route(self, state):
- run_hook.append('on_route' + self.id)
-
- def before(self, state):
- run_hook.append('before' + self.id)
-
- def after(self, state):
- run_hook.append('after' + self.id)
-
- def on_error(self, state, e):
- run_hook.append('error' + self.id)
-
- class SubController(HookController):
- __hooks__ = [SimpleHook(2)]
-
- @expose()
- def index(self, req, resp):
- run_hook.append('inside_sub')
- return 'Inside here!'
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- run_hook.append('inside')
- return 'Hello, World!'
-
- sub = SubController()
-
- app = TestApp(Pecan(
- RootController(),
- hooks=[SimpleHook(1)],
- use_context_locals=False
- ))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 4
- assert run_hook[0] == 'on_route1'
- assert run_hook[1] == 'before1'
- assert run_hook[2] == 'inside'
- assert run_hook[3] == 'after1'
-
- run_hook = []
-
- response = app.get('/sub/')
- assert response.status_int == 200
- assert response.body == b_('Inside here!')
-
- assert len(run_hook) == 6
- assert run_hook[0] == 'on_route1'
- assert run_hook[1] == 'before2'
- assert run_hook[2] == 'before1'
- assert run_hook[3] == 'inside_sub'
- assert run_hook[4] == 'after1'
- assert run_hook[5] == 'after2'
-
-
-class TestGeneric(PecanTestCase):
-
- @property
- def root(self):
- class RootController(object):
-
- def __init__(self, unique):
- self.unique = unique
-
- @expose(generic=True, template='json')
- def index(self, req, resp):
- assert self.__class__.__name__ == 'RootController'
- assert isinstance(req, Request)
- assert isinstance(resp, Response)
- assert self.unique == req.headers.get('X-Unique')
- return {'hello': 'world'}
-
- @index.when(method='POST', template='json')
- def index_post(self, req, resp):
- assert self.__class__.__name__ == 'RootController'
- assert isinstance(req, Request)
- assert isinstance(resp, Response)
- assert self.unique == req.headers.get('X-Unique')
- return req.json
-
- @expose(template='json')
- def echo(self, req, resp):
- assert self.__class__.__name__ == 'RootController'
- assert isinstance(req, Request)
- assert isinstance(resp, Response)
- assert self.unique == req.headers.get('X-Unique')
- return req.json
-
- @expose(template='json')
- def extra(self, req, resp, first, second):
- assert self.__class__.__name__ == 'RootController'
- assert isinstance(req, Request)
- assert isinstance(resp, Response)
- assert self.unique == req.headers.get('X-Unique')
- return {'first': first, 'second': second}
-
- return RootController
-
- def test_generics_with_im_self_default(self):
- uniq = str(time.time())
- with mock.patch('threading.local', side_effect=AssertionError()):
- app = TestApp(Pecan(self.root(uniq), use_context_locals=False))
- r = app.get('/', headers={'X-Unique': uniq})
- assert r.status_int == 200
- json_resp = loads(r.body.decode())
- assert json_resp['hello'] == 'world'
-
- def test_generics_with_im_self_with_method(self):
- uniq = str(time.time())
- with mock.patch('threading.local', side_effect=AssertionError()):
- app = TestApp(Pecan(self.root(uniq), use_context_locals=False))
- r = app.post_json('/', {'foo': 'bar'}, headers={'X-Unique': uniq})
- assert r.status_int == 200
- json_resp = loads(r.body.decode())
- assert json_resp['foo'] == 'bar'
-
- def test_generics_with_im_self_with_path(self):
- uniq = str(time.time())
- with mock.patch('threading.local', side_effect=AssertionError()):
- app = TestApp(Pecan(self.root(uniq), use_context_locals=False))
- r = app.post_json('/echo/', {'foo': 'bar'},
- headers={'X-Unique': uniq})
- assert r.status_int == 200
- json_resp = loads(r.body.decode())
- assert json_resp['foo'] == 'bar'
-
- def test_generics_with_im_self_with_extra_args(self):
- uniq = str(time.time())
- with mock.patch('threading.local', side_effect=AssertionError()):
- app = TestApp(Pecan(self.root(uniq), use_context_locals=False))
- r = app.get('/extra/123/456', headers={'X-Unique': uniq})
- assert r.status_int == 200
- json_resp = loads(r.body.decode())
- assert json_resp['first'] == '123'
- assert json_resp['second'] == '456'
diff --git a/pecan/tests/test_rest.py b/pecan/tests/test_rest.py
deleted file mode 100644
index ba4d141..0000000
--- a/pecan/tests/test_rest.py
+++ /dev/null
@@ -1,1640 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import struct
-import sys
-import warnings
-
-if sys.version_info < (2, 7):
- import unittest2 as unittest # pragma: nocover
-else:
- import unittest # pragma: nocover
-
-try:
- from simplejson import dumps, loads
-except:
- from json import dumps, loads # noqa
-
-from six import b as b_, PY3
-from webtest import TestApp
-
-from pecan import abort, expose, make_app, response, redirect
-from pecan.rest import RestController
-from pecan.tests import PecanTestCase
-
-
-class TestRestController(PecanTestCase):
-
- def test_basic_rest(self):
-
- class OthersController(object):
-
- @expose()
- def index(self):
- return 'OTHERS'
-
- @expose()
- def echo(self, value):
- return str(value)
-
- class ThingsController(RestController):
- data = ['zero', 'one', 'two', 'three']
-
- _custom_actions = {'count': ['GET'], 'length': ['GET', 'POST']}
-
- others = OthersController()
-
- @expose()
- def get_one(self, id):
- return self.data[int(id)]
-
- @expose('json')
- def get_all(self):
- return dict(items=self.data)
-
- @expose()
- def length(self, id, value=None):
- length = len(self.data[int(id)])
- if value:
- length += len(value)
- return str(length)
-
- @expose()
- def get_count(self):
- return str(len(self.data))
-
- @expose()
- def new(self):
- return 'NEW'
-
- @expose()
- def post(self, value):
- self.data.append(value)
- response.status = 302
- return 'CREATED'
-
- @expose()
- def edit(self, id):
- return 'EDIT %s' % self.data[int(id)]
-
- @expose()
- def put(self, id, value):
- self.data[int(id)] = value
- return 'UPDATED'
-
- @expose()
- def get_delete(self, id):
- return 'DELETE %s' % self.data[int(id)]
-
- @expose()
- def delete(self, id):
- del self.data[int(id)]
- return 'DELETED'
-
- @expose()
- def reset(self):
- return 'RESET'
-
- @expose()
- def post_options(self):
- return 'OPTIONS'
-
- @expose()
- def options(self):
- abort(500)
-
- @expose()
- def other(self):
- abort(500)
-
- class RootController(object):
- things = ThingsController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # test get_all
- r = app.get('/things')
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(items=ThingsController.data)))
-
- # test get_one
- for i, value in enumerate(ThingsController.data):
- r = app.get('/things/%d' % i)
- assert r.status_int == 200
- assert r.body == b_(value)
-
- # test post
- r = app.post('/things', {'value': 'four'})
- assert r.status_int == 302
- assert r.body == b_('CREATED')
-
- # make sure it works
- r = app.get('/things/4')
- assert r.status_int == 200
- assert r.body == b_('four')
-
- # test edit
- r = app.get('/things/3/edit')
- assert r.status_int == 200
- assert r.body == b_('EDIT three')
-
- # test put
- r = app.put('/things/4', {'value': 'FOUR'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- # make sure it works
- r = app.get('/things/4')
- assert r.status_int == 200
- assert r.body == b_('FOUR')
-
- # test put with _method parameter and GET
- r = app.get('/things/4?_method=put', {'value': 'FOUR!'}, status=405)
- assert r.status_int == 405
-
- # make sure it works
- r = app.get('/things/4')
- assert r.status_int == 200
- assert r.body == b_('FOUR')
-
- # test put with _method parameter and POST
- r = app.post('/things/4?_method=put', {'value': 'FOUR!'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- # make sure it works
- r = app.get('/things/4')
- assert r.status_int == 200
- assert r.body == b_('FOUR!')
-
- # test get delete
- r = app.get('/things/4/delete')
- assert r.status_int == 200
- assert r.body == b_('DELETE FOUR!')
-
- # test delete
- r = app.delete('/things/4')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- # make sure it works
- r = app.get('/things')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 4
-
- # test delete with _method parameter and GET
- r = app.get('/things/3?_method=DELETE', status=405)
- assert r.status_int == 405
-
- # make sure it works
- r = app.get('/things')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 4
-
- # test delete with _method parameter and POST
- r = app.post('/things/3?_method=DELETE')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- # make sure it works
- r = app.get('/things')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 3
-
- # test "RESET" custom action
- r = app.request('/things', method='RESET')
- assert r.status_int == 200
- assert r.body == b_('RESET')
-
- # test "RESET" custom action with _method parameter
- r = app.get('/things?_method=RESET')
- assert r.status_int == 200
- assert r.body == b_('RESET')
-
- # test the "OPTIONS" custom action
- r = app.request('/things', method='OPTIONS')
- assert r.status_int == 200
- assert r.body == b_('OPTIONS')
-
- # test the "OPTIONS" custom action with the _method parameter
- r = app.post('/things', {'_method': 'OPTIONS'})
- assert r.status_int == 200
- assert r.body == b_('OPTIONS')
-
- # test the "other" custom action
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = app.request('/things/other', method='MISC', status=405)
- assert r.status_int == 405
-
- # test the "other" custom action with the _method parameter
- r = app.post('/things/other', {'_method': 'MISC'}, status=405)
- assert r.status_int == 405
-
- # test the "others" custom action
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = app.request('/things/others/', method='MISC')
- assert r.status_int == 200
- assert r.body == b_('OTHERS')
-
- # test the "others" custom action missing trailing slash
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = app.request('/things/others', method='MISC', status=302)
- assert r.status_int == 302
-
- # test the "others" custom action with the _method parameter
- r = app.get('/things/others/?_method=MISC')
- assert r.status_int == 200
- assert r.body == b_('OTHERS')
-
- # test an invalid custom action
- r = app.get('/things?_method=BAD', status=405)
- assert r.status_int == 405
-
- # test custom "GET" request "count"
- r = app.get('/things/count')
- assert r.status_int == 200
- assert r.body == b_('3')
-
- # test custom "GET" request "length"
- r = app.get('/things/1/length')
- assert r.status_int == 200
- assert r.body == b_(str(len('one')))
-
- # test custom "GET" request through subcontroller
- r = app.get('/things/others/echo?value=test')
- assert r.status_int == 200
- assert r.body == b_('test')
-
- # test custom "POST" request "length"
- r = app.post('/things/1/length', {'value': 'test'})
- assert r.status_int == 200
- assert r.body == b_(str(len('onetest')))
-
- # test custom "POST" request through subcontroller
- r = app.post('/things/others/echo', {'value': 'test'})
- assert r.status_int == 200
- assert r.body == b_('test')
-
- def test_getall_with_trailing_slash(self):
-
- class ThingsController(RestController):
-
- data = ['zero', 'one', 'two', 'three']
-
- @expose('json')
- def get_all(self):
- return dict(items=self.data)
-
- class RootController(object):
- things = ThingsController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # test get_all
- r = app.get('/things/')
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(items=ThingsController.data)))
-
- def test_405_with_lookup(self):
-
- class LookupController(RestController):
-
- def __init__(self, _id):
- self._id = _id
-
- @expose()
- def get_all(self):
- return 'ID: %s' % self._id
-
- class ThingsController(RestController):
-
- @expose()
- def _lookup(self, _id, *remainder):
- return LookupController(_id), remainder
-
- class RootController(object):
- things = ThingsController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # these should 405
- for path in ('/things', '/things/'):
- r = app.get(path, expect_errors=True)
- assert r.status_int == 405
-
- r = app.get('/things/foo')
- assert r.status_int == 200
- assert r.body == b_('ID: foo')
-
- def test_getall_with_lookup(self):
-
- class LookupController(RestController):
-
- def __init__(self, _id):
- self._id = _id
-
- @expose()
- def get_all(self):
- return 'ID: %s' % self._id
-
- class ThingsController(RestController):
-
- data = ['zero', 'one', 'two', 'three']
-
- @expose()
- def _lookup(self, _id, *remainder):
- return LookupController(_id), remainder
-
- @expose('json')
- def get_all(self):
- return dict(items=self.data)
-
- class RootController(object):
- things = ThingsController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # test get_all
- for path in ('/things', '/things/'):
- r = app.get(path)
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(items=ThingsController.data)))
-
- r = app.get('/things/foo')
- assert r.status_int == 200
- assert r.body == b_('ID: foo')
-
- def test_simple_nested_rest(self):
-
- class BarController(RestController):
-
- @expose()
- def post(self):
- return "BAR-POST"
-
- @expose()
- def delete(self, id_):
- return "BAR-%s" % id_
-
- class FooController(RestController):
-
- bar = BarController()
-
- @expose()
- def post(self):
- return "FOO-POST"
-
- @expose()
- def delete(self, id_):
- return "FOO-%s" % id_
-
- class RootController(object):
- foo = FooController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- r = app.post('/foo')
- assert r.status_int == 200
- assert r.body == b_("FOO-POST")
-
- r = app.delete('/foo/1')
- assert r.status_int == 200
- assert r.body == b_("FOO-1")
-
- r = app.post('/foo/bar')
- assert r.status_int == 200
- assert r.body == b_("BAR-POST")
-
- r = app.delete('/foo/bar/2')
- assert r.status_int == 200
- assert r.body == b_("BAR-2")
-
- def test_complicated_nested_rest(self):
-
- class BarsController(RestController):
-
- data = [['zero-zero', 'zero-one'], ['one-zero', 'one-one']]
-
- @expose()
- def get_one(self, foo_id, id):
- return self.data[int(foo_id)][int(id)]
-
- @expose('json')
- def get_all(self, foo_id):
- return dict(items=self.data[int(foo_id)])
-
- @expose()
- def new(self, foo_id):
- return 'NEW FOR %s' % foo_id
-
- @expose()
- def post(self, foo_id, value):
- foo_id = int(foo_id)
- if len(self.data) < foo_id + 1:
- self.data.extend([[]] * (foo_id - len(self.data) + 1))
- self.data[foo_id].append(value)
- response.status = 302
- return 'CREATED FOR %s' % foo_id
-
- @expose()
- def edit(self, foo_id, id):
- return 'EDIT %s' % self.data[int(foo_id)][int(id)]
-
- @expose()
- def put(self, foo_id, id, value):
- self.data[int(foo_id)][int(id)] = value
- return 'UPDATED'
-
- @expose()
- def get_delete(self, foo_id, id):
- return 'DELETE %s' % self.data[int(foo_id)][int(id)]
-
- @expose()
- def delete(self, foo_id, id):
- del self.data[int(foo_id)][int(id)]
- return 'DELETED'
-
- class FoosController(RestController):
-
- data = ['zero', 'one']
-
- bars = BarsController()
-
- @expose()
- def get_one(self, id):
- return self.data[int(id)]
-
- @expose('json')
- def get_all(self):
- return dict(items=self.data)
-
- @expose()
- def new(self):
- return 'NEW'
-
- @expose()
- def edit(self, id):
- return 'EDIT %s' % self.data[int(id)]
-
- @expose()
- def post(self, value):
- self.data.append(value)
- response.status = 302
- return 'CREATED'
-
- @expose()
- def put(self, id, value):
- self.data[int(id)] = value
- return 'UPDATED'
-
- @expose()
- def get_delete(self, id):
- return 'DELETE %s' % self.data[int(id)]
-
- @expose()
- def delete(self, id):
- del self.data[int(id)]
- return 'DELETED'
-
- class RootController(object):
- foos = FoosController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # test get_all
- r = app.get('/foos')
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(items=FoosController.data)))
-
- # test nested get_all
- r = app.get('/foos/1/bars')
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(items=BarsController.data[1])))
-
- # test get_one
- for i, value in enumerate(FoosController.data):
- r = app.get('/foos/%d' % i)
- assert r.status_int == 200
- assert r.body == b_(value)
-
- # test nested get_one
- for i, value in enumerate(FoosController.data):
- for j, value in enumerate(BarsController.data[i]):
- r = app.get('/foos/%s/bars/%s' % (i, j))
- assert r.status_int == 200
- assert r.body == b_(value)
-
- # test post
- r = app.post('/foos', {'value': 'two'})
- assert r.status_int == 302
- assert r.body == b_('CREATED')
-
- # make sure it works
- r = app.get('/foos/2')
- assert r.status_int == 200
- assert r.body == b_('two')
-
- # test nested post
- r = app.post('/foos/2/bars', {'value': 'two-zero'})
- assert r.status_int == 302
- assert r.body == b_('CREATED FOR 2')
-
- # make sure it works
- r = app.get('/foos/2/bars/0')
- assert r.status_int == 200
- assert r.body == b_('two-zero')
-
- # test edit
- r = app.get('/foos/1/edit')
- assert r.status_int == 200
- assert r.body == b_('EDIT one')
-
- # test nested edit
- r = app.get('/foos/1/bars/1/edit')
- assert r.status_int == 200
- assert r.body == b_('EDIT one-one')
-
- # test put
- r = app.put('/foos/2', {'value': 'TWO'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- # make sure it works
- r = app.get('/foos/2')
- assert r.status_int == 200
- assert r.body == b_('TWO')
-
- # test nested put
- r = app.put('/foos/2/bars/0', {'value': 'TWO-ZERO'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- # make sure it works
- r = app.get('/foos/2/bars/0')
- assert r.status_int == 200
- assert r.body == b_('TWO-ZERO')
-
- # test put with _method parameter and GET
- r = app.get('/foos/2?_method=put', {'value': 'TWO!'}, status=405)
- assert r.status_int == 405
-
- # make sure it works
- r = app.get('/foos/2')
- assert r.status_int == 200
- assert r.body == b_('TWO')
-
- # test nested put with _method parameter and GET
- r = app.get(
- '/foos/2/bars/0?_method=put',
- {'value': 'ZERO-TWO!'}, status=405
- )
- assert r.status_int == 405
-
- # make sure it works
- r = app.get('/foos/2/bars/0')
- assert r.status_int == 200
- assert r.body == b_('TWO-ZERO')
-
- # test put with _method parameter and POST
- r = app.post('/foos/2?_method=put', {'value': 'TWO!'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- # make sure it works
- r = app.get('/foos/2')
- assert r.status_int == 200
- assert r.body == b_('TWO!')
-
- # test nested put with _method parameter and POST
- r = app.post('/foos/2/bars/0?_method=put', {'value': 'TWO-ZERO!'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- # make sure it works
- r = app.get('/foos/2/bars/0')
- assert r.status_int == 200
- assert r.body == b_('TWO-ZERO!')
-
- # test get delete
- r = app.get('/foos/2/delete')
- assert r.status_int == 200
- assert r.body == b_('DELETE TWO!')
-
- # test nested get delete
- r = app.get('/foos/2/bars/0/delete')
- assert r.status_int == 200
- assert r.body == b_('DELETE TWO-ZERO!')
-
- # test nested delete
- r = app.delete('/foos/2/bars/0')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- # make sure it works
- r = app.get('/foos/2/bars')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 0
-
- # test delete
- r = app.delete('/foos/2')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- # make sure it works
- r = app.get('/foos')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 2
-
- # test nested delete with _method parameter and GET
- r = app.get('/foos/1/bars/1?_method=DELETE', status=405)
- assert r.status_int == 405
-
- # make sure it works
- r = app.get('/foos/1/bars')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 2
-
- # test delete with _method parameter and GET
- r = app.get('/foos/1?_method=DELETE', status=405)
- assert r.status_int == 405
-
- # make sure it works
- r = app.get('/foos')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 2
-
- # test nested delete with _method parameter and POST
- r = app.post('/foos/1/bars/1?_method=DELETE')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- # make sure it works
- r = app.get('/foos/1/bars')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 1
-
- # test delete with _method parameter and POST
- r = app.post('/foos/1?_method=DELETE')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- # make sure it works
- r = app.get('/foos')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 1
-
- def test_nested_get_all(self):
-
- class BarsController(RestController):
-
- @expose()
- def get_one(self, foo_id, id):
- return '4'
-
- @expose()
- def get_all(self, foo_id):
- return '3'
-
- class FoosController(RestController):
-
- bars = BarsController()
-
- @expose()
- def get_one(self, id):
- return '2'
-
- @expose()
- def get_all(self):
- return '1'
-
- class RootController(object):
- foos = FoosController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foos/')
- assert r.status_int == 200
- assert r.body == b_('1')
-
- r = app.get('/foos/1/')
- assert r.status_int == 200
- assert r.body == b_('2')
-
- r = app.get('/foos/1/bars/')
- assert r.status_int == 200
- assert r.body == b_('3')
-
- r = app.get('/foos/1/bars/2/')
- assert r.status_int == 200
- assert r.body == b_('4')
-
- r = app.get('/foos/bars/', status=404)
- assert r.status_int == 404
-
- r = app.get('/foos/bars/1', status=404)
- assert r.status_int == 404
-
- def test_nested_get_all_with_lookup(self):
-
- class BarsController(RestController):
-
- @expose()
- def get_one(self, foo_id, id):
- return '4'
-
- @expose()
- def get_all(self, foo_id):
- return '3'
-
- @expose('json')
- def _lookup(self, id, *remainder):
- redirect('/lookup-hit/')
-
- class FoosController(RestController):
-
- bars = BarsController()
-
- @expose()
- def get_one(self, id):
- return '2'
-
- @expose()
- def get_all(self):
- return '1'
-
- class RootController(object):
- foos = FoosController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foos/')
- assert r.status_int == 200
- assert r.body == b_('1')
-
- r = app.get('/foos/1/')
- assert r.status_int == 200
- assert r.body == b_('2')
-
- r = app.get('/foos/1/bars/')
- assert r.status_int == 200
- assert r.body == b_('3')
-
- r = app.get('/foos/1/bars/2/')
- assert r.status_int == 200
- assert r.body == b_('4')
-
- r = app.get('/foos/bars/')
- assert r.status_int == 302
- assert r.headers['Location'].endswith('/lookup-hit/')
-
- r = app.get('/foos/bars/1')
- assert r.status_int == 302
- assert r.headers['Location'].endswith('/lookup-hit/')
-
- def test_bad_rest(self):
-
- class ThingsController(RestController):
- pass
-
- class RootController(object):
- things = ThingsController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # test get_all
- r = app.get('/things', status=405)
- assert r.status_int == 405
-
- # test get_one
- r = app.get('/things/1', status=405)
- assert r.status_int == 405
-
- # test post
- r = app.post('/things', {'value': 'one'}, status=405)
- assert r.status_int == 405
-
- # test edit
- r = app.get('/things/1/edit', status=405)
- assert r.status_int == 405
-
- # test put
- r = app.put('/things/1', {'value': 'ONE'}, status=405)
-
- # test put with _method parameter and GET
- r = app.get('/things/1?_method=put', {'value': 'ONE!'}, status=405)
- assert r.status_int == 405
-
- # test put with _method parameter and POST
- r = app.post('/things/1?_method=put', {'value': 'ONE!'}, status=405)
- assert r.status_int == 405
-
- # test get delete
- r = app.get('/things/1/delete', status=405)
- assert r.status_int == 405
-
- # test delete
- r = app.delete('/things/1', status=405)
- assert r.status_int == 405
-
- # test delete with _method parameter and GET
- r = app.get('/things/1?_method=DELETE', status=405)
- assert r.status_int == 405
-
- # test delete with _method parameter and POST
- r = app.post('/things/1?_method=DELETE', status=405)
- assert r.status_int == 405
-
- # test "RESET" custom action
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = app.request('/things', method='RESET', status=405)
- assert r.status_int == 405
-
- def test_nested_rest_with_missing_intermediate_id(self):
-
- class BarsController(RestController):
-
- data = [['zero-zero', 'zero-one'], ['one-zero', 'one-one']]
-
- @expose('json')
- def get_all(self, foo_id):
- return dict(items=self.data[int(foo_id)])
-
- class FoosController(RestController):
-
- data = ['zero', 'one']
-
- bars = BarsController()
-
- @expose()
- def get_one(self, id):
- return self.data[int(id)]
-
- @expose('json')
- def get_all(self):
- return dict(items=self.data)
-
- class RootController(object):
- foos = FoosController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # test get_all
- r = app.get('/foos')
- self.assertEqual(r.status_int, 200)
- self.assertEqual(r.body, b_(dumps(dict(items=FoosController.data))))
-
- # test nested get_all
- r = app.get('/foos/1/bars')
- self.assertEqual(r.status_int, 200)
- self.assertEqual(r.body, b_(dumps(dict(items=BarsController.data[1]))))
-
- r = app.get('/foos/bars', expect_errors=True)
- self.assertEqual(r.status_int, 404)
-
- def test_custom_with_trailing_slash(self):
-
- class CustomController(RestController):
-
- _custom_actions = {
- 'detail': ['GET'],
- 'create': ['POST'],
- 'update': ['PUT'],
- 'remove': ['DELETE'],
- }
-
- @expose()
- def detail(self):
- return 'DETAIL'
-
- @expose()
- def create(self):
- return 'CREATE'
-
- @expose()
- def update(self, id):
- return id
-
- @expose()
- def remove(self, id):
- return id
-
- app = TestApp(make_app(CustomController()))
-
- r = app.get('/detail')
- assert r.status_int == 200
- assert r.body == b_('DETAIL')
-
- r = app.get('/detail/')
- assert r.status_int == 200
- assert r.body == b_('DETAIL')
-
- r = app.post('/create')
- assert r.status_int == 200
- assert r.body == b_('CREATE')
-
- r = app.post('/create/')
- assert r.status_int == 200
- assert r.body == b_('CREATE')
-
- r = app.put('/update/123')
- assert r.status_int == 200
- assert r.body == b_('123')
-
- r = app.put('/update/123/')
- assert r.status_int == 200
- assert r.body == b_('123')
-
- r = app.delete('/remove/456')
- assert r.status_int == 200
- assert r.body == b_('456')
-
- r = app.delete('/remove/456/')
- assert r.status_int == 200
- assert r.body == b_('456')
-
- def test_custom_delete(self):
-
- class OthersController(object):
-
- @expose()
- def index(self):
- return 'DELETE'
-
- @expose()
- def reset(self, id):
- return str(id)
-
- class ThingsController(RestController):
-
- others = OthersController()
-
- @expose()
- def delete_fail(self):
- abort(500)
-
- class RootController(object):
- things = ThingsController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # test bad delete
- r = app.delete('/things/delete_fail', status=405)
- assert r.status_int == 405
-
- # test bad delete with _method parameter and GET
- r = app.get('/things/delete_fail?_method=delete', status=405)
- assert r.status_int == 405
-
- # test bad delete with _method parameter and POST
- r = app.post('/things/delete_fail', {'_method': 'delete'}, status=405)
- assert r.status_int == 405
-
- # test custom delete without ID
- r = app.delete('/things/others/')
- assert r.status_int == 200
- assert r.body == b_('DELETE')
-
- # test custom delete without ID with _method parameter and GET
- r = app.get('/things/others/?_method=delete', status=405)
- assert r.status_int == 405
-
- # test custom delete without ID with _method parameter and POST
- r = app.post('/things/others/', {'_method': 'delete'})
- assert r.status_int == 200
- assert r.body == b_('DELETE')
-
- # test custom delete with ID
- r = app.delete('/things/others/reset/1')
- assert r.status_int == 200
- assert r.body == b_('1')
-
- # test custom delete with ID with _method parameter and GET
- r = app.get('/things/others/reset/1?_method=delete', status=405)
- assert r.status_int == 405
-
- # test custom delete with ID with _method parameter and POST
- r = app.post('/things/others/reset/1', {'_method': 'delete'})
- assert r.status_int == 200
- assert r.body == b_('1')
-
- def test_get_with_var_args(self):
-
- class OthersController(object):
-
- @expose()
- def index(self, one, two, three):
- return 'NESTED: %s, %s, %s' % (one, two, three)
-
- class ThingsController(RestController):
-
- others = OthersController()
-
- @expose()
- def get_one(self, *args):
- return ', '.join(args)
-
- class RootController(object):
- things = ThingsController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # test get request
- r = app.get('/things/one/two/three')
- assert r.status_int == 200
- assert r.body == b_('one, two, three')
-
- # test nested get request
- r = app.get('/things/one/two/three/others/')
- assert r.status_int == 200
- assert r.body == b_('NESTED: one, two, three')
-
- def test_sub_nested_rest(self):
-
- class BazsController(RestController):
-
- data = [[['zero-zero-zero']]]
-
- @expose()
- def get_one(self, foo_id, bar_id, id):
- return self.data[int(foo_id)][int(bar_id)][int(id)]
-
- class BarsController(RestController):
-
- data = [['zero-zero']]
-
- bazs = BazsController()
-
- @expose()
- def get_one(self, foo_id, id):
- return self.data[int(foo_id)][int(id)]
-
- class FoosController(RestController):
-
- data = ['zero']
-
- bars = BarsController()
-
- @expose()
- def get_one(self, id):
- return self.data[int(id)]
-
- class RootController(object):
- foos = FoosController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # test sub-nested get_one
- r = app.get('/foos/0/bars/0/bazs/0')
- assert r.status_int == 200
- assert r.body == b_('zero-zero-zero')
-
- def test_sub_nested_rest_with_overwrites(self):
-
- class FinalController(object):
-
- @expose()
- def index(self):
- return 'FINAL'
-
- @expose()
- def named(self):
- return 'NAMED'
-
- class BazsController(RestController):
-
- data = [[['zero-zero-zero']]]
-
- final = FinalController()
-
- @expose()
- def get_one(self, foo_id, bar_id, id):
- return self.data[int(foo_id)][int(bar_id)][int(id)]
-
- @expose()
- def post(self):
- return 'POST-GRAND-CHILD'
-
- @expose()
- def put(self, id):
- return 'PUT-GRAND-CHILD'
-
- class BarsController(RestController):
-
- data = [['zero-zero']]
-
- bazs = BazsController()
-
- @expose()
- def get_one(self, foo_id, id):
- return self.data[int(foo_id)][int(id)]
-
- @expose()
- def post(self):
- return 'POST-CHILD'
-
- @expose()
- def put(self, id):
- return 'PUT-CHILD'
-
- class FoosController(RestController):
-
- data = ['zero']
-
- bars = BarsController()
-
- @expose()
- def get_one(self, id):
- return self.data[int(id)]
-
- @expose()
- def post(self):
- return 'POST'
-
- @expose()
- def put(self, id):
- return 'PUT'
-
- class RootController(object):
- foos = FoosController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- r = app.post('/foos')
- assert r.status_int == 200
- assert r.body == b_('POST')
-
- r = app.put('/foos/0')
- assert r.status_int == 200
- assert r.body == b_('PUT')
-
- r = app.post('/foos/bars')
- assert r.status_int == 200
- assert r.body == b_('POST-CHILD')
-
- r = app.put('/foos/bars/0')
- assert r.status_int == 200
- assert r.body == b_('PUT-CHILD')
-
- r = app.post('/foos/bars/bazs')
- assert r.status_int == 200
- assert r.body == b_('POST-GRAND-CHILD')
-
- r = app.put('/foos/bars/bazs/0')
- assert r.status_int == 200
- assert r.body == b_('PUT-GRAND-CHILD')
-
- r = app.get('/foos/bars/bazs/final/')
- assert r.status_int == 200
- assert r.body == b_('FINAL')
-
- r = app.get('/foos/bars/bazs/final/named')
- assert r.status_int == 200
- assert r.body == b_('NAMED')
-
- def test_post_with_kwargs_only(self):
-
- class RootController(RestController):
-
- @expose()
- def get_all(self):
- return 'INDEX'
-
- @expose('json')
- def post(self, **kw):
- return kw
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_('INDEX')
-
- kwargs = {'foo': 'bar', 'spam': 'eggs'}
- r = app.post('/', kwargs)
- assert r.status_int == 200
- assert r.namespace['foo'] == 'bar'
- assert r.namespace['spam'] == 'eggs'
-
- def test_nested_rest_with_lookup(self):
-
- class SubController(RestController):
-
- @expose()
- def get_all(self):
- return "SUB"
-
- class FinalController(RestController):
-
- def __init__(self, id_):
- self.id_ = id_
-
- @expose()
- def get_all(self):
- return "FINAL-%s" % self.id_
-
- @expose()
- def post(self):
- return "POST-%s" % self.id_
-
- class LookupController(RestController):
-
- sub = SubController()
-
- def __init__(self, id_):
- self.id_ = id_
-
- @expose()
- def _lookup(self, id_, *remainder):
- return FinalController(id_), remainder
-
- @expose()
- def get_all(self):
- raise AssertionError("Never Reached")
-
- @expose()
- def post(self):
- return "POST-LOOKUP-%s" % self.id_
-
- @expose()
- def put(self, id_):
- return "PUT-LOOKUP-%s-%s" % (self.id_, id_)
-
- @expose()
- def delete(self, id_):
- return "DELETE-LOOKUP-%s-%s" % (self.id_, id_)
-
- class FooController(RestController):
-
- @expose()
- def _lookup(self, id_, *remainder):
- return LookupController(id_), remainder
-
- @expose()
- def get_one(self, id_):
- return "GET ONE"
-
- @expose()
- def get_all(self):
- return "INDEX"
-
- @expose()
- def post(self):
- return "POST"
-
- @expose()
- def put(self, id_):
- return "PUT-%s" % id_
-
- @expose()
- def delete(self, id_):
- return "DELETE-%s" % id_
-
- class RootController(RestController):
- foo = FooController()
-
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foo')
- assert r.status_int == 200
- assert r.body == b_('INDEX')
-
- r = app.post('/foo')
- assert r.status_int == 200
- assert r.body == b_('POST')
-
- r = app.get('/foo/1')
- assert r.status_int == 200
- assert r.body == b_('GET ONE')
-
- r = app.post('/foo/1')
- assert r.status_int == 200
- assert r.body == b_('POST-LOOKUP-1')
-
- r = app.put('/foo/1')
- assert r.status_int == 200
- assert r.body == b_('PUT-1')
-
- r = app.delete('/foo/1')
- assert r.status_int == 200
- assert r.body == b_('DELETE-1')
-
- r = app.put('/foo/1/2')
- assert r.status_int == 200
- assert r.body == b_('PUT-LOOKUP-1-2')
-
- r = app.delete('/foo/1/2')
- assert r.status_int == 200
- assert r.body == b_('DELETE-LOOKUP-1-2')
-
- r = app.get('/foo/1/2')
- assert r.status_int == 200
- assert r.body == b_('FINAL-2')
-
- r = app.post('/foo/1/2')
- assert r.status_int == 200
- assert r.body == b_('POST-2')
-
- def test_nested_rest_with_default(self):
-
- class FooController(RestController):
-
- @expose()
- def _default(self, *remainder):
- return "DEFAULT %s" % remainder
-
- class RootController(RestController):
- foo = FooController()
-
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foo/missing')
- assert r.status_int == 200
- assert r.body == b_("DEFAULT missing")
-
- def test_rest_with_non_utf_8_body(self):
- if PY3:
- # webob+PY3 doesn't suffer from this bug; the POST parsing in PY3
- # seems to more gracefully detect the bytestring
- return
-
- class FooController(RestController):
-
- @expose()
- def post(self):
- return "POST"
-
- class RootController(RestController):
- foo = FooController()
-
- app = TestApp(make_app(RootController()))
-
- data = struct.pack('255h', *range(0, 255))
- r = app.post('/foo/', data, expect_errors=True)
- assert r.status_int == 400
-
- def test_dynamic_rest_lookup(self):
- class BarController(RestController):
- @expose()
- def get_all(self):
- return "BAR"
-
- @expose()
- def put(self):
- return "PUT_BAR"
-
- @expose()
- def delete(self):
- return "DELETE_BAR"
-
- class BarsController(RestController):
- @expose()
- def _lookup(self, id_, *remainder):
- return BarController(), remainder
-
- @expose()
- def get_all(self):
- return "BARS"
-
- @expose()
- def post(self):
- return "POST_BARS"
-
- class FooController(RestController):
- bars = BarsController()
-
- @expose()
- def get_all(self):
- return "FOO"
-
- @expose()
- def put(self):
- return "PUT_FOO"
-
- @expose()
- def delete(self):
- return "DELETE_FOO"
-
- class FoosController(RestController):
- @expose()
- def _lookup(self, id_, *remainder):
- return FooController(), remainder
-
- @expose()
- def get_all(self):
- return "FOOS"
-
- @expose()
- def post(self):
- return "POST_FOOS"
-
- class RootController(RestController):
- foos = FoosController()
-
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foos')
- assert r.status_int == 200
- assert r.body == b_('FOOS')
-
- r = app.post('/foos')
- assert r.status_int == 200
- assert r.body == b_('POST_FOOS')
-
- r = app.get('/foos/foo')
- assert r.status_int == 200
- assert r.body == b_('FOO')
-
- r = app.put('/foos/foo')
- assert r.status_int == 200
- assert r.body == b_('PUT_FOO')
-
- r = app.delete('/foos/foo')
- assert r.status_int == 200
- assert r.body == b_('DELETE_FOO')
-
- r = app.get('/foos/foo/bars')
- assert r.status_int == 200
- assert r.body == b_('BARS')
-
- r = app.post('/foos/foo/bars')
- assert r.status_int == 200
- assert r.body == b_('POST_BARS')
-
- r = app.get('/foos/foo/bars/bar')
- assert r.status_int == 200
- assert r.body == b_('BAR')
-
- r = app.put('/foos/foo/bars/bar')
- assert r.status_int == 200
- assert r.body == b_('PUT_BAR')
-
- r = app.delete('/foos/foo/bars/bar')
- assert r.status_int == 200
- assert r.body == b_('DELETE_BAR')
-
- def test_method_not_allowed_get(self):
- class ThingsController(RestController):
-
- @expose()
- def put(self, id_, value):
- response.status = 200
-
- @expose()
- def delete(self, id_):
- response.status = 200
-
- app = TestApp(make_app(ThingsController()))
- r = app.get('/', status=405)
- assert r.status_int == 405
- assert r.headers['Allow'] == 'DELETE, PUT'
-
- def test_method_not_allowed_post(self):
- class ThingsController(RestController):
-
- @expose()
- def get_one(self):
- return dict()
-
- app = TestApp(make_app(ThingsController()))
- r = app.post('/', {'foo': 'bar'}, status=405)
- assert r.status_int == 405
- assert r.headers['Allow'] == 'GET'
-
- def test_method_not_allowed_put(self):
- class ThingsController(RestController):
-
- @expose()
- def get_one(self):
- return dict()
-
- app = TestApp(make_app(ThingsController()))
- r = app.put('/123', status=405)
- assert r.status_int == 405
- assert r.headers['Allow'] == 'GET'
-
- def test_method_not_allowed_delete(self):
- class ThingsController(RestController):
-
- @expose()
- def get_one(self):
- return dict()
-
- app = TestApp(make_app(ThingsController()))
- r = app.delete('/123', status=405)
- assert r.status_int == 405
- assert r.headers['Allow'] == 'GET'
-
- def test_proper_allow_header_multiple_gets(self):
- class ThingsController(RestController):
-
- @expose()
- def get_all(self):
- return dict()
-
- @expose()
- def get(self):
- return dict()
-
- app = TestApp(make_app(ThingsController()))
- r = app.put('/123', status=405)
- assert r.status_int == 405
- assert r.headers['Allow'] == 'GET'
-
- def test_rest_with_utf8_uri(self):
-
- class FooController(RestController):
- key = chr(0x1F330) if PY3 else unichr(0x1F330)
- data = {key: 'Success!'}
-
- @expose()
- def get_one(self, id_):
- return self.data[id_]
-
- @expose()
- def get_all(self):
- return "Hello, World!"
-
- @expose()
- def put(self, id_, value):
- return self.data[id_]
-
- @expose()
- def delete(self, id_):
- return self.data[id_]
-
- class RootController(RestController):
- foo = FooController()
-
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foo/%F0%9F%8C%B0')
- assert r.status_int == 200
- assert r.body == b'Success!'
-
- r = app.put('/foo/%F0%9F%8C%B0', {'value': 'pecans'})
- assert r.status_int == 200
- assert r.body == b'Success!'
-
- r = app.delete('/foo/%F0%9F%8C%B0')
- assert r.status_int == 200
- assert r.body == b'Success!'
-
- r = app.get('/foo/')
- assert r.status_int == 200
- assert r.body == b'Hello, World!'
-
- @unittest.skipIf(not PY3, "test is Python3 specific")
- def test_rest_with_utf8_endpoint(self):
- class ChildController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- class FooController(RestController):
- pass
-
- # okay, so it's technically a chestnut, but close enough...
- setattr(FooController, '🌰', ChildController())
-
- class RootController(RestController):
- foo = FooController()
-
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foo/%F0%9F%8C%B0/')
- assert r.status_int == 200
- assert r.body == b'Hello, World!'
-
-
-class TestExplicitRoute(PecanTestCase):
-
- def test_alternate_route(self):
-
- class RootController(RestController):
-
- @expose(route='some-path')
- def get_all(self):
- return "Hello, World!"
-
- self.assertRaises(
- ValueError,
- RootController
- )
diff --git a/pecan/tests/test_scaffolds.py b/pecan/tests/test_scaffolds.py
deleted file mode 100644
index 669aa7d..0000000
--- a/pecan/tests/test_scaffolds.py
+++ /dev/null
@@ -1,160 +0,0 @@
-import os
-import sys
-import tempfile
-import shutil
-
-from six.moves import cStringIO as StringIO
-
-from pecan.tests import PecanTestCase
-
-if sys.version_info < (2, 7):
- import unittest2 as unittest
-else:
- import unittest # noqa
-
-
-class TestPecanScaffold(PecanTestCase):
-
- def test_normalize_pkg_name(self):
- from pecan.scaffolds import PecanScaffold
- s = PecanScaffold()
- assert s.normalize_pkg_name('sam') == 'sam'
- assert s.normalize_pkg_name('sam1') == 'sam1'
- assert s.normalize_pkg_name('sam_') == 'sam_'
- assert s.normalize_pkg_name('Sam') == 'sam'
- assert s.normalize_pkg_name('SAM') == 'sam'
- assert s.normalize_pkg_name('sam ') == 'sam'
- assert s.normalize_pkg_name(' sam') == 'sam'
- assert s.normalize_pkg_name('sam$') == 'sam'
- assert s.normalize_pkg_name('sam-sam') == 'samsam'
-
-
-class TestScaffoldUtils(PecanTestCase):
-
- def setUp(self):
- super(TestScaffoldUtils, self).setUp()
- self.scaffold_destination = tempfile.mkdtemp()
- self.out = sys.stdout
-
- sys.stdout = StringIO()
-
- def tearDown(self):
- shutil.rmtree(self.scaffold_destination)
- sys.stdout = self.out
-
- def test_copy_dir(self):
- from pecan.scaffolds import PecanScaffold
-
- class SimpleScaffold(PecanScaffold):
- _scaffold_dir = ('pecan', os.path.join(
- 'tests', 'scaffold_fixtures', 'simple'
- ))
-
- SimpleScaffold().copy_to(os.path.join(
- self.scaffold_destination,
- 'someapp'
- ), out_=StringIO())
-
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'someapp', 'foo'
- ))
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'someapp', 'bar', 'spam.txt'
- ))
- assert open(os.path.join(
- self.scaffold_destination, 'someapp', 'foo'
- ), 'r').read().strip() == 'YAR'
- assert open(os.path.join(
- self.scaffold_destination, 'someapp', 'foo'
- ), 'r').read().strip() == 'YAR'
-
- def test_destination_directory_levels_deep(self):
- from pecan.scaffolds import copy_dir
- f = StringIO()
- copy_dir(
- (
- 'pecan', os.path.join('tests', 'scaffold_fixtures', 'simple')
- ),
- os.path.join(self.scaffold_destination, 'some', 'app'),
- {},
- out_=f
- )
-
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'some', 'app', 'foo')
- )
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'some', 'app', 'bar', 'spam.txt')
- )
- assert open(os.path.join(
- self.scaffold_destination, 'some', 'app', 'foo'
- ), 'r').read().strip() == 'YAR'
- assert open(os.path.join(
- self.scaffold_destination, 'some', 'app', 'bar', 'spam.txt'
- ), 'r').read().strip() == 'Pecan'
-
- def test_destination_directory_already_exists(self):
- from pecan.scaffolds import copy_dir
- f = StringIO()
- copy_dir(
- (
- 'pecan', os.path.join('tests', 'scaffold_fixtures', 'simple')
- ),
- os.path.join(self.scaffold_destination),
- {},
- out_=f
- )
- assert 'already exists' in f.getvalue()
-
- def test_copy_dir_with_filename_substitution(self):
- from pecan.scaffolds import copy_dir
- copy_dir(
- (
- 'pecan', os.path.join('tests', 'scaffold_fixtures', 'file_sub')
- ),
- os.path.join(
- self.scaffold_destination, 'someapp'
- ),
- {'package': 'thingy'},
- out_=StringIO()
- )
-
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'someapp', 'foo_thingy')
- )
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'someapp', 'bar_thingy', 'spam.txt')
- )
- assert open(os.path.join(
- self.scaffold_destination, 'someapp', 'foo_thingy'
- ), 'r').read().strip() == 'YAR'
- assert open(os.path.join(
- self.scaffold_destination, 'someapp', 'bar_thingy', 'spam.txt'
- ), 'r').read().strip() == 'Pecan'
-
- def test_copy_dir_with_file_content_substitution(self):
- from pecan.scaffolds import copy_dir
- copy_dir(
- (
- 'pecan',
- os.path.join('tests', 'scaffold_fixtures', 'content_sub'),
- ),
- os.path.join(
- self.scaffold_destination, 'someapp'
- ),
- {'package': 'thingy'},
- out_=StringIO()
- )
-
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'someapp', 'foo')
- )
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'someapp', 'bar', 'spam.txt')
- )
- assert open(os.path.join(
- self.scaffold_destination, 'someapp', 'foo'
- ), 'r').read().strip() == 'YAR thingy'
- assert open(os.path.join(
- self.scaffold_destination, 'someapp', 'bar', 'spam.txt'
- ), 'r').read().strip() == 'Pecan thingy'
diff --git a/pecan/tests/test_secure.py b/pecan/tests/test_secure.py
deleted file mode 100644
index 1d30ea3..0000000
--- a/pecan/tests/test_secure.py
+++ /dev/null
@@ -1,563 +0,0 @@
-import sys
-
-from six import b as b_
-from webtest import TestApp
-
-from pecan import expose, make_app
-from pecan.secure import secure, unlocked, SecureController
-from pecan.tests import PecanTestCase
-
-if sys.version_info < (2, 7):
- import unittest2 as unittest
-else:
- import unittest # noqa
-
-try:
- set()
-except:
- from sets import Set as set
-
-
-class TestSecure(PecanTestCase):
- def test_simple_secure(self):
- authorized = False
-
- class SecretController(SecureController):
- @expose()
- def index(self):
- return 'Index'
-
- @expose()
- @unlocked
- def allowed(self):
- return 'Allowed!'
-
- @classmethod
- def check_permissions(cls):
- return authorized
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- @expose()
- @secure(lambda: False)
- def locked(self):
- return 'No dice!'
-
- @expose()
- @secure(lambda: True)
- def unlocked(self):
- return 'Sure thing'
-
- secret = SecretController()
-
- app = TestApp(make_app(
- RootController(),
- debug=True,
- static_root='tests/static'
- ))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- response = app.get('/unlocked')
- assert response.status_int == 200
- assert response.body == b_('Sure thing')
-
- response = app.get('/locked', expect_errors=True)
- assert response.status_int == 401
-
- response = app.get('/secret/', expect_errors=True)
- assert response.status_int == 401
-
- response = app.get('/secret/allowed')
- assert response.status_int == 200
- assert response.body == b_('Allowed!')
-
- def test_unlocked_attribute(self):
- class AuthorizedSubController(object):
- @expose()
- def index(self):
- return 'Index'
-
- @expose()
- def allowed(self):
- return 'Allowed!'
-
- class SecretController(SecureController):
- @expose()
- def index(self):
- return 'Index'
-
- @expose()
- @unlocked
- def allowed(self):
- return 'Allowed!'
-
- authorized = unlocked(AuthorizedSubController())
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- @expose()
- @secure(lambda: False)
- def locked(self):
- return 'No dice!'
-
- @expose()
- @secure(lambda: True)
- def unlocked(self):
- return 'Sure thing'
-
- secret = SecretController()
-
- app = TestApp(make_app(
- RootController(),
- debug=True,
- static_root='tests/static'
- ))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- response = app.get('/unlocked')
- assert response.status_int == 200
- assert response.body == b_('Sure thing')
-
- response = app.get('/locked', expect_errors=True)
- assert response.status_int == 401
-
- response = app.get('/secret/', expect_errors=True)
- assert response.status_int == 401
-
- response = app.get('/secret/allowed')
- assert response.status_int == 200
- assert response.body == b_('Allowed!')
-
- response = app.get('/secret/authorized/')
- assert response.status_int == 200
- assert response.body == b_('Index')
-
- response = app.get('/secret/authorized/allowed')
- assert response.status_int == 200
- assert response.body == b_('Allowed!')
-
- def test_secure_attribute(self):
- authorized = False
-
- class SubController(object):
- @expose()
- def index(self):
- return 'Hello from sub!'
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello from root!'
-
- sub = secure(SubController(), lambda: authorized)
-
- app = TestApp(make_app(RootController()))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello from root!')
-
- response = app.get('/sub/', expect_errors=True)
- assert response.status_int == 401
-
- authorized = True
- response = app.get('/sub/')
- assert response.status_int == 200
- assert response.body == b_('Hello from sub!')
-
- def test_secured_generic_controller(self):
- authorized = False
-
- class RootController(object):
-
- @classmethod
- def check_permissions(cls):
- return authorized
-
- @expose(generic=True)
- def index(self):
- return 'Index'
-
- @secure('check_permissions')
- @index.when(method='POST')
- def index_post(self):
- return 'I should not be allowed'
-
- @secure('check_permissions')
- @expose(generic=True)
- def secret(self):
- return 'I should not be allowed'
-
- app = TestApp(make_app(
- RootController(),
- debug=True,
- static_root='tests/static'
- ))
- response = app.get('/')
- assert response.status_int == 200
- response = app.post('/', expect_errors=True)
- assert response.status_int == 401
- response = app.get('/secret/', expect_errors=True)
- assert response.status_int == 401
-
- def test_secured_generic_controller_lambda(self):
- authorized = False
-
- class RootController(object):
-
- @expose(generic=True)
- def index(self):
- return 'Index'
-
- @secure(lambda: authorized)
- @index.when(method='POST')
- def index_post(self):
- return 'I should not be allowed'
-
- @secure(lambda: authorized)
- @expose(generic=True)
- def secret(self):
- return 'I should not be allowed'
-
- app = TestApp(make_app(
- RootController(),
- debug=True,
- static_root='tests/static'
- ))
- response = app.get('/')
- assert response.status_int == 200
- response = app.post('/', expect_errors=True)
- assert response.status_int == 401
- response = app.get('/secret/', expect_errors=True)
- assert response.status_int == 401
-
- def test_secured_generic_controller_secure_attribute(self):
- authorized = False
-
- class SecureController(object):
-
- @expose(generic=True)
- def index(self):
- return 'I should not be allowed'
-
- @index.when(method='POST')
- def index_post(self):
- return 'I should not be allowed'
-
- @expose(generic=True)
- def secret(self):
- return 'I should not be allowed'
-
- class RootController(object):
- sub = secure(SecureController(), lambda: authorized)
-
- app = TestApp(make_app(
- RootController(),
- debug=True,
- static_root='tests/static'
- ))
- response = app.get('/sub/', expect_errors=True)
- assert response.status_int == 401
- response = app.post('/sub/', expect_errors=True)
- assert response.status_int == 401
- response = app.get('/sub/secret/', expect_errors=True)
- assert response.status_int == 401
-
- def test_secured_generic_controller_secure_attribute_with_unlocked(self):
-
- class RootController(SecureController):
-
- @unlocked
- @expose(generic=True)
- def index(self):
- return 'Unlocked!'
-
- @unlocked
- @index.when(method='POST')
- def index_post(self):
- return 'Unlocked!'
-
- @expose(generic=True)
- def secret(self):
- return 'I should not be allowed'
-
- app = TestApp(make_app(
- RootController(),
- debug=True,
- static_root='tests/static'
- ))
- response = app.get('/')
- assert response.status_int == 200
- response = app.post('/')
- assert response.status_int == 200
- response = app.get('/secret/', expect_errors=True)
- assert response.status_int == 401
-
- def test_state_attribute(self):
- from pecan.secure import Any, Protected
- assert repr(Any) == '<SecureState Any>'
- assert bool(Any) is False
-
- assert repr(Protected) == '<SecureState Protected>'
- assert bool(Protected) is True
-
- def test_secure_obj_only_failure(self):
- class Foo(object):
- pass
-
- try:
- secure(Foo())
- except Exception as e:
- assert isinstance(e, TypeError)
-
-
-class TestObjectPathSecurity(PecanTestCase):
-
- def setUp(self):
- super(TestObjectPathSecurity, self).setUp()
- permissions_checked = set()
-
- class DeepSecretController(SecureController):
- authorized = False
-
- @expose()
- @unlocked
- def _lookup(self, someID, *remainder):
- if someID == 'notfound':
- return None
- return SubController(someID), remainder
-
- @expose()
- def index(self):
- return 'Deep Secret'
-
- @classmethod
- def check_permissions(cls):
- permissions_checked.add('deepsecret')
- return cls.authorized
-
- class SubController(object):
- def __init__(self, myID):
- self.myID = myID
-
- @expose()
- def index(self):
- return 'Index %s' % self.myID
-
- deepsecret = DeepSecretController()
-
- class SecretController(SecureController):
- authorized = False
- independent_authorization = False
-
- @expose()
- def _lookup(self, someID, *remainder):
- if someID == 'notfound':
- return None
- elif someID == 'lookup_wrapped':
- return self.wrapped, remainder
- return SubController(someID), remainder
-
- @secure('independent_check_permissions')
- @expose()
- def independent(self):
- return 'Independent Security'
-
- wrapped = secure(
- SubController('wrapped'), 'independent_check_permissions'
- )
-
- @classmethod
- def check_permissions(cls):
- permissions_checked.add('secretcontroller')
- return cls.authorized
-
- @classmethod
- def independent_check_permissions(cls):
- permissions_checked.add('independent')
- return cls.independent_authorization
-
- class NotSecretController(object):
- @expose()
- def _lookup(self, someID, *remainder):
- if someID == 'notfound':
- return None
- return SubController(someID), remainder
-
- unlocked = unlocked(SubController('unlocked'))
-
- class RootController(object):
- secret = SecretController()
- notsecret = NotSecretController()
-
- self.deepsecret_cls = DeepSecretController
- self.secret_cls = SecretController
-
- self.permissions_checked = permissions_checked
- self.app = TestApp(make_app(
- RootController(),
- debug=True,
- static_root='tests/static'
- ))
-
- def tearDown(self):
- self.permissions_checked.clear()
- self.secret_cls.authorized = False
- self.deepsecret_cls.authorized = False
-
- def test_sub_of_both_not_secret(self):
- response = self.app.get('/notsecret/hi/')
- assert response.status_int == 200
- assert response.body == b_('Index hi')
-
- def test_protected_lookup(self):
- response = self.app.get('/secret/hi/', expect_errors=True)
- assert response.status_int == 401
-
- self.secret_cls.authorized = True
- response = self.app.get('/secret/hi/')
- assert response.status_int == 200
- assert response.body == b_('Index hi')
- assert 'secretcontroller' in self.permissions_checked
-
- def test_secured_notfound_lookup(self):
- response = self.app.get('/secret/notfound/', expect_errors=True)
- assert response.status_int == 404
-
- def test_secret_through_lookup(self):
- response = self.app.get(
- '/notsecret/hi/deepsecret/', expect_errors=True
- )
- assert response.status_int == 401
-
- def test_layered_protection(self):
- response = self.app.get('/secret/hi/deepsecret/', expect_errors=True)
- assert response.status_int == 401
- assert 'secretcontroller' in self.permissions_checked
-
- self.secret_cls.authorized = True
- response = self.app.get('/secret/hi/deepsecret/', expect_errors=True)
- assert response.status_int == 401
- assert 'secretcontroller' in self.permissions_checked
- assert 'deepsecret' in self.permissions_checked
-
- self.deepsecret_cls.authorized = True
- response = self.app.get('/secret/hi/deepsecret/')
- assert response.status_int == 200
- assert response.body == b_('Deep Secret')
- assert 'secretcontroller' in self.permissions_checked
- assert 'deepsecret' in self.permissions_checked
-
- def test_cyclical_protection(self):
- self.secret_cls.authorized = True
- self.deepsecret_cls.authorized = True
- response = self.app.get('/secret/1/deepsecret/2/deepsecret/')
- assert response.status_int == 200
- assert response.body == b_('Deep Secret')
- assert 'secretcontroller' in self.permissions_checked
- assert 'deepsecret' in self.permissions_checked
-
- def test_unlocked_lookup(self):
- response = self.app.get('/notsecret/1/deepsecret/2/')
- assert response.status_int == 200
- assert response.body == b_('Index 2')
- assert 'deepsecret' not in self.permissions_checked
-
- response = self.app.get(
- '/notsecret/1/deepsecret/notfound/', expect_errors=True
- )
- assert response.status_int == 404
- assert 'deepsecret' not in self.permissions_checked
-
- def test_mixed_protection(self):
- self.secret_cls.authorized = True
- response = self.app.get(
- '/secret/1/deepsecret/notfound/', expect_errors=True
- )
- assert response.status_int == 404
- assert 'secretcontroller' in self.permissions_checked
- assert 'deepsecret' not in self.permissions_checked
-
- def test_independent_check_failure(self):
- response = self.app.get('/secret/independent/', expect_errors=True)
- assert response.status_int == 401
- assert len(self.permissions_checked) == 1
- assert 'independent' in self.permissions_checked
-
- def test_independent_check_success(self):
- self.secret_cls.independent_authorization = True
- response = self.app.get('/secret/independent')
- assert response.status_int == 200
- assert response.body == b_('Independent Security')
- assert len(self.permissions_checked) == 1
- assert 'independent' in self.permissions_checked
-
- def test_wrapped_attribute_failure(self):
- self.secret_cls.independent_authorization = False
- response = self.app.get('/secret/wrapped/', expect_errors=True)
- assert response.status_int == 401
- assert len(self.permissions_checked) == 1
- assert 'independent' in self.permissions_checked
-
- def test_wrapped_attribute_success(self):
- self.secret_cls.independent_authorization = True
- response = self.app.get('/secret/wrapped/')
- assert response.status_int == 200
- assert response.body == b_('Index wrapped')
- assert len(self.permissions_checked) == 1
- assert 'independent' in self.permissions_checked
-
- def test_lookup_to_wrapped_attribute_on_self(self):
- self.secret_cls.authorized = True
- self.secret_cls.independent_authorization = True
- response = self.app.get('/secret/lookup_wrapped/')
- assert response.status_int == 200
- assert response.body == b_('Index wrapped')
- assert len(self.permissions_checked) == 2
- assert 'independent' in self.permissions_checked
- assert 'secretcontroller' in self.permissions_checked
-
- def test_unlocked_attribute_in_insecure(self):
- response = self.app.get('/notsecret/unlocked/')
- assert response.status_int == 200
- assert response.body == b_('Index unlocked')
-
-
-class SecureControllerSharedPermissionsRegression(PecanTestCase):
- """Regression tests for https://github.com/dreamhost/pecan/issues/131"""
-
- def setUp(self):
- super(SecureControllerSharedPermissionsRegression, self).setUp()
-
- class Parent(object):
- @expose()
- def index(self):
- return 'hello'
-
- class UnsecuredChild(Parent):
- pass
-
- class SecureChild(Parent, SecureController):
- @classmethod
- def check_permissions(cls):
- return False
-
- class RootController(object):
-
- secured = SecureChild()
- unsecured = UnsecuredChild()
-
- self.app = TestApp(make_app(RootController()))
-
- def test_inherited_security(self):
- assert self.app.get('/secured/', status=401).status_int == 401
- assert self.app.get('/unsecured/').status_int == 200
diff --git a/pecan/tests/test_templating.py b/pecan/tests/test_templating.py
deleted file mode 100644
index 5fffb0c..0000000
--- a/pecan/tests/test_templating.py
+++ /dev/null
@@ -1,50 +0,0 @@
-import tempfile
-
-from six import b as b_
-
-from pecan.templating import RendererFactory, format_line_context
-from pecan.tests import PecanTestCase
-
-
-class TestTemplate(PecanTestCase):
- def setUp(self):
- super(TestTemplate, self).setUp()
- self.rf = RendererFactory()
-
- def test_available(self):
- self.assertTrue(self.rf.available('json'))
- self.assertFalse(self.rf.available('badrenderer'))
-
- def test_create_bad(self):
- self.assertEqual(self.rf.get('doesnotexist', '/'), None)
-
- def test_extra_vars(self):
- extra_vars = self.rf.extra_vars
- self.assertEqual(extra_vars.make_ns({}), {})
-
- extra_vars.update({'foo': 1})
- self.assertEqual(extra_vars.make_ns({}), {'foo': 1})
-
- def test_update_extra_vars(self):
- extra_vars = self.rf.extra_vars
- extra_vars.update({'foo': 1})
-
- self.assertEqual(extra_vars.make_ns({'bar': 2}), {'foo': 1, 'bar': 2})
- self.assertEqual(extra_vars.make_ns({'foo': 2}), {'foo': 2})
-
-
-class TestTemplateLineFormat(PecanTestCase):
-
- def setUp(self):
- super(TestTemplateLineFormat, self).setUp()
- self.f = tempfile.NamedTemporaryFile()
-
- def tearDown(self):
- del self.f
-
- def test_format_line_context(self):
- for i in range(11):
- self.f.write(b_('Testing Line %d\n' % i))
- self.f.flush()
-
- assert format_line_context(self.f.name, 0).count('Testing Line') == 10
diff --git a/pecan/tests/test_util.py b/pecan/tests/test_util.py
deleted file mode 100644
index 00e81f5..0000000
--- a/pecan/tests/test_util.py
+++ /dev/null
@@ -1,95 +0,0 @@
-import functools
-import inspect
-import unittest
-
-from pecan import expose
-from pecan import util
-
-
-class TestArgSpec(unittest.TestCase):
-
- @property
- def controller(self):
-
- class RootController(object):
-
- @expose()
- def index(self, a, b, c=1, *args, **kwargs):
- return 'Hello, World!'
-
- @staticmethod
- @expose()
- def static_index(a, b, c=1, *args, **kwargs):
- return 'Hello, World!'
-
- return RootController()
-
- def test_no_decorator(self):
- expected = inspect.getargspec(self.controller.index.__func__)
- actual = util.getargspec(self.controller.index.__func__)
- assert expected == actual
-
- expected = inspect.getargspec(self.controller.static_index)
- actual = util.getargspec(self.controller.static_index)
- assert expected == actual
-
- def test_simple_decorator(self):
- def dec(f):
- return f
-
- expected = inspect.getargspec(self.controller.index.__func__)
- actual = util.getargspec(dec(self.controller.index.__func__))
- assert expected == actual
-
- expected = inspect.getargspec(self.controller.static_index)
- actual = util.getargspec(dec(self.controller.static_index))
- assert expected == actual
-
- def test_simple_wrapper(self):
- def dec(f):
- @functools.wraps(f)
- def wrapped(*a, **kw):
- return f(*a, **kw)
- return wrapped
-
- expected = inspect.getargspec(self.controller.index.__func__)
- actual = util.getargspec(dec(self.controller.index.__func__))
- assert expected == actual
-
- expected = inspect.getargspec(self.controller.static_index)
- actual = util.getargspec(dec(self.controller.static_index))
- assert expected == actual
-
- def test_multiple_decorators(self):
- def dec(f):
- @functools.wraps(f)
- def wrapped(*a, **kw):
- return f(*a, **kw)
- return wrapped
-
- expected = inspect.getargspec(self.controller.index.__func__)
- actual = util.getargspec(dec(dec(dec(self.controller.index.__func__))))
- assert expected == actual
-
- expected = inspect.getargspec(self.controller.static_index)
- actual = util.getargspec(dec(dec(dec(
- self.controller.static_index))))
- assert expected == actual
-
- def test_decorator_with_args(self):
- def dec(flag):
- def inner(f):
- @functools.wraps(f)
- def wrapped(*a, **kw):
- return f(*a, **kw)
- return wrapped
- return inner
-
- expected = inspect.getargspec(self.controller.index.__func__)
- actual = util.getargspec(dec(True)(self.controller.index.__func__))
- assert expected == actual
-
- expected = inspect.getargspec(self.controller.static_index)
- actual = util.getargspec(dec(True)(
- self.controller.static_index))
- assert expected == actual
diff --git a/pecan/util.py b/pecan/util.py
deleted file mode 100644
index bdb5d2b..0000000
--- a/pecan/util.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import inspect
-import sys
-
-import six
-
-
-def iscontroller(obj):
- return getattr(obj, 'exposed', False)
-
-
-def getargspec(method):
- """
- Drill through layers of decorators attempting to locate the actual argspec
- for a method.
- """
-
- argspec = inspect.getargspec(method)
- args = argspec[0]
- if args and args[0] == 'self':
- return argspec
- if hasattr(method, '__func__'):
- method = method.__func__
-
- func_closure = six.get_function_closure(method)
-
- # NOTE(sileht): if the closure is None we cannot look deeper,
- # so return actual argspec, this occurs when the method
- # is static for example.
- if func_closure is None:
- return argspec
-
- closure = next(
- (
- c for c in func_closure if six.callable(c.cell_contents)
- ),
- None
- )
- method = closure.cell_contents
- return getargspec(method)
-
-
-def _cfg(f):
- if not hasattr(f, '_pecan'):
- f._pecan = {}
- return f._pecan
-
-
-if sys.version_info >= (2, 6, 5):
- def encode_if_needed(s):
- return s
-else:
- def encode_if_needed(s): # noqa
- return s.encode('utf-8')
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index d38259b..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-WebOb>=1.2dev
-Mako>=0.4.0
-WebTest>=1.3.1
-six
-logutils>=0.3
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index dcd5b92..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-[nosetests]
-match=^test
-where=pecan
-nocapture=1
-cover-package=pecan
-cover-erase=1
-
-[pytest]
-norecursedirs = +package+ config_fixtures docs .git *.egg .tox
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 6500d88..0000000
--- a/setup.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import sys
-import platform
-
-from setuptools import setup, find_packages
-
-version = '0.9.0'
-
-#
-# determine requirements
-#
-with open('requirements.txt') as reqs:
- requirements = [
- line for line in reqs.read().split('\n')
- if (line and not line.startswith('-'))
- ]
-
-try:
- import json # noqa
-except:
- try:
- import simplejson # noqa
- except:
- requirements.append("simplejson >= 2.1.1")
-
-try:
- import argparse # noqa
-except:
- #
- # This was introduced in Python 2.7 - the argparse package contains
- # a backported replacement for 2.6
- #
- requirements.append('argparse')
-
-try:
- from functools import singledispatch # noqa
-except:
- #
- # This was introduced in Python 3.4 - the singledispatch package contains
- # a backported replacement for 2.6 through 3.4
- #
- requirements.append('singledispatch')
- try:
- from collections import OrderedDict
- except:
- requirements.append('ordereddict')
-
-
-tests_require = requirements + [
- 'virtualenv',
- 'Jinja2',
- 'gunicorn',
- 'mock',
- 'sqlalchemy'
-]
-if sys.version_info < (2, 7):
- tests_require += ['unittest2']
-
-if sys.version_info < (3, 0):
- # These don't support Python3 yet - don't run their tests
- if platform.python_implementation() != 'PyPy':
- # Kajiki is not pypy-compatible
- tests_require += ['Kajiki']
- tests_require += ['Genshi']
-else:
- # Genshi added Python3 support in 0.7
- tests_require += ['Genshi>=0.7']
-
-#
-# call setup
-#
-setup(
- name='pecan',
- version=version,
- description="A WSGI object-dispatching web framework, designed to be "
- "lean and fast, with few dependencies.",
- long_description=None,
- classifiers=[
- 'Development Status :: 5 - Production/Stable',
- 'Environment :: Console',
- 'Environment :: Web Environment',
- 'Intended Audience :: Developers',
- 'Intended Audience :: System Administrators',
- 'License :: OSI Approved :: BSD License',
- 'Operating System :: MacOS :: MacOS X',
- 'Operating System :: Microsoft :: Windows',
- 'Operating System :: POSIX',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.6',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.3',
- 'Programming Language :: Python :: 3.4',
- 'Programming Language :: Python :: Implementation :: PyPy',
- 'Topic :: Internet :: WWW/HTTP :: WSGI',
- 'Topic :: Software Development :: Libraries :: Application Frameworks'
- ],
- keywords='web framework wsgi object-dispatch http',
- author='Jonathan LaCour',
- author_email='info@pecanpy.org',
- url='http://github.com/stackforge/pecan',
- license='BSD',
- packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
- include_package_data=True,
- scripts=['bin/pecan'],
- zip_safe=False,
- install_requires=requirements,
- tests_require=tests_require,
- test_suite='pecan',
- entry_points="""
- [pecan.command]
- serve = pecan.commands:ServeCommand
- shell = pecan.commands:ShellCommand
- create = pecan.commands:CreateCommand
- [pecan.scaffold]
- base = pecan.scaffolds:BaseScaffold
- rest-api = pecan.scaffolds:RestAPIScaffold
- [console_scripts]
- pecan = pecan.commands:CommandRunner.handle_command_line
- gunicorn_pecan = pecan.commands.serve:gunicorn_run
- """
-)
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index 593f2ad..0000000
--- a/tox.ini
+++ /dev/null
@@ -1,229 +0,0 @@
-[tox]
-envlist = py26,py27,py33,py34,scaffolds-26,scaffolds-27,scaffolds-33,scaffolds-34,scaffolds-26-rest-api,scaffolds-27-rest-api,scaffolds-33-rest-api,scaffolds-34-rest-api,pep8
-
-[testenv]
-commands={envpython} setup.py test -v {posargs}
-
-[testenv:scaffolds-base]
-deps = pep8
- gunicorn
- uwsgi
-
-[testenv:scaffolds-26]
-basepython = python2.6
-deps = {[testenv:scaffolds-base]deps}
- unittest2
-changedir={envdir}/tmp
-commands=pecan create testing123
- {envpython} testing123/setup.py install
- {envpython} testing123/setup.py test -q
- pep8 --repeat --show-source testing123/setup.py testing123/testing123
- {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
-
-[testenv:scaffolds-26-rest-api]
-basepython = python2.6
-deps = {[testenv:scaffolds-base]deps}
- unittest2
-changedir={envdir}/tmp
-commands=pecan create testing123 rest-api
- {envpython} testing123/setup.py install
- {envpython} testing123/setup.py test -q
- pep8 --repeat --show-source testing123/setup.py testing123/testing123
- {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
-
-[testenv:scaffolds-27]
-basepython = python2.7
-deps = {[testenv:scaffolds-base]deps}
-changedir={[testenv:scaffolds-26]changedir}
-commands=pecan create testing123
- {envpython} testing123/setup.py install
- {envpython} testing123/setup.py test -q
- pep8 --repeat --show-source testing123/setup.py testing123/testing123
- {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
-
-[testenv:scaffolds-27-rest-api]
-basepython = python2.7
-deps = {[testenv:scaffolds-base]deps}
-changedir={[testenv:scaffolds-26]changedir}
-commands=pecan create testing123 rest-api
- {envpython} testing123/setup.py install
- {envpython} testing123/setup.py test -q
- pep8 --repeat --show-source testing123/setup.py testing123/testing123
- {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
-
-[testenv:scaffolds-33]
-basepython = python3.3
-deps = {[testenv:scaffolds-base]deps}
-changedir={[testenv:scaffolds-26]changedir}
-commands=pecan create testing123
- {envpython} testing123/setup.py install
- {envpython} testing123/setup.py test -q
- pep8 --repeat --show-source testing123/setup.py testing123/testing123
- {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
-
-[testenv:scaffolds-33-rest-api]
-basepython = python3.3
-deps = {[testenv:scaffolds-base]deps}
-changedir={[testenv:scaffolds-26]changedir}
-commands=pecan create testing123 rest-api
- {envpython} testing123/setup.py install
- {envpython} testing123/setup.py test -q
- pep8 --repeat --show-source testing123/setup.py testing123/testing123
- {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
-
-[testenv:scaffolds-34]
-basepython = python3.4
-deps = {[testenv:scaffolds-base]deps}
-changedir={[testenv:scaffolds-26]changedir}
-commands=pecan create testing123
- {envpython} testing123/setup.py install
- {envpython} testing123/setup.py test -q
- pep8 --repeat --show-source testing123/setup.py testing123/testing123
- {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
-
-[testenv:scaffolds-34-rest-api]
-basepython = python3.4
-deps = {[testenv:scaffolds-base]deps}
-changedir={[testenv:scaffolds-26]changedir}
-commands=pecan create testing123 rest-api
- {envpython} testing123/setup.py install
- {envpython} testing123/setup.py test -q
- pep8 --repeat --show-source testing123/setup.py testing123/testing123
- {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
-
-[testenv:wsme-stable]
-basepython = python2.7
-deps = nose
- ipaddr
- simplegeneric
-changedir = {envdir}/src
-commands = pip install --download {envdir}/src --pre --no-deps --no-clean --no-use-wheel wsme
- sh -c "find -iname 'wsme*.tar.gz' | xargs tar --strip-components 1 -xf"
- {envdir}/bin/pip install -U {toxinidir} # install pecan-dev
- {envdir}/bin/pip install .
- nosetests -v tests/pecantest
-
-[testenv:wsme-tip]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/stackforge/wsme#egg=wsme
- nose
-changedir = {envdir}/src/wsme
-commands = nosetests -v tests/pecantest
-
-[testenv:ceilometer-stable]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/openstack/ceilometer@stable/kilo#egg=ceilometer
-changedir = {envdir}/src/ceilometer
-commands = tox -e py27 --notest # ensure a virtualenv is built
- {envdir}/src/ceilometer/.tox/py27/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27
-
-[testenv:ceilometer-tip]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/openstack/ceilometer#egg=ceilometer
-changedir = {envdir}/src/ceilometer
-commands = tox -e py27 --notest # ensure a virtualenv is built
- {envdir}/src/ceilometer/.tox/py27/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27
-
-[testenv:ironic-stable]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/openstack/ironic@stable/kilo#egg=ironic
-changedir = {envdir}/src/ironic
-commands = tox -e py27 --notest # ensure a virtualenv is built
- {envdir}/src/ironic/.tox/py27/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27
-
-[testenv:ironic-tip]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/openstack/ironic#egg=ironic
-changedir = {envdir}/src/ironic
-commands = tox -e py27 --notest # ensure a virtualenv is built
- {envdir}/src/ironic/.tox/py27/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27
-
-[testenv:barbican-stable]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/openstack/barbican@stable/kilo#egg=barbican
-changedir = {envdir}/src/barbican
-commands = tox -e py27 --notest # ensure a virtualenv is built
- {envdir}/src/barbican/.tox/py27/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27
-
-[testenv:barbican-tip]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/openstack/barbican#egg=barbican
-changedir = {envdir}/src/barbican
-commands = tox -e py27 --notest # ensure a virtualenv is built
- {envdir}/src/barbican/.tox/py27/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27
-
-[testenv:designate-stable]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/openstack/designate@stable/kilo#egg=designate
-changedir = {envdir}/src/designate
-commands = tox -e py27 --notest # ensure a virtualenv is built
- {envdir}/src/designate/.tox/py27/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27
-
-[testenv:designate-tip]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/openstack/designate#egg=designate
-changedir = {envdir}/src/designate
-commands = tox -e py27 --notest # ensure a virtualenv is built
- {envdir}/src/designate/.tox/py27/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27
-
-[testenv:magnum-stable]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/openstack/magnum@stable/kilo#egg=magnum
-changedir = {envdir}/src/magnum
-commands = tox -e py27 --notest # ensure a virtualenv is built
- {envdir}/src/magnum/.tox/py27/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27
-
-[testenv:magnum-tip]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/openstack/magnum#egg=magnum
-changedir = {envdir}/src/magnum
-commands = tox -e py27 --notest # ensure a virtualenv is built
- {envdir}/src/magnum/.tox/py27/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27
-
-[testenv:gnocchi-stable]
-whitelist_externals = sh
-basepython = python2.7
-changedir = {envdir}/src
-commands = pip install --download {envdir}/src --pre --no-deps --no-clean --no-use-wheel gnocchi
- sh -c "find -iname 'gnocchi*.tar.gz' | xargs tar --strip-components 1 -xf"
- tox -e py27-mysql-file --notest
- {envdir}/src/.tox/py27-mysql-file/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27-mysql-file
-
-[testenv:gnocchi-tip]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/openstack/gnocchi#egg=gnocchi
-changedir = {envdir}/src/gnocchi
-commands = tox -e py27-mysql-file --notest # ensure a virtualenv is built
- {envdir}/src/gnocchi/.tox/py27-mysql-file/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27-mysql-file
-
-[testenv:storyboard-tip]
-basepython = python2.7
-deps = -egit+http://git.openstack.org/openstack-infra/storyboard#egg=storyboard
-changedir = {envdir}/src/storyboard
-commands = tox -e py27 --notest # ensure a virtualenv is built
- {envdir}/src/storyboard/.tox/py27/bin/pip install -U {toxinidir} # install pecan-dev
- tox -e py27
-
-[testenv:pep8]
-deps = pep8
-commands = pep8 --repeat --show-source pecan setup.py --ignore=E402
-
-# Generic environment for running commands like packaging
-[testenv:venv]
-commands={posargs}
-
-[testenv:docs]
-deps = sphinx
-commands = python setup.py build_sphinx