summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml1
-rw-r--r--CHANGES.txt29
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--HACKING.txt2
-rw-r--r--README.rst4
-rw-r--r--docs/conf.py2
-rw-r--r--docs/designdefense.rst2
-rw-r--r--docs/glossary.rst2
-rw-r--r--docs/index.rst1
-rw-r--r--docs/narr/resources.rst2
-rw-r--r--docs/narr/sessions.rst92
-rw-r--r--docs/tutorials/pycharm/images/create_new_project.pngbin0 -> 118472 bytes
-rw-r--r--docs/tutorials/pycharm/images/create_setup.pngbin0 -> 123209 bytes
-rw-r--r--docs/tutorials/pycharm/images/create_virtual_environment.pngbin0 -> 42991 bytes
-rw-r--r--docs/tutorials/pycharm/images/edit_run_debug_configurations.pngbin0 -> 106932 bytes
-rw-r--r--docs/tutorials/pycharm/images/install_package.pngbin0 -> 69633 bytes
-rw-r--r--docs/tutorials/pycharm/images/install_package_pyramid.pngbin0 -> 125125 bytes
-rw-r--r--docs/tutorials/pycharm/images/install_package_setuptools.pngbin0 -> 107886 bytes
-rw-r--r--docs/tutorials/pycharm/images/python_interpreters_1.pngbin0 -> 130060 bytes
-rw-r--r--docs/tutorials/pycharm/images/python_interpreters_2.pngbin0 -> 130852 bytes
-rw-r--r--docs/tutorials/pycharm/images/run_configuration.pngbin0 -> 57129 bytes
-rw-r--r--docs/tutorials/pycharm/images/start_up_screen.pngbin0 -> 70500 bytes
-rw-r--r--docs/tutorials/pycharm/index.rst355
-rw-r--r--pyramid/chameleon_text.py3
-rw-r--r--pyramid/chameleon_zpt.py3
-rw-r--r--pyramid/config/assets.py38
-rw-r--r--pyramid/config/routes.py4
-rw-r--r--pyramid/interfaces.py44
-rw-r--r--pyramid/mako_templating.py35
-rw-r--r--pyramid/router.py1
-rw-r--r--pyramid/scaffolds/__init__.py18
-rw-r--r--pyramid/scripts/prequest.py42
-rw-r--r--pyramid/session.py26
-rw-r--r--pyramid/testing.py6
-rw-r--r--pyramid/tests/pkgs/viewdecoratorapp/views/templates/foo.pt (renamed from pyramid/tests/pkgs/viewdecoratorapp/views/templates/foo.mak)0
-rw-r--r--pyramid/tests/pkgs/viewdecoratorapp/views/views.py4
-rw-r--r--pyramid/tests/test_config/test_assets.py96
-rw-r--r--pyramid/tests/test_integration.py10
-rw-r--r--pyramid/tests/test_mako_templating.py23
-rw-r--r--pyramid/tests/test_scripts/test_prequest.py69
-rw-r--r--pyramid/tests/test_session.py21
-rw-r--r--pyramid/tests/test_testing.py4
-rw-r--r--pyramid/url.py2
44 files changed, 848 insertions, 97 deletions
diff --git a/.gitignore b/.gitignore
index 5fa2a2ee4..8dca2069c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,7 @@
.coverage
.tox/
nosetests.xml
-pyramid/coverage.xml
+coverage.xml
tutorial.db
build/
dist/
diff --git a/.travis.yml b/.travis.yml
index ab9c3ff30..00c293046 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,4 @@
+# Wire up travis
language: python
python:
diff --git a/CHANGES.txt b/CHANGES.txt
index e6dd9f0cb..0156b24fd 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,12 @@ next release
Features
--------
+- ``scripts/prequest.py``: add support for submitting ``PUT`` and ``PATCH``
+ requests. See https://github.com/Pylons/pyramid/pull/1033. add support for
+ submitting ``OPTIONS`` and ``PROPFIND`` requests, and allow users to specify
+ basic authentication credentials in the request via a ``--login`` argument to
+ the script. See https://github.com/Pylons/pyramid/pull/1039.
+
- ``ACLAuthorizationPolicy`` supports ``__acl__`` as a callable. This
removes the ambiguity between the potential ``AttributeError`` that would
be raised on the ``context`` when the property was not defined and the
@@ -29,9 +35,27 @@ Features
``initialize_myapp_db etc/development.ini a=1 b=2``.
See https://github.com/Pylons/pyramid/pull/911
+- The ``request.session.check_csrf_token()`` method and the ``check_csrf`` view
+ predicate now take into account the value of the HTTP header named
+ ``X-CSRF-Token`` (as well as the ``csrf_token`` form parameter, which they
+ always did). The header is tried when the form parameter does not exist.
+
Bug Fixes
---------
+- Make the ``pyramid.config.assets.PackageOverrides`` object implement the API
+ for ``__loader__`` objects specified in PEP 302. Proxies to the
+ ``__loader__`` set by the importer, if present; otherwise, raises
+ ``NotImplementedError``. This makes Pyramid static view overrides work
+ properly under Python 3.3 (previously they would not). See
+ https://github.com/Pylons/pyramid/pull/1015 for more information.
+
+- ``mako_templating``: added defensive workaround for non-importability of
+ ``mako`` due to upstream ``markupsafe`` dropping Python 3.2 support. Mako
+ templating will no longer work under the combination of MarkupSafe 0.17 and
+ Python 3.2 (although the combination of MarkupSafe 0.17 and Python 3.3 or any
+ supported Python 2 version will work OK).
+
- View lookup will now search for valid views based on the inheritance
hierarchy of the context. It tries to find views based on the most
specific context first, and upon predicate failure, will move up the
@@ -93,6 +117,11 @@ Bug Fixes
files have now been removed. See
https://github.com/Pylons/pyramid/issues/981
+- ``pyramid.testing.DummyResource`` didn't define ``__bool__``, so code under
+ Python 3 would use ``__len__`` to find truthiness; this usually caused an
+ instance of DummyResource to be "falsy" instead of "truthy". See
+ https://github.com/Pylons/pyramid/pull/1032
+
1.4 (2012-12-18)
================
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 7cd7123c5..d2b116f46 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -199,4 +199,6 @@ Contributors
- Jason McKellar, 2013/03/28
+- Luke Cyca, 2013/05/30
+
- Laurence Rowe, 2013/04/24
diff --git a/HACKING.txt b/HACKING.txt
index 26e85ee80..5b5dcc458 100644
--- a/HACKING.txt
+++ b/HACKING.txt
@@ -126,7 +126,7 @@ documentation in this package which references that API or behavior must
change to reflect the bug fix, ideally in the same commit that fixes the bug
or adds the feature.
-To build and review docs (where ``$yourvenv`` refers to the virtualenv you're
+To build and review docs (where ``$VENV`` refers to the virtualenv you're
using to develop Pyramid):
1. Run ``$VENV/bin/python setup.py dev docs``. This will cause Sphinx
diff --git a/README.rst b/README.rst
index 4d427a13d..a3458028b 100644
--- a/README.rst
+++ b/README.rst
@@ -1,8 +1,8 @@
Pyramid
=======
-Pyramid is a small, fast, down-to-earth, open source Python web application
-development framework. It makes real-world web application development and
+Pyramid is a small, fast, down-to-earth, open source Python web framework.
+It makes real-world web application development and
deployment more fun, more predictable, and more productive.
Pyramid is produced by the `Pylons Project <http://pylonsproject.org/>`_.
diff --git a/docs/conf.py b/docs/conf.py
index 67e02471f..84b01a791 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -170,7 +170,7 @@ html_theme_path = ['_themes']
html_theme = 'pyramid'
html_theme_options = dict(
github_url='https://github.com/Pylons/pyramid',
-# in_progress='true',
+ in_progress='true',
)
# The style sheet to use for HTML and HTML Help pages. A file of that name
# must exist either in Sphinx' static/ path, or in one of the custom paths
diff --git a/docs/designdefense.rst b/docs/designdefense.rst
index fdc57e0c1..ea46053a0 100644
--- a/docs/designdefense.rst
+++ b/docs/designdefense.rst
@@ -1691,7 +1691,7 @@ some sort of monolithic thing, and a lot of its software is usable
externally. And while it's not really the job of this document to defend it,
Zope has been around for over 10 years and has an incredibly large, active
community. If you don't believe this,
-http://taichino.appspot.com/pypi_ranking/authors is an eye-opening reality
+http://pypi-ranking.info/author is an eye-opening reality
check.
Love Simplicity
diff --git a/docs/glossary.rst b/docs/glossary.rst
index b6bd35ffe..abc37c7f8 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -698,7 +698,7 @@ Glossary
:app:`Pyramid` runs on GAE.
Venusian
- `Venusian <http://docs.repoze.org/venusian>`_ is a library which
+ :ref:`Venusian` is a library which
allows framework authors to defer decorator actions. Instead of
taking actions when a function (or class) decorator is executed
at import time, the action usually taken by the decorator is
diff --git a/docs/index.rst b/docs/index.rst
index bc711f8ff..93b550d60 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -108,6 +108,7 @@ platforms.
tutorials/wiki/index.rst
tutorials/bfg/index.rst
tutorials/modwsgi/index.rst
+ tutorials/pycharm/index.rst
API Documentation
==================
diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst
index 699a3d4ac..b1bb611e5 100644
--- a/docs/narr/resources.rst
+++ b/docs/narr/resources.rst
@@ -300,7 +300,7 @@ the resource by :meth:`~pyramid.request.Request.resource_url`.
The ``__resource_url__`` hook is passed two arguments: ``request`` and
``info``. ``request`` is the :term:`request` object passed to
:meth:`~pyramid.request.Request.resource_url`. ``info`` is a dictionary with
-two keys:
+the following keys:
``physical_path``
A string representing the "physical path" computed for the resource, as
diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst
index c4f4b5f07..7ec280c8a 100644
--- a/docs/narr/sessions.rst
+++ b/docs/narr/sessions.rst
@@ -298,14 +298,15 @@ Preventing Cross-Site Request Forgery Attacks
`Cross-site request forgery
<http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a
-phenomenon whereby a user with an identity on your website might click on a
-URL or button on another website which secretly redirects the user to your
-application to perform some command that requires elevated privileges.
-
-You can avoid most of these attacks by making sure that the correct *CSRF
-token* has been set in an :app:`Pyramid` session object before performing any
-actions in code which requires elevated privileges that is invoked via a form
-post. To use CSRF token support, you must enable a :term:`session factory`
+phenomenon whereby a user who is logged in to your website might inadvertantly
+load a URL because it is linked from, or embedded in, an attacker's website.
+If the URL is one that may modify or delete data, the consequences can be dire.
+
+You can avoid most of these attacks by issuing a unique token to the browser
+and then requiring that it be present in all potentially unsafe requests.
+:app:`Pyramid` sessions provide facilities to create and check CSRF tokens.
+
+To use CSRF tokens, you must first enable a :term:`session factory`
as described in :ref:`using_the_default_session_factory` or
:ref:`using_alternate_session_factories`.
@@ -324,33 +325,82 @@ To get the current CSRF token from the session, use the
The ``session.get_csrf_token()`` method accepts no arguments. It returns a
CSRF *token* string. If ``session.get_csrf_token()`` or
-``session.new_csrf_token()`` was invoked previously for this session, the
+``session.new_csrf_token()`` was invoked previously for this session, then the
existing token will be returned. If no CSRF token previously existed for
-this session, a new token will be will be set into the session and returned.
+this session, then a new token will be will be set into the session and returned.
The newly created token will be opaque and randomized.
You can use the returned token as the value of a hidden field in a form that
-posts to a method that requires elevated privileges. The handler for the
-form post should use ``session.get_csrf_token()`` *again* to obtain the
-current CSRF token related to the user from the session, and compare it to
-the value of the hidden form field. For example, if your form rendering
-included the CSRF token obtained via ``session.get_csrf_token()`` as a hidden
-input field named ``csrf_token``:
+posts to a method that requires elevated privileges, or supply it as a request
+header in AJAX requests.
+
+For example, include the CSRF token as a hidden field:
+
+.. code-block:: html
+
+ <form method="post" action="/myview">
+ <input type="hidden" name="csrf_token" value="${request.session.get_csrf_token()}">
+ <input type="submit" value="Delete Everything">
+ </form>
+
+Or, include it as a header in a jQuery AJAX request:
+
+.. code-block:: javascript
+
+ var csrfToken = ${request.session.get_csrf_token()};
+ $.ajax({
+ type: "POST",
+ url: "/myview",
+ headers: { 'X-CSRF-Token': csrfToken }
+ }).done(function() {
+ alert("Deleted");
+ });
+
+
+The handler for the URL that receives the request
+should then require that the correct CSRF token is supplied.
+
+Using the ``session.check_csrf_token`` Method
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In request handling code, you can check the presence and validity of a CSRF
+token with ``session.check_csrf_token(request)``. If the token is valid,
+it will return True, otherwise it will raise ``HTTPBadRequest``.
+
+By default, it checks for a GET or POST parameter named ``csrf_token`` or a
+header named ``X-CSRF-Token``.
.. code-block:: python
- :linenos:
- token = request.session.get_csrf_token()
- if token != request.POST['csrf_token']:
- raise ValueError('CSRF token did not match')
+ def myview(request):
+ session = request.session
+
+ # Require CSRF Token
+ session.check_csrf_token(request):
+
+ ...
.. index::
single: session.new_csrf_token
+Checking CSRF Tokens With A View Predicate
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A convenient way to require a valid CSRF Token for a particular view is to
+include ``check_csrf=True`` as a view predicate.
+See :meth:`pyramid.config.Configurator.add_route`.
+
+.. code-block:: python
+
+ @view_config(request_method='POST', check_csrf=True, ...)
+ def myview(request):
+ ...
+
+
Using the ``session.new_csrf_token`` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-To explicitly add a new CSRF token to the session, use the
+To explicitly create a new CSRF token, use the
``session.new_csrf_token()`` method. This differs only from
``session.get_csrf_token()`` inasmuch as it clears any existing CSRF token,
creates a new CSRF token, sets the token into the session, and returns the
diff --git a/docs/tutorials/pycharm/images/create_new_project.png b/docs/tutorials/pycharm/images/create_new_project.png
new file mode 100644
index 000000000..f15068b65
--- /dev/null
+++ b/docs/tutorials/pycharm/images/create_new_project.png
Binary files differ
diff --git a/docs/tutorials/pycharm/images/create_setup.png b/docs/tutorials/pycharm/images/create_setup.png
new file mode 100644
index 000000000..de4cb364b
--- /dev/null
+++ b/docs/tutorials/pycharm/images/create_setup.png
Binary files differ
diff --git a/docs/tutorials/pycharm/images/create_virtual_environment.png b/docs/tutorials/pycharm/images/create_virtual_environment.png
new file mode 100644
index 000000000..0bd3c9263
--- /dev/null
+++ b/docs/tutorials/pycharm/images/create_virtual_environment.png
Binary files differ
diff --git a/docs/tutorials/pycharm/images/edit_run_debug_configurations.png b/docs/tutorials/pycharm/images/edit_run_debug_configurations.png
new file mode 100644
index 000000000..7708fa9dc
--- /dev/null
+++ b/docs/tutorials/pycharm/images/edit_run_debug_configurations.png
Binary files differ
diff --git a/docs/tutorials/pycharm/images/install_package.png b/docs/tutorials/pycharm/images/install_package.png
new file mode 100644
index 000000000..944a05f6a
--- /dev/null
+++ b/docs/tutorials/pycharm/images/install_package.png
Binary files differ
diff --git a/docs/tutorials/pycharm/images/install_package_pyramid.png b/docs/tutorials/pycharm/images/install_package_pyramid.png
new file mode 100644
index 000000000..05a209b6c
--- /dev/null
+++ b/docs/tutorials/pycharm/images/install_package_pyramid.png
Binary files differ
diff --git a/docs/tutorials/pycharm/images/install_package_setuptools.png b/docs/tutorials/pycharm/images/install_package_setuptools.png
new file mode 100644
index 000000000..8932a3f40
--- /dev/null
+++ b/docs/tutorials/pycharm/images/install_package_setuptools.png
Binary files differ
diff --git a/docs/tutorials/pycharm/images/python_interpreters_1.png b/docs/tutorials/pycharm/images/python_interpreters_1.png
new file mode 100644
index 000000000..6b1455001
--- /dev/null
+++ b/docs/tutorials/pycharm/images/python_interpreters_1.png
Binary files differ
diff --git a/docs/tutorials/pycharm/images/python_interpreters_2.png b/docs/tutorials/pycharm/images/python_interpreters_2.png
new file mode 100644
index 000000000..61c3de2b1
--- /dev/null
+++ b/docs/tutorials/pycharm/images/python_interpreters_2.png
Binary files differ
diff --git a/docs/tutorials/pycharm/images/run_configuration.png b/docs/tutorials/pycharm/images/run_configuration.png
new file mode 100644
index 000000000..4612b2b3c
--- /dev/null
+++ b/docs/tutorials/pycharm/images/run_configuration.png
Binary files differ
diff --git a/docs/tutorials/pycharm/images/start_up_screen.png b/docs/tutorials/pycharm/images/start_up_screen.png
new file mode 100644
index 000000000..c65e01eeb
--- /dev/null
+++ b/docs/tutorials/pycharm/images/start_up_screen.png
Binary files differ
diff --git a/docs/tutorials/pycharm/index.rst b/docs/tutorials/pycharm/index.rst
new file mode 100644
index 000000000..f2e3b7bcb
--- /dev/null
+++ b/docs/tutorials/pycharm/index.rst
@@ -0,0 +1,355 @@
+**************************
+Using PyCharm with Pyramid
+**************************
+
+This tutorial is a very brief overview of how to use PyCharm with Pyramid.
+`PyCharm <http://www.jetbrains.com/pycharm/>`_ is an Integrated Development
+Environment (IDE) for Python programmers. It has numerous features including
+code completion, project management, version control system (git, Subversion,
+etc.), debugger, and more.
+
+This tutorial is a continual evolving document. Both PyCharm and Pyramid are
+under active development, and changes to either may necessitate changes to
+this document. In addition, there may be errors or omissions in this
+document, and corrections and improvements through a pull request are most
+welcome.
+
+To get started with Pyramid in PyCharm, we need to install prerequisite
+software.
+
+* Python
+* PyCharm and certain Python packages
+* Pyramid and its requirements
+
+Install Python
+==============
+
+You can download installers for Mac OS X and Windows, or source tarballs for
+Linux, Unix, or Mac OS X from `python.org Download
+<http://python.org/download/>`_. Follow the instructions in the README files.
+
+Install PyCharm
+===============
+
+PyCharm is a commercial application that requires a license. Several license
+types are available depending on your usage.
+
+Pyramid is an open source project, and on an annual basis fulfills the terms of
+the Open Source License with JetBrains for the use of PyCharm to develop for
+Pyramid under the Pylons Project. If you contribute to Pyramid or the Pylons
+Project, and would like to use our 1-year license, please contact the license
+maintainer `stevepiercy` in the `#pyramid` channel on `irc.freenode.net`.
+
+Alternatively you can download a 30-day trial of PyCharm or `purchase a license
+<http://www.jetbrains.com/pycharm/buy/index.jsp>`_ for development or training
+purposes under any other license.
+
+`Download PyCharm <http://www.jetbrains.com/pycharm/download/index.html>`_ and
+follow the installation instructions on that web page.
+
+Configure PyCharm
+=================
+
+Create a New Project
+--------------------
+
+Launch the PyCharm application.
+
+From the Start Up screen, click Create New Project.
+
+.. image:: images/start_up_screen.png
+
+If the Start Up screen does not appear, you probably have an existing project
+open. Close the existing project and the Start Up screen will appear.
+
+.. image:: images/create_new_project.png
+
+In the Create New Project dialog window do the following.
+
+* Enter a Project name. The Location should automatically populate as you
+ type. You can change the path as you wish. It is common practice to use the
+ path `~/projects/` to contain projects. This location shall be referred to
+ as your "project directory" throughout the rest of this document.
+* Project type should be Empty project.
+* For Interpreter, click the ellipsis button to create a new virtual
+ environment.
+
+A new window appears, "Python Interpreters".
+
+Create or Select a Python Interpreter
+-------------------------------------
+
+.. image:: images/python_interpreters_1.png
+
+* Either click the `+` button to add a new Python interpreter for Python
+ 2.7 (the Python 2.7 installer uses the path
+ `/Library/Frameworks/Python.framework/Versions/2.7/bin`), or use an existing
+ Python interpreter for Python 2.7. PyCharm will take a few seconds to add a
+ new interpreter.
+
+.. image:: images/python_interpreters_2.png
+
+Create a Virtual Environment
+----------------------------
+
+* Click the button with the Python logo and a green "V". A new window appears,
+ "Create Virtual Environment".
+
+.. image:: images/create_virtual_environment.png
+
+* Enter a Virtual Environment name.
+* The Location should automatically populate as you type. You can change the
+ path as you wish.
+* The Base interpreter should be already selected, but if not, select
+ `/Library/Frameworks/Python.framework/Versions/2.7/bin` or other Python 2.7
+ interpreter.
+* Leave the box unchecked for "Inherit global site packages".
+* Click "OK". PyCharm will set up libraries and packages, and return you to
+ the Python Interpreters window.
+
+Install setuptools and pyramid Packages
+---------------------------------------
+
+If you already have setuptools installed, you can skip this step.
+
+In the Python Interpreters window with the just-created virtual environment
+selected in the top pane, in the lower pane select the Packages tab, and click
+the Install button. The Available Packages window appears.
+
+.. image:: images/install_package.png
+
+In the Available Packages window, in the search bar, enter "setuptools".
+Select the plain old "setuptools" package, and click the Install Package button
+and wait for the status message to disappear. PyCharm will install the package
+and any dependencies.
+
+.. image:: images/install_package_setuptools.png
+
+Repeat the previous step, except use "pyramid" for searching and selecting.
+
+.. image:: images/install_package_pyramid.png
+
+When PyCharm finishes installing the packages, close the Available Packages
+window.
+
+In the Python Interpreters window, click the OK button.
+
+In the Create New Project window, click the OK button.
+
+If PyCharm displays a warning, click the Yes button. PyCharm opens the new
+project.
+
+Clone the Pyramid repository
+============================
+
+By cloning the Pyramid repository, you can contribute changes to the code or
+documentation. We recommend that you fork the Pyramid repository to your own
+GitHub account, then clone your forked repository, so that you can commit your
+changes to your GitHub repository and submit pull requests to the Pyramid
+project.
+
+In PyCharm, select *VCS > Enable Version Control Integration...*, then select
+Git as your VCS and click the OK button.
+
+See `Cloning a Repository from GitHub <http://www.jetbrains.com/pycharm/webhelp/cloning-a-repository-from-github.html>`_
+in the PyCharm documentation for more information on using GitHub and git in
+PyCharm.
+
+We will refer to the cloned repository of Pyramid on your computer as your
+"local Pyramid repository".
+
+Install development and documentation requirements
+==================================================
+
+In order to contribute bug fixes, features, and documentation changes to
+Pyramid, you must install development and documentation requirements into your
+virtual environment. Pyramid uses Sphinx and reStructuredText for
+documentation.
+
+* In PyCharm, select *Run > Edit Configurations...*. The Run/Debug
+ Configurations window appears.
+
+ .. image:: images/edit_run_debug_configurations.png
+
+* Click the "+" button, then select Python to add a new Python run
+ configuration.
+* Name the configuration "setup dev".
+* Either manually enter the path to the `setup.py` script or click the ellipsis
+ button to navigate to the `pyramid/setup.py` path and select it.
+* For Script parameters enter `dev`.
+* Click the "Apply" button to save the run configuration.
+
+While we're here, let's duplicate this run configuration for installing the
+documentation requirements.
+
+* Click the "Copy Configuration" button. Its icon looks like two dog-eared
+ pages, with a blue page on top of a grey page.
+* Name the configuration "setup docs".
+* Leave the path as is.
+* For Script parameters enter `docs`.
+* Click the "Apply" button to save the run configuration.
+* Click the "OK" button to return to the project window.
+
+In the PyCharm toolbar, you will see a Python icon and your run configurations.
+
+.. image:: images/run_configuration.png
+
+First select "setup dev", and click the "run" button (the green triangle). It
+may take some time to install the requirements. Second select "setup docs",
+and click the "run" button again.
+
+As of this writing, PyCharm does not yet have a command line interface to a
+shell. So there are some things that require you to go into a shell to enter
+commands. This next step requires doing just so.
+
+* In your shell, navigate to your project directory, e.g., `cd
+ ~/projects/pycharm_pyramid/`.
+* Enter the command `source bin/activate` to activate your virtual environment.
+* Navigate into your local Pyramid repository, e.g., `cd pyramid`.
+* Issue the command `git submodule update --init --recursive`.
+* Navigate to the `docs` directory in your local Pyramid repository with the
+ command `cd docs`.
+* Issue the command `make clean html` to generate the HTML documentation from
+ reStructuredText files.
+* The HTML files are in `_build/html`. Open up `index.html` in a web browser
+ to see the result.
+* Whenever you want to edit existing docs and see the effect of your changes,
+ simply run `make html` from within the `docs` directory.
+
+Unfortunately, the author was unable to figure out how to generate docs in
+PyCharm using either a "Python docs" or "Python" run configuration. If anyone
+knows, please submit a pull request.
+
+You will now be ready to hack in and contribute to Pyramid.
+
+Template Languages
+==================
+
+To configure the template languages Mako and Jinja, see the PyCharm
+documentation `Templates
+<http://www.jetbrains.com/pycharm/webhelp/templates.html>`_.
+
+To configure the template language Chameleon, see `Creating and Registering
+File Types
+<http://www.jetbrains.com/pycharm/webhelp/creating-and-registering-file-types.
+html>`_. Specifically for Chameleon, we want to associate XML to the `*.pt`
+extension.
+
+* Open *PyCharm > Preferences...*, then the File Types dialog box.
+* From the Recognized File Types list, select "XML files".
+* In the Registered Patterns area, click the "+" button, and the Add Wildcard
+ window opens. Enter `*.pt` in the Add Wildcard window, and click the OK
+ button. Click OK again to save the settings.
+
+Creating a Pyramid Project
+==========================
+
+The information for this section is derived from `Creating a Pyramid Project
+<http://docs.pylonsproject.org/projects/pyramid/en/master/narr/project.html>`_
+and adapted for use in PyCharm.
+
+Creating a Pyramid Project Using Scaffolds
+------------------------------------------
+
+Within PyCharm, you can start a project using a scaffold by doing the
+following.
+
+* Select *Run > Edit Configurations...*.
+* Click the "+" button, then select Python to add a new Python run
+ configuration.
+* Name the configuration "pcreate".
+* Either manually enter the path to the `pcreate` script or click the ellipsis
+ button to navigate to the `$VENV/bin/pcreate` path and select it.
+* For Script parameters enter `-s starter MyProject`. "starter" is the name of
+ one of the scaffolds included with Pyramid, but you can use any scaffold.
+ "MyProject" is the name of your project.
+* Select the directory into which you want to place `MyProject`. A common
+ practice is `~/projects/`.
+* Click the OK button to save the run configuration.
+* Select *Run > Run 'pcreate'* to run the run configuration. Your project will
+ be created.
+* Select *File > Open directory*, select the directory where you created your
+ project `MyProject`, and click the Choose button. You will be prompted to
+ open the project, and you may find it convenient to select "Open in current
+ window", and check "Add to currently open projects".
+* Finally set the Project Interpreter to your virtual environment or verify it
+ as such. Select *PyCharm > Preferences... > Project Interpreter*, and verify
+ that the project is using the same virtual environment as the parent project.
+* If a yellow bar warns you to install requirements, then click link to do so.
+
+Installing your Newly Created Project for Development
+-----------------------------------------------------
+
+We will create another run configuration, just like before.
+
+* In PyCharm, select the `setup.py` script in the `MyProject` folder. This
+ should populate some fields with the proper values.
+* Select *Run > Edit Configurations...*.
+* Click the "+" button, then select Python to add a new Python run
+ configuration.
+* Name the configuration "MyProject setup develop".
+* Either manually enter the path to the `setup.py` script in the `MyProject`
+ folder or click the ellipsis button to navigate to the path and select it.
+* For Script parameters enter `develop`.
+* For Project, select "MyProject".
+* For Working directory, enter or select the path to `MyProject`.
+* Click the "Apply" button to save the run configuration.
+* Finally run the run configuration "MyProject setup develop". Your project
+ will be installed.
+
+Running The Tests For Your Application
+--------------------------------------
+
+We will create yet another run configuration. [If you know of an easier method
+while in PyCharm, please submit a pull request.]
+
+* Select *Run > Edit Configurations...*.
+* Select the previous run configuration "MyProject setup develop", and click
+ the Copy Configuration button.
+* Name the configuration "MyProject setup test".
+* The path to the `setup.py` script in the `MyProject` folder should already be
+ entered.
+* For Script parameters enter `test -q`.
+* For Project "MyProject" should be selected.
+* For Working directory, the path to `MyProject` should be selected.
+* Click the "Apply" button to save the run configuration.
+* Finally run the run configuration "MyProject setup test". Your project will
+ run its unit tests.
+
+Running The Project Application
+-------------------------------
+
+When will creation of run configurations end? Not today!
+
+* Select *Run > Edit Configurations...*.
+* Select the previous run configuration "MyProject setup develop", and click
+ the Copy Configuration button.
+* Name the configuration "MyProject pserve".
+* Either manually enter the path to the `pserve` script or click the ellipsis
+ button to navigate to the `$VENV/bin/pserve` path and select it.
+* For Script parameters enter `development.ini`.
+* For Project "MyProject" should be selected.
+* For Working directory, the path to `MyProject` should be selected.
+* Click the "Apply" button to save the run configuration.
+* Finally run the run configuration "MyProject pserve". Your project will run.
+ Click the link in the Python console or visit the URL http://0.0.0.0:6543/ in
+ a web browser.
+
+You can also reload any changes to your project's `.py` or `.ini` files
+automatically by using the Script parameters `development.ini --reload`.
+
+Debugging
+=========
+
+See the PyCharm documentation `Running and Debugging
+<http://www.jetbrains.com/pycharm/webhelp/running-and-debugging.html>`_ for
+details on how to debug your Pyramid app in PyCharm.
+
+First, you cannot simultaneously run and debug your app. Terminate your app if
+it is running before you debug it.
+
+To debug your app, open a file in your app that you want to debug and click on
+the gutter (the space between line numbers and the code) to set a breakpoint.
+Then select "MyProject pserve" in the PyCharm toolbar, then click the debug
+icon (which looks like a green ladybug). Your app will run up to the first
+breakpoint.
diff --git a/pyramid/chameleon_text.py b/pyramid/chameleon_text.py
index 8cf04bf79..d2a943a28 100644
--- a/pyramid/chameleon_text.py
+++ b/pyramid/chameleon_text.py
@@ -1,7 +1,5 @@
from zope.interface import implementer
-from chameleon.zpt.template import PageTextTemplateFile
-
from pyramid.interfaces import ITemplateRenderer
from pyramid.decorator import reify
@@ -20,6 +18,7 @@ class TextTemplateRenderer(object):
@reify # avoid looking up reload_templates before manager pushed
def template(self):
+ from chameleon.zpt.template import PageTextTemplateFile
return PageTextTemplateFile(self.path,
auto_reload=self.lookup.auto_reload,
debug=self.lookup.debug,
diff --git a/pyramid/chameleon_zpt.py b/pyramid/chameleon_zpt.py
index d8a8ee1be..89e5d02b5 100644
--- a/pyramid/chameleon_zpt.py
+++ b/pyramid/chameleon_zpt.py
@@ -1,7 +1,5 @@
from zope.interface import implementer
-from chameleon.zpt.template import PageTemplateFile
-
from pyramid.interfaces import ITemplateRenderer
from pyramid.decorator import reify
from pyramid import renderers
@@ -18,6 +16,7 @@ class ZPTTemplateRenderer(object):
@reify # avoid looking up reload_templates before manager pushed
def template(self):
+ from chameleon.zpt.template import PageTemplateFile
tf = PageTemplateFile(
self.path,
auto_reload=self.lookup.auto_reload,
diff --git a/pyramid/config/assets.py b/pyramid/config/assets.py
index 5d4682349..0616e6cda 100644
--- a/pyramid/config/assets.py
+++ b/pyramid/config/assets.py
@@ -81,14 +81,12 @@ class OverrideProvider(pkg_resources.DefaultProvider):
self, resource_name)
@implementer(IPackageOverrides)
-class PackageOverrides:
+class PackageOverrides(object):
# pkg_resources arg in kw args below for testing
def __init__(self, package, pkg_resources=pkg_resources):
- if hasattr(package, '__loader__') and not isinstance(package.__loader__,
- self.__class__):
- raise TypeError('Package %s already has a non-%s __loader__ '
- '(probably a module in a zipped egg)' %
- (package, self.__class__))
+ loader = self._real_loader = getattr(package, '__loader__', None)
+ if isinstance(loader, self.__class__):
+ self._real_loader = None
# We register ourselves as a __loader__ *only* to support the
# setuptools _find_adapter adapter lookup; this class doesn't
# actually support the PEP 302 loader "API". This is
@@ -150,7 +148,33 @@ class PackageOverrides:
for package, rname in self.search_path(resource_name):
if pkg_resources.resource_exists(package, rname):
return pkg_resources.resource_listdir(package, rname)
-
+
+ @property
+ def real_loader(self):
+ if self._real_loader is None:
+ raise NotImplementedError()
+ return self._real_loader
+
+ def get_data(self, path):
+ """ See IPEP302Loader.
+ """
+ return self.real_loader.get_data(path)
+
+ def is_package(self, fullname):
+ """ See IPEP302Loader.
+ """
+ return self.real_loader.is_package(fullname)
+
+ def get_code(self, fullname):
+ """ See IPEP302Loader.
+ """
+ return self.real_loader.get_code(fullname)
+
+ def get_source(self, fullname):
+ """ See IPEP302Loader.
+ """
+ return self.real_loader.get_source(fullname)
+
class DirectoryOverride:
def __init__(self, path, package, prefix):
diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py
index f495794b4..c86e4a2dd 100644
--- a/pyramid/config/routes.py
+++ b/pyramid/config/routes.py
@@ -90,10 +90,10 @@ class RoutesConfiguratorMixin(object):
``traverse`` argument provided to ``add_route`` is
``/{article}``, when a request comes in that causes the route
to match in such a way that the ``article`` match value is
- '1' (when the request URI is ``/articles/1/edit``), the
+ ``'1'`` (when the request URI is ``/articles/1/edit``), the
traversal path will be generated as ``/1``. This means that
the root object's ``__getitem__`` will be called with the
- name ``1`` during the traversal phase. If the ``1`` object
+ name ``'1'`` during the traversal phase. If the ``'1'`` object
exists, it will become the :term:`context` of the request.
:ref:`traversal_chapter` has more information about
traversal.
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 4fb4d615c..a57f61ddb 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -793,7 +793,49 @@ deprecated(
'See the "What\'s new In Pyramid 1.3" document for more details.'
)
-class IPackageOverrides(Interface):
+class IPEP302Loader(Interface):
+ """ See http://www.python.org/dev/peps/pep-0302/#id30.
+ """
+ def get_data(path):
+ """ Retrieve data for and arbitrary "files" from storage backend.
+
+ Raise IOError for not found.
+
+ Data is returned as bytes.
+ """
+
+ def is_package(fullname):
+ """ Return True if the module specified by 'fullname' is a package.
+ """
+
+ def get_code(fullname):
+ """ Return the code object for the module identified by 'fullname'.
+
+ Return 'None' if it's a built-in or extension module.
+
+ If the loader doesn't have the code object but it does have the source
+ code, return the compiled source code.
+
+ Raise ImportError if the module can't be found by the importer at all.
+ """
+
+ def get_source(fullname):
+ """ Return the source code for the module identified by 'fullname'.
+
+ Return a string, using newline characters for line endings, or None
+ if the source is not available.
+
+ Raise ImportError if the module can't be found by the importer at all.
+ """
+
+ def get_filename(fullname):
+ """ Return the value of '__file__' if the named module was loaded.
+
+ If the module is not found, raise ImportError.
+ """
+
+
+class IPackageOverrides(IPEP302Loader):
""" Utility for pkg_resources overrides """
# VH_ROOT_KEY is an interface; its imported from other packages (e.g.
diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py
index 061bcb717..8d4583d82 100644
--- a/pyramid/mako_templating.py
+++ b/pyramid/mako_templating.py
@@ -1,6 +1,5 @@
import os
import posixpath
-import re
import sys
import threading
@@ -23,8 +22,34 @@ from pyramid.interfaces import ITemplateRenderer
from pyramid.settings import asbool
from pyramid.util import DottedNameResolver
-from mako.lookup import TemplateLookup
-from mako import exceptions
+def _no_mako(*arg, **kw): # pragma: no cover
+ raise NotImplementedError(
+ "'mako' package is not importable (maybe downgrade MarkupSafe to "
+ "0.16 or below if you're using Python 3.2)"
+ )
+
+try:
+ from mako.lookup import TemplateLookup
+except (ImportError, SyntaxError, AttributeError): #pragma NO COVER
+ class TemplateLookup(object):
+ def __init__(self, **kw):
+ for name in ('adjust_uri', 'get_template', 'filename_to_uri',
+ 'put_string', 'put_template'):
+ setattr(self, name, _no_mako)
+ self.filesystem_checks = False
+
+try:
+ from mako.exceptions import TopLevelLookupException
+except (ImportError, SyntaxError, AttributeError): #pragma NO COVER
+ class TopLevelLookupException(Exception):
+ pass
+
+try:
+ from mako.exceptions import text_error_template
+except (ImportError, SyntaxError, AttributeError): #pragma NO COVER
+ def text_error_template(lookup=None):
+ _no_mako()
+
class IMakoLookup(Interface):
pass
@@ -78,7 +103,7 @@ class PkgResourceTemplateLookup(TemplateLookup):
srcfile = abspath_from_asset_spec(path, pname)
if os.path.isfile(srcfile):
return self._load(srcfile, adjusted)
- raise exceptions.TopLevelLookupException(
+ raise TopLevelLookupException(
"Can not locate template for uri %r" % uri)
return TemplateLookup.get_template(self, uri)
@@ -208,7 +233,7 @@ class MakoLookupTemplateRenderer(object):
except:
try:
exc_info = sys.exc_info()
- errtext = exceptions.text_error_template().render(
+ errtext = text_error_template().render(
error=exc_info[1],
traceback=exc_info[2]
)
diff --git a/pyramid/router.py b/pyramid/router.py
index f780f6711..3d2a2ff3e 100644
--- a/pyramid/router.py
+++ b/pyramid/router.py
@@ -1,5 +1,4 @@
from zope.interface import (
- Interface,
implementer,
providedBy,
)
diff --git a/pyramid/scaffolds/__init__.py b/pyramid/scaffolds/__init__.py
index dc207b540..c993ce5f9 100644
--- a/pyramid/scaffolds/__init__.py
+++ b/pyramid/scaffolds/__init__.py
@@ -1,5 +1,6 @@
import binascii
import os
+from textwrap import dedent
from pyramid.compat import native_
@@ -33,7 +34,22 @@ class PyramidTemplate(Template):
""" Overrides :meth:`pyramid.scaffolds.template.Template.post`, to
print "Welcome to Pyramid. Sorry for the convenience." after a
successful scaffolding rendering."""
- self.out('Welcome to Pyramid. Sorry for the convenience.')
+
+ separator = "=" * 79
+ msg = dedent(
+ """
+ %(separator)s
+ Tutorials: http://docs.pylonsproject.org/projects/pyramid_tutorials
+ Documentation: http://docs.pylonsproject.org/projects/pyramid
+
+ Twitter (tips & updates): http://twitter.com/pylons
+ Mailing List: http://groups.google.com/group/pylons-discuss
+
+ Welcome to Pyramid. Sorry for the convenience.
+ %(separator)s
+ """ % {'separator': separator})
+
+ self.out(msg)
return Template.post(self, command, output_dir, vars)
def out(self, msg): # pragma: no cover (replaceable testing hook)
diff --git a/pyramid/scripts/prequest.py b/pyramid/scripts/prequest.py
index da6b8cc14..8628d5a5a 100644
--- a/pyramid/scripts/prequest.py
+++ b/pyramid/scripts/prequest.py
@@ -1,3 +1,4 @@
+import base64
import optparse
import sys
import textwrap
@@ -18,9 +19,22 @@ class PRequestCommand(object):
This command makes an artifical request to a web application that uses a
PasteDeploy (.ini) configuration file for the server and application.
- Use "prequest config.ini /path" to request "/path". Use "prequest
- --method=POST config.ini /path < data" to do a POST with the given
- request body.
+ Use "prequest config.ini /path" to request "/path".
+
+ Use "prequest --method=POST config.ini /path < data" to do a POST with
+ the given request body.
+
+ Use "prequest --method=PUT config.ini /path < data" to do a
+ PUT with the given request body.
+
+ Use "prequest --method=PATCH config.ini /path < data" to do a
+ PATCH with the given request body.
+
+ Use "prequest --method=OPTIONS config.ini /path" to do an
+ OPTIONS request.
+
+ Use "prequest --method=PROPFIND config.ini /path" to do a
+ PROPFIND request.
If the path is relative (doesn't begin with "/") it is interpreted as
relative to "/". The path passed to this script should be URL-quoted.
@@ -59,9 +73,17 @@ class PRequestCommand(object):
parser.add_option(
'-m', '--method',
dest='method',
- choices=['GET', 'HEAD', 'POST', 'DELETE'],
+ choices=['GET', 'HEAD', 'POST', 'PUT', 'PATCH','DELETE',
+ 'PROPFIND', 'OPTIONS'],
type='choice',
- help='Request method type (GET, POST, DELETE)',
+ help='Request method type (GET, POST, PUT, PATCH, DELETE, '
+ 'PROPFIND, OPTIONS)',
+ )
+ parser.add_option(
+ '-l', '--login',
+ dest='login',
+ type='string',
+ help='HTTP basic auth username:password pair',
)
get_app = staticmethod(get_app)
@@ -92,6 +114,10 @@ class PRequestCommand(object):
path = url_unquote(path)
headers = {}
+ if self.options.login:
+ enc = base64.b64encode(self.options.login.encode('ascii'))
+ headers['Authorization'] = 'Basic ' + enc.decode('ascii')
+
if self.options.headers:
for item in self.options.headers:
if ':' not in item:
@@ -110,9 +136,9 @@ class PRequestCommand(object):
environ = {
'REQUEST_METHOD': request_method,
'SCRIPT_NAME': '', # may be empty if app is at the root
- 'PATH_INFO': path,
+ 'PATH_INFO': path,
'SERVER_NAME': 'localhost', # always mandatory
- 'SERVER_PORT': '80', # always mandatory
+ 'SERVER_PORT': '80', # always mandatory
'SERVER_PROTOCOL': 'HTTP/1.0',
'CONTENT_TYPE': 'text/plain',
'REMOTE_ADDR':'127.0.0.1',
@@ -127,7 +153,7 @@ class PRequestCommand(object):
'paste.command_request': True,
}
- if request_method == 'POST':
+ if request_method in ('POST', 'PUT', 'PATCH'):
environ['wsgi.input'] = self.stdin
environ['CONTENT_LENGTH'] = '-1'
diff --git a/pyramid/session.py b/pyramid/session.py
index 7db8c8e0e..3708ef879 100644
--- a/pyramid/session.py
+++ b/pyramid/session.py
@@ -81,15 +81,22 @@ def signed_deserialize(serialized, secret, hmac=hmac):
return pickle.loads(pickled)
-def check_csrf_token(request, token='csrf_token', raises=True):
+def check_csrf_token(request,
+ token='csrf_token',
+ header='X-CSRF-Token',
+ raises=True):
""" Check the CSRF token in the request's session against the value in
- ``request.params.get(token)``. If a ``token`` keyword is not supplied
- to this function, the string ``csrf_token`` will be used to look up
- the token within ``request.params``. If the value in
- ``request.params.get(token)`` doesn't match the value supplied by
- ``request.session.get_csrf_token()``, and ``raises`` is ``True``, this
- function will raise an :exc:`pyramid.httpexceptions.HTTPBadRequest`
- exception. If the check does succeed and ``raises`` is ``False``, this
+ ``request.params.get(token)`` or ``request.headers.get(header)``.
+ If a ``token`` keyword is not supplied to this function, the string
+ ``csrf_token`` will be used to look up the token in ``request.params``.
+ If a ``header`` keyword is not supplied to this function, the string
+ ``X-CSRF-Token`` will be used to look up the token in ``request.headers``.
+
+ If the value supplied by param or by header doesn't match the value
+ supplied by ``request.session.get_csrf_token()``, and ``raises`` is
+ ``True``, this function will raise an
+ :exc:`pyramid.httpexceptions.HTTPBadRequest` exception.
+ If the check does succeed and ``raises`` is ``False``, this
function will return ``False``. If the CSRF check is successful, this
function will return ``True`` unconditionally.
@@ -98,7 +105,8 @@ def check_csrf_token(request, token='csrf_token', raises=True):
.. versionadded:: 1.4a2
"""
- if request.params.get(token) != request.session.get_csrf_token():
+ supplied_token = request.params.get(token, request.headers.get(header))
+ if supplied_token != request.session.get_csrf_token():
if raises:
raise HTTPBadRequest('incorrect CSRF token')
return False
diff --git a/pyramid/testing.py b/pyramid/testing.py
index 0c701727b..9bd245e4e 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -222,6 +222,8 @@ class DummyResource:
def __nonzero__(self):
return True
+ __bool__ = __nonzero__
+
def __len__(self):
return len(self.subs)
@@ -482,9 +484,9 @@ def tearDown(unhook_zca=True):
If the ``unhook_zca`` argument is ``True`` (the default), call
:func:`zope.component.getSiteManager.reset`. This undoes the
- action of :func:`pyramid.testing.setUp` called with the
+ action of :func:`pyramid.testing.setUp` when called with the
argument ``hook_zca=True``. If :mod:`zope.component` cannot be
- imported, ignore the argument.
+ imported, ``unhook_zca`` is set to ``False``.
"""
global have_zca
if unhook_zca and have_zca:
diff --git a/pyramid/tests/pkgs/viewdecoratorapp/views/templates/foo.mak b/pyramid/tests/pkgs/viewdecoratorapp/views/templates/foo.pt
index 6a2f701b6..6a2f701b6 100644
--- a/pyramid/tests/pkgs/viewdecoratorapp/views/templates/foo.mak
+++ b/pyramid/tests/pkgs/viewdecoratorapp/views/templates/foo.pt
diff --git a/pyramid/tests/pkgs/viewdecoratorapp/views/views.py b/pyramid/tests/pkgs/viewdecoratorapp/views/views.py
index 6f7ff1e21..2b7d7e928 100644
--- a/pyramid/tests/pkgs/viewdecoratorapp/views/views.py
+++ b/pyramid/tests/pkgs/viewdecoratorapp/views/views.py
@@ -1,11 +1,11 @@
from pyramid.view import view_config
-@view_config(renderer='templates/foo.mak', name='first')
+@view_config(renderer='templates/foo.pt', name='first')
def first(request):
return {'result':'OK1'}
@view_config(
- renderer='pyramid.tests.pkgs.viewdecoratorapp.views:templates/foo.mak',
+ renderer='pyramid.tests.pkgs.viewdecoratorapp.views:templates/foo.pt',
name='second')
def second(request):
return {'result':'OK2'}
diff --git a/pyramid/tests/test_config/test_assets.py b/pyramid/tests/test_config/test_assets.py
index 5fe02c358..345e7f8d6 100644
--- a/pyramid/tests/test_config/test_assets.py
+++ b/pyramid/tests/test_config/test_assets.py
@@ -314,16 +314,40 @@ class TestPackageOverrides(unittest.TestCase):
from pyramid.config.assets import PackageOverrides
return PackageOverrides
- def _makeOne(self, package, pkg_resources=None):
+ def _makeOne(self, package=None, pkg_resources=None):
+ if package is None:
+ package = DummyPackage('package')
klass = self._getTargetClass()
if pkg_resources is None:
pkg_resources = DummyPkgResources()
return klass(package, pkg_resources=pkg_resources)
+ def test_class_conforms_to_IPackageOverrides(self):
+ from zope.interface.verify import verifyClass
+ from pyramid.interfaces import IPackageOverrides
+ verifyClass(IPackageOverrides, self._getTargetClass())
+
+ def test_instance_conforms_to_IPackageOverrides(self):
+ from zope.interface.verify import verifyObject
+ from pyramid.interfaces import IPackageOverrides
+ verifyObject(IPackageOverrides, self._makeOne())
+
+ def test_class_conforms_to_IPEP302Loader(self):
+ from zope.interface.verify import verifyClass
+ from pyramid.interfaces import IPEP302Loader
+ verifyClass(IPEP302Loader, self._getTargetClass())
+
+ def test_instance_conforms_to_IPEP302Loader(self):
+ from zope.interface.verify import verifyObject
+ from pyramid.interfaces import IPEP302Loader
+ verifyObject(IPEP302Loader, self._makeOne())
+
def test_ctor_package_already_has_loader_of_different_type(self):
package = DummyPackage('package')
- package.__loader__ = True
- self.assertRaises(TypeError, self._makeOne, package)
+ loader = package.__loader__ = DummyLoader()
+ po = self._makeOne(package)
+ self.assertTrue(package.__loader__ is po)
+ self.assertTrue(po.real_loader is loader)
def test_ctor_package_already_has_loader_of_same_type(self):
package = DummyPackage('package')
@@ -502,6 +526,55 @@ class TestPackageOverrides(unittest.TestCase):
po.overrides= overrides
self.assertEqual(po.listdir('whatever'), None)
+ # PEP 302 __loader__ extensions: use the "real" __loader__, if present.
+ def test_get_data_pkg_has_no___loader__(self):
+ package = DummyPackage('package')
+ po = self._makeOne(package)
+ self.assertRaises(NotImplementedError, po.get_data, 'whatever')
+
+ def test_get_data_pkg_has___loader__(self):
+ package = DummyPackage('package')
+ loader = package.__loader__ = DummyLoader()
+ po = self._makeOne(package)
+ self.assertEqual(po.get_data('whatever'), b'DEADBEEF')
+ self.assertEqual(loader._got_data, 'whatever')
+
+ def test_is_package_pkg_has_no___loader__(self):
+ package = DummyPackage('package')
+ po = self._makeOne(package)
+ self.assertRaises(NotImplementedError, po.is_package, 'whatever')
+
+ def test_is_package_pkg_has___loader__(self):
+ package = DummyPackage('package')
+ loader = package.__loader__ = DummyLoader()
+ po = self._makeOne(package)
+ self.assertTrue(po.is_package('whatever'))
+ self.assertEqual(loader._is_package, 'whatever')
+
+ def test_get_code_pkg_has_no___loader__(self):
+ package = DummyPackage('package')
+ po = self._makeOne(package)
+ self.assertRaises(NotImplementedError, po.get_code, 'whatever')
+
+ def test_get_code_pkg_has___loader__(self):
+ package = DummyPackage('package')
+ loader = package.__loader__ = DummyLoader()
+ po = self._makeOne(package)
+ self.assertEqual(po.get_code('whatever'), b'DEADBEEF')
+ self.assertEqual(loader._got_code, 'whatever')
+
+ def test_get_source_pkg_has_no___loader__(self):
+ package = DummyPackage('package')
+ po = self._makeOne(package)
+ self.assertRaises(NotImplementedError, po.get_source, 'whatever')
+
+ def test_get_source_pkg_has___loader__(self):
+ package = DummyPackage('package')
+ loader = package.__loader__ = DummyLoader()
+ po = self._makeOne(package)
+ self.assertEqual(po.get_source('whatever'), 'def foo():\n pass')
+ self.assertEqual(loader._got_source, 'whatever')
+
class TestDirectoryOverride(unittest.TestCase):
def _getTargetClass(self):
from pyramid.config.assets import DirectoryOverride
@@ -570,10 +643,25 @@ class DummyPkgResources:
def register_loader_type(self, typ, inst):
self.registered.append((typ, inst))
-
+
class DummyPackage:
def __init__(self, name):
self.__name__ = name
+
+class DummyLoader:
+ _got_data = _is_package = None
+ def get_data(self, path):
+ self._got_data = path
+ return b'DEADBEEF'
+ def is_package(self, fullname):
+ self._is_package = fullname
+ return True
+ def get_code(self, fullname):
+ self._got_code = fullname
+ return b'DEADBEEF'
+ def get_source(self, fullname):
+ self._got_source = fullname
+ return 'def foo():\n pass'
class DummyUnderOverride:
def __call__(self, package, path, override_package, override_prefix,
diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py
index c8418c61d..66205e618 100644
--- a/pyramid/tests/test_integration.py
+++ b/pyramid/tests/test_integration.py
@@ -369,22 +369,12 @@ class TestForbiddenAppHasResult(IntegrationBase, unittest.TestCase):
class TestViewDecoratorApp(IntegrationBase, unittest.TestCase):
package = 'pyramid.tests.pkgs.viewdecoratorapp'
- def _configure_mako(self):
- tmpldir = os.path.join(os.path.dirname(__file__),
- 'pkgs',
- 'viewdecoratorapp',
- 'views')
- self.config.registry.settings['mako.directories'] = tmpldir
def test_first(self):
- # we use mako here instead of chameleon because it works on Jython
- self._configure_mako()
res = self.testapp.get('/first', status=200)
self.assertTrue(b'OK' in res.body)
def test_second(self):
- # we use mako here instead of chameleon because it works on Jython
- self._configure_mako()
res = self.testapp.get('/second', status=200)
self.assertTrue(b'OK2' in res.body)
diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py
index 50ef360d9..f607a5497 100644
--- a/pyramid/tests/test_mako_templating.py
+++ b/pyramid/tests/test_mako_templating.py
@@ -22,7 +22,20 @@ class Base(object):
def tearDown(self):
self.config.end()
-class Test_renderer_factory(Base, unittest.TestCase):
+def maybe_unittest():
+ # The latest release of MarkupSafe (0.17) which is used by Mako is
+ # incompatible with Python 3.2, so we skip these tests if we cannot
+ # import a Mako module which ends up importing MarkupSafe. Note that
+ # this version of MarkupSafe *is* compatible with Python 2.6, 2.7, and 3.3,
+ # so these tests should not be skipped on those platforms.
+ try:
+ import mako.lookup
+ except (ImportError, SyntaxError, AttributeError): # pragma: no cover
+ return object
+ else:
+ return unittest.TestCase
+
+class Test_renderer_factory(Base, maybe_unittest()):
def _callFUT(self, info):
from pyramid.mako_templating import renderer_factory
return renderer_factory(info)
@@ -298,7 +311,7 @@ class Test_renderer_factory(Base, unittest.TestCase):
self.assertEqual(result.path, 'hello .world.mako')
self.assertEqual(result.defname, 'comp')
-class MakoRendererFactoryHelperTests(Base, unittest.TestCase):
+class MakoRendererFactoryHelperTests(Base, maybe_unittest()):
def _getTargetClass(self):
from pyramid.mako_templating import MakoRendererFactoryHelper
return MakoRendererFactoryHelper
@@ -345,7 +358,7 @@ class MakoRendererFactoryHelperTests(Base, unittest.TestCase):
self.assertEqual(renderer.path, 'helloworld.mak')
self.assertEqual(renderer.lookup, lookup)
-class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
+class MakoLookupTemplateRendererTests(Base, maybe_unittest()):
def _getTargetClass(self):
from pyramid.mako_templating import MakoLookupTemplateRenderer
return MakoLookupTemplateRenderer
@@ -426,7 +439,7 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase):
self.assertTrue(isinstance(result, text_type))
self.assertEqual(result, text_('result'))
-class TestIntegration(unittest.TestCase):
+class TestIntegration(maybe_unittest()):
def setUp(self):
import pyramid.mako_templating
self.config = testing.setUp()
@@ -501,7 +514,7 @@ class TestIntegration(unittest.TestCase):
{'name':'<b>fred</b>'}).replace('\r','')
self.assertEqual(result, text_('Hello, &lt;b&gt;fred&lt;/b&gt;!\n'))
-class TestPkgResourceTemplateLookup(unittest.TestCase):
+class TestPkgResourceTemplateLookup(maybe_unittest()):
def _makeOne(self, **kw):
from pyramid.mako_templating import PkgResourceTemplateLookup
return PkgResourceTemplateLookup(**kw)
diff --git a/pyramid/tests/test_scripts/test_prequest.py b/pyramid/tests/test_scripts/test_prequest.py
index 91d2b322a..37f1d3c0f 100644
--- a/pyramid/tests/test_scripts/test_prequest.py
+++ b/pyramid/tests/test_scripts/test_prequest.py
@@ -68,6 +68,19 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
+ def test_command_w_basic_auth(self):
+ command = self._makeOne(
+ ['', '--login=user:password',
+ '--header=name:value','development.ini', '/'])
+ command.run()
+ self.assertEqual(self._environ['HTTP_NAME'], 'value')
+ self.assertEqual(self._environ['HTTP_AUTHORIZATION'],
+ 'Basic dXNlcjpwYXNzd29yZA==')
+ self.assertEqual(self._path_info, '/')
+ self.assertEqual(self._spec, 'development.ini')
+ self.assertEqual(self._app_name, None)
+ self.assertEqual(self._out, ['abc'])
+
def test_command_has_content_type_header_var(self):
command = self._makeOne(
['', '--header=content-type:app/foo','development.ini', '/'])
@@ -96,6 +109,7 @@ class TestPRequestCommand(unittest.TestCase):
def test_command_method_get(self):
command = self._makeOne(['', '--method=GET', 'development.ini', '/'])
command.run()
+ self.assertEqual(self._environ['REQUEST_METHOD'], 'GET')
self.assertEqual(self._path_info, '/')
self.assertEqual(self._spec, 'development.ini')
self.assertEqual(self._app_name, None)
@@ -107,6 +121,35 @@ class TestPRequestCommand(unittest.TestCase):
stdin = NativeIO()
command.stdin = stdin
command.run()
+ self.assertEqual(self._environ['REQUEST_METHOD'], 'POST')
+ self.assertEqual(self._environ['CONTENT_LENGTH'], '-1')
+ self.assertEqual(self._environ['wsgi.input'], stdin)
+ self.assertEqual(self._path_info, '/')
+ self.assertEqual(self._spec, 'development.ini')
+ self.assertEqual(self._app_name, None)
+ self.assertEqual(self._out, ['abc'])
+
+ def test_command_method_put(self):
+ from pyramid.compat import NativeIO
+ command = self._makeOne(['', '--method=PUT', 'development.ini', '/'])
+ stdin = NativeIO()
+ command.stdin = stdin
+ command.run()
+ self.assertEqual(self._environ['REQUEST_METHOD'], 'PUT')
+ self.assertEqual(self._environ['CONTENT_LENGTH'], '-1')
+ self.assertEqual(self._environ['wsgi.input'], stdin)
+ self.assertEqual(self._path_info, '/')
+ self.assertEqual(self._spec, 'development.ini')
+ self.assertEqual(self._app_name, None)
+ self.assertEqual(self._out, ['abc'])
+
+ def test_command_method_patch(self):
+ from pyramid.compat import NativeIO
+ command = self._makeOne(['', '--method=PATCH', 'development.ini', '/'])
+ stdin = NativeIO()
+ command.stdin = stdin
+ command.run()
+ self.assertEqual(self._environ['REQUEST_METHOD'], 'PATCH')
self.assertEqual(self._environ['CONTENT_LENGTH'], '-1')
self.assertEqual(self._environ['wsgi.input'], stdin)
self.assertEqual(self._path_info, '/')
@@ -114,6 +157,32 @@ class TestPRequestCommand(unittest.TestCase):
self.assertEqual(self._app_name, None)
self.assertEqual(self._out, ['abc'])
+ def test_command_method_propfind(self):
+ from pyramid.compat import NativeIO
+ command = self._makeOne(['', '--method=PROPFIND', 'development.ini',
+ '/'])
+ stdin = NativeIO()
+ command.stdin = stdin
+ command.run()
+ self.assertEqual(self._environ['REQUEST_METHOD'], 'PROPFIND')
+ self.assertEqual(self._path_info, '/')
+ self.assertEqual(self._spec, 'development.ini')
+ self.assertEqual(self._app_name, None)
+ self.assertEqual(self._out, ['abc'])
+
+ def test_command_method_options(self):
+ from pyramid.compat import NativeIO
+ command = self._makeOne(['', '--method=OPTIONS', 'development.ini',
+ '/'])
+ stdin = NativeIO()
+ command.stdin = stdin
+ command.run()
+ self.assertEqual(self._environ['REQUEST_METHOD'], 'OPTIONS')
+ self.assertEqual(self._path_info, '/')
+ self.assertEqual(self._spec, 'development.ini')
+ self.assertEqual(self._app_name, None)
+ self.assertEqual(self._out, ['abc'])
+
def test_command_with_query_string(self):
command = self._makeOne(['', 'development.ini', '/abc?a=1&b=2&c'])
command.run()
diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py
index b3e0e20c4..35e2b5c27 100644
--- a/pyramid/tests/test_session.py
+++ b/pyramid/tests/test_session.py
@@ -356,20 +356,29 @@ class Test_signed_deserialize(unittest.TestCase):
self.assertRaises(ValueError, self._callFUT, serialized, 'secret')
class Test_check_csrf_token(unittest.TestCase):
- def _callFUT(self, request, token, raises=True):
+ def _callFUT(self, *args, **kwargs):
from ..session import check_csrf_token
- return check_csrf_token(request, token, raises=raises)
+ return check_csrf_token(*args, **kwargs)
- def test_success(self):
+ def test_success_token(self):
request = testing.DummyRequest()
request.params['csrf_token'] = request.session.get_csrf_token()
- self.assertEqual(self._callFUT(request, 'csrf_token'), True)
+ self.assertEqual(self._callFUT(request, token='csrf_token'), True)
+
+ def test_success_header(self):
+ request = testing.DummyRequest()
+ request.headers['X-CSRF-Token'] = request.session.get_csrf_token()
+ self.assertEqual(self._callFUT(request, header='X-CSRF-Token'), True)
def test_success_default_token(self):
- from ..session import check_csrf_token
request = testing.DummyRequest()
request.params['csrf_token'] = request.session.get_csrf_token()
- self.assertEqual(check_csrf_token(request), True)
+ self.assertEqual(self._callFUT(request), True)
+
+ def test_success_default_header(self):
+ request = testing.DummyRequest()
+ request.headers['X-CSRF-Token'] = request.session.get_csrf_token()
+ self.assertEqual(self._callFUT(request), True)
def test_failure_raises(self):
from pyramid.httpexceptions import HTTPBadRequest
diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py
index 7f14462a2..da57c6301 100644
--- a/pyramid/tests/test_testing.py
+++ b/pyramid/tests/test_testing.py
@@ -114,6 +114,10 @@ class TestDummyResource(unittest.TestCase):
resource = self._makeOne()
self.assertEqual(resource.__nonzero__(), True)
+ def test_bool(self):
+ resource = self._makeOne()
+ self.assertEqual(resource.__bool__(), True)
+
def test_ctor_with__provides__(self):
resource = self._makeOne(__provides__=IDummy)
self.assertTrue(IDummy.providedBy(resource))
diff --git a/pyramid/url.py b/pyramid/url.py
index 84b58ac45..83f0d1eab 100644
--- a/pyramid/url.py
+++ b/pyramid/url.py
@@ -387,7 +387,7 @@ class URLMethodsMixin(object):
resulting url of a resource that has a path of ``/baz/bar`` will be
``http://foo/baz/bar``. If you want to generate completely relative
URLs with no leading scheme, host, port, or initial path, you can
- pass ``app_url=''`. Passing ``app_url=''` when the resource path is
+ pass ``app_url=''``. Passing ``app_url=''`` when the resource path is
``/baz/bar`` will return ``/baz/bar``.
.. versionadded:: 1.3