Chris McDonough
2011-12-16 63f7a19b58738b8fe5fbe82f362825fa53ddc0d2
Merge branch '1.3-branch'
4 files added
25 files modified
1203 ■■■■ changed files
CHANGES.txt 42 ●●●●● patch | view | raw | blame | history
TODO.txt 1 ●●●● patch | view | raw | blame | history
docs/api.rst 1 ●●●● patch | view | raw | blame | history
docs/api/scaffolds.rst 13 ●●●●● patch | view | raw | blame | history
docs/conf.py 7 ●●●●● patch | view | raw | blame | history
docs/index.rst 6 ●●●● patch | view | raw | blame | history
docs/narr/commandline.rst 296 ●●●●● patch | view | raw | blame | history
docs/narr/hooks.rst 2 ●●● patch | view | raw | blame | history
docs/narr/scaffolding.rst 171 ●●●●● patch | view | raw | blame | history
docs/whatsnew-1.3.rst 26 ●●●● patch | view | raw | blame | history
pyramid/scaffolds/__init__.py 15 ●●●●● patch | view | raw | blame | history
pyramid/scaffolds/copydir.py 2 ●●●●● patch | view | raw | blame | history
pyramid/scaffolds/template.py 35 ●●●● patch | view | raw | blame | history
pyramid/scripts/pcreate.py 24 ●●●●● patch | view | raw | blame | history
pyramid/scripts/prequest.py 150 ●●●●● patch | view | raw | blame | history
pyramid/scripts/proutes.py 31 ●●●● patch | view | raw | blame | history
pyramid/scripts/pserve.py 19 ●●●● patch | view | raw | blame | history
pyramid/scripts/pshell.py 40 ●●●● patch | view | raw | blame | history
pyramid/scripts/ptweens.py 40 ●●●● patch | view | raw | blame | history
pyramid/scripts/pviews.py 34 ●●●● patch | view | raw | blame | history
pyramid/tests/test_scaffolds/test_template.py 30 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_pcreate.py 18 ●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_prequest.py 141 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_proutes.py 16 ●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_pserve.py 4 ●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_pshell.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_ptweens.py 8 ●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_pviews.py 26 ●●●● patch | view | raw | blame | history
setup.py 3 ●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -4,11 +4,41 @@
Features
--------
- Added a ``prequest`` script (along the lines of ``paster request``).  It is
  documented in the "Command-Line Pyramid" chapter in the section entitled
  "Invoking a Request".
Bug Fixes
---------
- Normalized exit values and ``-h`` output for all ``p*`` scripts
  (``pviews``, ``proutes``, etc).
Documentation
-------------
- Added a section named "Making Your Script into a Console Script" in the
  "Command-Line Pyramid" chapter.
1.3a2 (2011-12-14)
==================
Features
--------
- New API: ``pyramid.view.view_defaults``. If you use a class as a view, you
  can use the new ``view_defaults`` class decorator on the class to provide
  defaults to the view configuration information used by every
  ``@view_config`` decorator that decorates a method of that class.  It also
  works against view configurations involving a class made imperatively.
- Added a backwards compatibility knob to ``pcreate`` to emulate ``paster
  create`` handling for the ``--list-templates`` option.
- Changed scaffolding machinery around a bit to make it easier for people who
  want to have extension scaffolds that can work across Pyramid 1.0.X, 1.1.X,
  1.2.X and 1.3.X.  See the new "Creating Pyramid Scaffolds" chapter in the
  narrative documentation for more info.
Documentation
-------------
@@ -18,6 +48,18 @@
- Added API docs for ``view_defaults`` class decorator.
- Added an API docs chapter for ``pyramid.scaffolds``.
- Added a narrative docs chapter named "Creating Pyramid Scaffolds".
Backwards Incompatibilities
---------------------------
- The ``template_renderer`` method of ``pyramid.scaffolds.PyramidScaffold``
  was renamed to ``render_template``.  If you were overriding it, you're a
  bad person, because it wasn't an API before now.  But we're nice so we're
  letting you know.
1.3a1 (2011-12-09)
==================
TODO.txt
@@ -22,6 +22,7 @@
- Fix deployment recipes in cookbook (discourage proxying without changing
  server).
- Allow prequest path to have query string variables.
Nice-to-Have
------------
docs/api.rst
@@ -26,6 +26,7 @@
   api/renderers
   api/request
   api/response
   api/scaffolds
   api/scripting
   api/security
   api/session
docs/api/scaffolds.rst
New file
@@ -0,0 +1,13 @@
.. _scaffolds_module:
:mod:`pyramid.scaffolds`
------------------------
.. automodule:: pyramid.scaffolds
  .. autoclass:: pyramid.scaffolds.Template
     :members:
  .. autoclass:: pyramid.scaffolds.PyramidTemplate
     :members:
docs/conf.py
@@ -80,7 +80,8 @@
# other places throughout the built documents.
#
# The short X.Y version.
version = '1.4dev'
version = '1.3a2'
# The full version, including alpha/beta/rc tags.
release = version
@@ -475,7 +476,7 @@
# -- Options for Epub output ---------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = 'The Pyramid Web Application Development Framework, Version 1.4dev'
epub_title = 'The Pyramid Web Application Development Framework, Version 1.3'
epub_author = 'Chris McDonough'
epub_publisher = 'Agendaless Consulting'
epub_copyright = '2008-2011'
@@ -492,7 +493,7 @@
epub_identifier = '0615445675'
# A unique identification for the text.
epub_uid = 'The Pyramid Web Application Development Framework, Version 1.4dev'
epub_uid = 'The Pyramid Web Application Development Framework, Version 1.3'
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
docs/index.rst
@@ -19,10 +19,9 @@
   $ easy_install pyramid
   $ python helloworld.py
   serving on 0.0.0.0:8080 view at http://127.0.0.1:8080
And when you visit ``http://localhost:8080/hello/world`` in a browser, you
will see the text ``Hello, world!``.
When you visit ``http://localhost:8080/hello/world`` in a browser, you will
see the text ``Hello, world!``.
See :ref:`firstapp_chapter` for a full explanation of how this application
works. Read the :ref:`html_narrative_documentation` to understand how
@@ -93,6 +92,7 @@
   narr/extending
   narr/advconfig
   narr/extconfig
   narr/scaffolding
   narr/threadlocals
   narr/zca
docs/narr/commandline.rst
@@ -121,7 +121,8 @@
Once you've installed your program for development using ``setup.py
develop``, you can use an interactive Python shell to execute expressions in
a Python environment exactly like the one that will be used when your
application runs "for real".  To do so, use the ``pshell`` command.
application runs "for real".  To do so, use the ``pshell`` command line
utility.
The argument to ``pshell`` follows the format ``config_file#section_name``
where ``config_file`` is the path to your application's ``.ini`` file and
@@ -311,7 +312,7 @@
.. code-block:: text
   :linenos:
   [chrism@thinko MyProject]$ ../bin/proutes development.ini#MyProject
   [chrism@thinko MyProject]$ ../bin/proutes development.ini
   Name            Pattern                        View
   ----            -------                        ----                     
   home            /                              <function my_view>
@@ -354,7 +355,7 @@
.. code-block:: text
   :linenos:
   [chrism@thinko pyramid]$ ptweens development.ini
   [chrism@thinko pyramid]$ myenv/bin/ptweens development.ini
   "pyramid.tweens" config value NOT set (implicitly ordered tweens used)
   Implicit Tween Chain
@@ -415,6 +416,64 @@
                    pyramid.tweens.excview_tween_factory
See :ref:`registering_tweens` for more information about tweens.
.. index::
   single: invoking a request
   single: prequest
.. _invoking_a_request:
Invoking a Request
------------------
You can use the ``prequest`` command-line utility to send a request to your
application and see the response body without starting a server.
There are two required arguments to ``prequest``:
- The config file/section: follows the format ``config_file#section_name``
  where ``config_file`` is the path to your application's ``.ini`` file and
  ``section_name`` is the ``app`` section name inside the ``.ini`` file.  The
  ``section_name`` is optional, it defaults to ``main``.  For example:
  ``development.ini``.
- The path: this should be the non-url-quoted path element of the URL to the
  resource you'd like to be rendered on the server.  For example, ``/``.
For example::
   $ bin/prequest development.ini /
This will print the body of the response to the console on which it was
invoked.
Several options are supported by ``prequest``.  These should precede any
config file name or URL.
``prequest`` has a ``-d`` (aka ``--display-headers``) option which prints the
status and headers returned by the server before the output::
   $ bin/prequest -d development.ini /
This will print the status, then the headers, then the body of the response
to the console.
You can add request header values by using the ``--header`` option::
   $ bin/prequest --header=Host=example.com development.ini /
Headers are added to the WSGI environment by converting them to their
CGI/WSGI equivalents (e.g. ``Host=example.com`` will insert the ``HTTP_HOST``
header variable as the value ``example.com``).  Multiple ``--header`` options
can be supplied.  The special header value ``content-type`` sets the
``CONTENT_TYPE`` in the WSGI environment.
By default, ``prequest`` sends a ``GET`` request.  You can change this by
using the ``-m`` (aka ``--method``) option.  ``GET``, ``HEAD``, ``POST`` and
``DELETE`` are currently supported.  When you use ``POST``, the standard
input of the ``prequest`` process is used as the ``POST`` body::
   $ bin/prequest -mPOST development.ini / < somefile
.. _writing_a_script:
@@ -595,3 +654,234 @@
   import logging.config
   logging.config.fileConfig('/path/to/my/development.ini')
.. index::
   single: console script
.. _making_a_console_script:
Making Your Script into a Console Script
----------------------------------------
A "console script" is :term:`setuptools` terminology for a script that gets
installed into the ``bin`` directory of a Python :term:`virtualenv` (or
"base" Python environment) when a :term:`distribution` which houses that
script is installed.  Because it's installed into the ``bin`` directory of a
virtualenv when the distribution is installed, it's a convenient way to
package and distribute functionality that you can call from the command-line.
It's often more convenient to create a console script than it is to create a
``.py`` script and instruct people to call it with "the right Python
interpreter": because it generates a file that lives in ``bin``, when it's
invoked, it will always use "the right" Python environment, which means it
will always be invoked in an environment where all the libraries it needs
(such as Pyramid) are available.
In general, you can make your script into a console script by doing the
following:
- Use an existing distribution (such as one you've already created via
  ``pcreate``) or create a new distribution that possesses at least one
  package or module.  It should, within any module within the distribution,
  house a callable (usually a function) that takes no arguments and which
  runs any of the code you wish to run.
- Add a ``[console_scripts]`` section to the ``entry_points`` argument of the
  distribution which creates a mapping between a script name and a dotted
  name representing the callable you added to your distribution.
- Run ``setup.py develop``, ``setup.py install``, or ``easy_install`` to get
  your distribution reinstalled.  When you reinstall your distribution, a
  file representing the script that you named in the last step will be in the
  ``bin`` directory of the virtualenv in which you installed the
  distribution.  It will be executable.  Invoking it from a terminal will
  execute your callable.
As an example, let's create some code that can be invoked by a console script
that prints the deployment settings of a Pyramid application.  To do so,
we'll pretend you have a distribution with a package in it named
``myproject``.  Within this package, we'll pretend you've added a
``scripts.py`` module which contains the following code:
.. code-block:: python
   :linenos:
   # myproject.scripts module
   import optparse
   import sys
   import textwrap
   from pyramid.paster import bootstrap
   def settings_show():
       description = """\
       Print the deployment settings for a Pyramid application.  Example:
       'psettings deployment.ini'
       """
       usage = "usage: %prog config_uri"
       parser = optparse.OptionParser(
           usage=usage,
           description=textwrap.dedent(description)
           )
       parser.add_option(
           '-o', '--omit',
           dest='omit',
           metavar='PREFIX',
           type='string',
           action='append',
           help=("Omit settings which start with PREFIX (you can use this "
                 "option multiple times)")
           )
       options, args = parser.parse_args(sys.argv[1:])
       if not len(args) >= 1:
           print('You must provide at least one argument')
           return 2
       config_uri = args[0]
       omit = options.omit
       if omit is None:
           omit = []
       env = bootstrap(config_uri)
       settings, closer = env['registry'].settings, env['closer']
       try:
           for k, v in settings.items():
               if any([k.startswith(x) for x in omit]):
                   continue
               print('%-40s     %-20s' % (k, v))
       finally:
           closer()
This script uses the Python ``optparse`` module to allow us to make sense out
of extra arguments passed to the script.  It uses the
:func:`pyramid.paster.bootstrap` function to get information about the the
application defined by a config file, and prints the deployment settings
defined in that config file.
After adding this script to the package, you'll need to tell your
distribution's ``setup.py`` about its existence.  Within your distribution's
top-level directory your ``setup.py`` file will look something like this:
.. code-block:: python
   :linenos:
   import os
   from setuptools import setup, find_packages
   here = os.path.abspath(os.path.dirname(__file__))
   README = open(os.path.join(here, 'README.txt')).read()
   CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
   requires = ['pyramid', 'pyramid_debugtoolbar']
   setup(name='MyProject',
         version='0.0',
         description='My project',
         long_description=README + '\n\n' +  CHANGES,
         classifiers=[
           "Programming Language :: Python",
           "Framework :: Pylons",
           "Topic :: Internet :: WWW/HTTP",
           "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
           ],
         author='',
         author_email='',
         url='',
         keywords='web pyramid pylons',
         packages=find_packages(),
         include_package_data=True,
         zip_safe=False,
         install_requires=requires,
         tests_require=requires,
         test_suite="wiggystatic",
         entry_points = """\
         [paste.app_factory]
         main = wiggystatic:main
         """,
         )
We're going to change the setup.py file to add an ``[console_scripts]``
section with in the ``entry_points`` string.  Within this section, you should
specify a ``scriptname = dotted.path.to:yourfunction`` line.  For example::
  [console_scripts]
  show_settings = myproject.scripts:settings_show
The ``show_settings`` name will be the name of the script that is installed
into ``bin``.  The colon (``:``) between ``myproject.scripts`` and
``settings_show`` above indicates that ``myproject.scripts`` is a Python
module, and ``settings_show`` is the function in that module which contains
the code you'd like to run as the result of someone invoking the
``show_settings`` script from their command line.
The result will be something like:
.. code-block:: python
   :linenos:
   import os
   from setuptools import setup, find_packages
   here = os.path.abspath(os.path.dirname(__file__))
   README = open(os.path.join(here, 'README.txt')).read()
   CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
   requires = ['pyramid', 'pyramid_debugtoolbar']
   setup(name='MyProject',
         version='0.0',
         description='My project',
         long_description=README + '\n\n' +  CHANGES,
         classifiers=[
           "Programming Language :: Python",
           "Framework :: Pylons",
           "Topic :: Internet :: WWW/HTTP",
           "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
           ],
         author='',
         author_email='',
         url='',
         keywords='web pyramid pylons',
         packages=find_packages(),
         include_package_data=True,
         zip_safe=False,
         install_requires=requires,
         tests_require=requires,
         test_suite="wiggystatic",
         entry_points = """\
         [paste.app_factory]
         main = wiggystatic:main
         [console_scripts]
         show_settings = myproject.scripts:settings_show
         """,
         )
Once you've done this, invoking ``$somevirtualenv/bin/python setup.py
develop`` will install a file named ``show_settings`` into the
``$somevirtualenv/bin`` directory with a small bit of Python code that points
to your entry point.  It will be executable.  Running it without any
arguments will print an error and exit.  Running it with a single argument
that is the path of a config file will print the settings.  Running it with
an ``--omit=foo`` argument will omit the settings that have keys that start
with ``foo``.  Running it with two "omit" options (e.g. ``--omit=foo
--omit=bar``) will omit all settings that have keys that start with either
``foo`` or ``bar``::
  [chrism@thinko somevenv]$ bin/show_settings development.ini \
                            --omit=pyramid \
                            --omit=debugtoolbar
  debug_routematch                             False
  debug_templates                              True
  reload_templates                             True
  mako.directories                             []
  debug_notfound                               False
  default_locale_name                          en
  reload_resources                             False
  debug_authorization                          False
  reload_assets                                False
  prevent_http_cache                           False
Pyramid's ``pserve``, ``pcreate``, ``pshell``, ``prequest``, ``ptweens`` and
other ``p*`` scripts are implemented as console scripts.  When you invoke one
of those, you are using a console script.
docs/narr/hooks.rst
@@ -119,7 +119,7 @@
.. code-block:: python
   :linenos:
   from pyramid.views import view_config
   from pyramid.view import view_config
   from pyramid.response import Response
   def forbidden_view(request):
docs/narr/scaffolding.rst
New file
@@ -0,0 +1,171 @@
.. _scaffolding_chapter:
Creating Pyramid Scaffolds
==========================
You can extend Pyramid by creating a :term:`scaffold` template.  A scaffold
template is useful if you'd like to distribute a customizable configuration
of Pyramid to other users.  Once you've created a scaffold, and someone has
installed the distribution that houses the scaffold, they can use the
``pcreate`` script to create a custom version of your scaffold's template.
Pyramid itself uses scaffolds to allow people to bootstrap new projects.  For
example, ``pcreate -s alchemy MyStuff`` causes Pyramid to render the
``alchemy`` scaffold template to the ``MyStuff`` directory.
Basics
------
A scaffold template is just a bunch of source files and directories on disk.
A small definition class points at this directory; it is in turn pointed at
by a :term:`setuptools` "entry point" which registers the scaffold so it can
be found by the ``pcreate`` command.
To create a scaffold template, create a Python :term:`distribution` to house
the scaffold which includes a ``setup.py`` that relies on the ``setuptools``
package.  See `Creating a Package
<http://guide.python-distribute.org/creation.html>`_ for more information
about how to do this.  For the sake of example, we'll pretend the
distribution you create is named ``CoolExtension``, and it has a package
directory within it named ``coolextension``
Once you've created the distribution put a "scaffolds" directory within your
distribution's package directory, and create a file within that directory
named ``__init__.py`` with something like the following:
.. code-block:: python
   :linenos:
   # CoolExtension/coolextension/scaffolds/__init__.py
   from pyramid.scaffolds import PyramidTemplate
     class CoolExtensionTemplate(PyramidTemplate):
         _template_dir = 'coolextension_scaffold'
         summary = 'My cool extension'
Once this is done, within the ``scaffolds`` directory, create a template
directory.  Our example used a template directory named
``coolextension_scaffold``.
As you create files and directories within the template directory, note that:
- Files which have a name which are suffixed with the value ``_tmpl`` will be
  rendered, and replacing any instance of the literal string ``{{var}}`` with
  the string value of the variable named ``var`` provided to the scaffold.
- Files and directories with filenames that contain the string ``+var+`` will
  have that string replaced with the value of the ``var`` variable provided
  to the scaffold.
Otherwise, files and directories which live in the template directory will be
copied directly without modification to the ``pcreate`` output location.
The variables provided by the default ``PyramidTemplate`` include ``project``
(the project name provided by the user as an argument to ``pcreate``),
``package`` (a lowercasing and normalizing of the project name provided by
the user), ``random_string`` (a long random string), and ``package_logger``
(the name of the package's logger).
See Pyramid's "scaffolds" package
(https://github.com/Pylons/pyramid/tree/master/pyramid/scaffolds) for
concrete examples of scaffold directories (``zodb``, ``alchemy``, and
``starter``, for example).
After you've created the template directory, add the following to the
``entry_points`` value of your distribution's ``setup.py``:
      [pyramid.scaffold]
      coolextension=coolextension.scaffolds:CoolExtensionTemplate
For example::
    def setup(
          ...,
          entry_points = """\
            [pyramid.scaffold]
            coolextension=coolextension.scaffolds:CoolExtensionTemplate
          """
         )
Run your distribution's ``setup.py develop`` or ``setup.py install``
command. After that, you should be able to see your scaffolding template
listed when you run ``pcreate -l``.  It will be named ``coolextension``
because that's the name we gave it in the entry point setup.  Running
``pcreate -s coolextension MyStuff`` will then render your scaffold to an
output directory named ``MyStuff``.
See the module documentation for :mod:`pyramid.scaffolds` for information
about the API of the :class:`pyramid.scaffolds.PyramidScaffold` class and
related classes.  You can override methods of this class to get special
behavior.
Supporting Older Pyramid Versions
---------------------------------
Because different versions of Pyramid handled scaffolding differently, if you
want to have extension scaffolds that can work across Pyramid 1.0.X, 1.1.X,
1.2.X and 1.3.X, you'll need to use something like this bit of horror while
defining your scaffold template:
.. code-block:: python
   :linenos:
     try: # pyramid 1.0.X
         # "pyramid.paster.paste_script..." doesn't exist past 1.0.X
         from pyramid.paster import paste_script_template_renderer
         from pyramid.paster import PyramidTemplate
     except ImportError:
         try: # pyramid 1.1.X, 1.2.X
             # trying to import "paste_script_template_renderer" fails on 1.3.X
             from pyramid.scaffolds import paste_script_template_renderer
             from pyramid.scaffolds import PyramidTemplate
         except ImportError: # pyramid >=1.3a2
             paste_script_template_renderer = None
             from pyramid.scaffolds import PyramidTemplate
     class CoolExtensionTemplateTemplate(PyramidTemplate):
         _template_dir = 'coolextension_scaffold'
         summary = 'My cool extension'
         template_renderer = staticmethod(paste_script_template_renderer)
And then in the setup.py of the package that contains your scaffold, define
the template as a target of both ``paste.paster_create_template`` (for
``paster create``) and ``pyramid.scaffold`` (for ``pcreate``)::
      [paste.paster_create_template]
      coolextension=coolextension.scaffolds:CoolExtensionTemplate
      [pyramid.scaffold]
      coolextension=coolextension.scaffolds:CoolExtensionTemplate
Doing this hideousness will allow your scaffold to work as a ``paster
create`` target (under 1.0, 1.1, or 1.2) or as a ``pcreate`` target (under
1.3).  If an invoker tries to run ``paster create`` against a scaffold
defined this way under 1.3, an error is raised instructing them to use
``pcreate`` instead.
If you want only to support Pyramid 1.3 only, it's much cleaner, and the API
is stable:
.. code-block:: python
   :linenos:
   from pyramid.scaffolds import PyramidTemplate
   class CoolExtensionTemplate(PyramidTemplate):
       _template_dir = 'coolextension_scaffold'
       summary = 'My cool_extension'
You only need to specify a ``paste.paster_create_template`` entry point
target in your ``setup.py`` if you want your scaffold to be consumable by
users of Pyramid 1.0, 1.1, or 1.2.  To support only 1.3, specifying only the
``pyramid.scaffold`` entry point is good enough.  If you want to support both
``paster create`` and ``pcreate`` (meaning you want to support Pyramid 1.2
and some older version), you'll need to define both.
Examples
--------
Existing third-party distributions which house scaffolding are available via
:term:`PyPI`.  The ``pyramid_jqm``, ``pyramid_zcml`` and ``pyramid_jinja2``
packages house scaffolds.  You can install and examine these packages to see
how they work in the quest to develop your own scaffolding.
docs/whatsnew-1.3.rst
@@ -76,12 +76,14 @@
The ``ini`` configuration file format supported by Pyramid has not changed.
As a result, Python 2-only users can install PasteScript manually and use
``paster serve`` and ``paster create`` instead if they like.  However, using
``pserve`` and ``pcreate`` will work under both Python 2 and Python 3.
``paster serve`` instead if they like.  However, using ``pserve`` will work
under both Python 2 and Python 3.  ``pcreate`` is required to be used for
internal Pyramid scaffolding; externally distributed scaffolding may allow
for both ``pcreate`` and/or ``paster create``.
Analogues of ``paster pshell``, ``paster pviews`` and ``paster ptweens`` also
exist under the respective console script names ``pshell``, ``pviews``, and
``ptweens``.
Analogues of ``paster pshell``, ``paster pviews``, ``paster request`` and
``paster ptweens`` also exist under the respective console script names
``pshell``, ``pviews``, ``prequest`` and ``ptweens``.
We've replaced use of the Paste ``httpserver`` with the ``wsgiref`` server in
the scaffolds, so once you create a project from a scaffold, its
@@ -100,6 +102,10 @@
actually recommended if you rely on proxying from Apache or Nginx to a
``pserve`` -invoked application.  **The wsgiref server is not a production
quality server.** See :ref:`alternate_wsgi_server` for more information.
New releases in every older major Pyramid series (1.0.2, 1.1.3, 1.2.5) also
have the ``egg:pyramid#wsgiref`` entry point, so scaffold-writers can depend
on it being there even in older major Pyramid versions.
.. warning::
@@ -286,6 +292,16 @@
- A narrative documentation chapter named :ref:`using_introspection` was
  added.  It describes how to query the introspection system.
- Added an API docs chapter for :mod:`pyramid.scaffolds`.
- Added a narrative docs chapter named :ref:`scaffolding_chapter`.
- Added a description of the ``prequest`` command-line script at
  :ref:`invoking_a_request`.
- Added a section to the "Command-Line Pyramid" chapter named
  :ref:`making_a_console_script`.
Dependency Changes
------------------
pyramid/scaffolds/__init__.py
@@ -3,10 +3,20 @@
from pyramid.compat import native_
from pyramid.scaffolds.template import Template
from pyramid.scaffolds.template import Template # API
class PyramidTemplate(Template):
    """
     A class that can be used as a base class for Pyramid scaffolding
     templates.
    """
    def pre(self, command, output_dir, vars):
        """ Overrides :meth:`pyramid.scaffold.template.Template.pre`, adding
        several variables to the default variables list (including
        ``random_string``, and ``package_logger``).  It also prevents common
        misnamings (such as naming a package "site" or naming a package
        logger "root".
        """
        if vars['package'] == 'site':
            raise ValueError('Sorry, you may not name your package "site". '
                             'The package name "site" has a special meaning in '
@@ -20,6 +30,9 @@
        return Template.pre(self, command, output_dir, vars)
    def post(self, command, output_dir, vars): # pragma: no cover
        """ Overrides :meth:`pyramid.scaffold.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.')
        return Template.post(self, command, output_dir, vars)
pyramid/scaffolds/copydir.py
@@ -5,8 +5,6 @@
import os
import sys
import pkg_resources
import cgi
import urllib
from pyramid.compat import (
    input_,
pyramid/scaffolds/template.py
@@ -16,13 +16,22 @@
fsenc = sys.getfilesystemencoding()
class Template(object):
    """ Inherit from this base class and override methods to use the Pyramid
    scaffolding system."""
    copydir = copydir # for testing
    _template_dir = None
    def __init__(self, name):
        self.name = name
    def template_renderer(self, content, vars, filename=None):
    def render_template(self, content, vars, filename=None):
        """ Return a bytestring representing a templated file based on the
        input (content) and the variable names defined (vars).  ``filename``
        is used for exception reporting."""
        # this method must not be named "template_renderer" fbo of extension
        # scaffolds that need to work under pyramid 1.2 and 1.3, and which
        # need to do "template_renderer =
        # staticmethod(paste_script_template_renderer)"
        content = native_(content, fsenc)
        try:
            return bytes_(
@@ -32,14 +41,20 @@
            raise
    def module_dir(self):
        """Returns the module directory of this template."""
        mod = sys.modules[self.__class__.__module__]
        return os.path.dirname(mod.__file__)
    def template_dir(self):
        """ Return the template directory of the scaffold.  By default, it
        returns the value of ``os.path.join(self.module_dir(),
        self._template_dir)`` (``self.module_dir()`` returns the module in
        which your subclass has been defined).  If ``self._template_dir`` is
        a tuple this method just returns the value instead of trying to
        construct a path.  If _template_dir is a tuple, it should be a
        2-element tuple: ``(package_name, package_relative_path)``."""
        assert self._template_dir is not None, (
            "Template %r didn't set _template_dir" % self)
        if isinstance( self._template_dir, tuple):
        if isinstance(self._template_dir, tuple):
            return self._template_dir
        else:
            return os.path.join(self.module_dir(), self._template_dir)
@@ -78,7 +93,7 @@
            interactive=command.options.interactive,
            overwrite=command.options.overwrite,
            indent=1,
            template_renderer=self.template_renderer
            template_renderer=self.render_template,
            )
    def makedirs(self, dir): # pragma: no cover
@@ -90,6 +105,18 @@
    def out(self, msg): # pragma: no cover
        print(msg)
    # hair for exit with usage when paster create is used under 1.3 instead
    # of pcreate for extension scaffolds which need to support multiple
    # versions of pyramid; the check_vars method is called by pastescript
    # only as the result of "paster create"; pyramid doesn't use it.  the
    # required_templates tuple is required to allow it to get as far as
    # calling check_vars.
    required_templates = ()
    def check_vars(self, vars, other):
        raise RuntimeError(
            'Under Pyramid 1.3, you should use the "pcreate" command rather '
            'than "paster create"')
class TypeMapper(dict):
    def __getitem__(self, item):
pyramid/scripts/pcreate.py
@@ -13,12 +13,13 @@
def main(argv=sys.argv, quiet=False):
    command = PCreateCommand(argv, quiet)
    command.run()
    return command.run()
class PCreateCommand(object):
    verbosity = 1
    usage = "usage: %prog [options] distribution_name"
    parser = optparse.OptionParser(usage)
    verbosity = 1 # required
    description = "Render Pyramid scaffolding to an output directory"
    usage = "usage: %prog [options] output_directory"
    parser = optparse.OptionParser(usage, description=description)
    parser.add_option('-s', '--scaffold',
                      dest='scaffold_name',
                      action='append',
@@ -34,6 +35,11 @@
                      dest='list',
                      action='store_true',
                      help="List all available scaffold names")
    parser.add_option('--list-templates',
                      dest='list',
                      action='store_true',
                      help=("A backwards compatibility alias for -l/--list.  "
                            "List all available scaffold names."))
    parser.add_option('--simulate',
                      dest='simulate',
                      action='store_true',
@@ -57,15 +63,15 @@
            return self.show_scaffolds()
        if not self.options.scaffold_name:
            self.out('You must provide at least one scaffold name')
            return
            return 2
        if not self.args:
            self.out('You must provide a project name')
            return
            return 2
        available = [x.name for x in self.scaffolds]
        diff = set(self.options.scaffold_name).difference(available)
        if diff:
            self.out('Unavailable scaffolds: %s' % list(diff))
            return
            return 2
        return self.render_scaffolds()
    def render_scaffolds(self):
@@ -85,7 +91,7 @@
            for scaffold in self.scaffolds:
                if scaffold.name == scaffold_name:
                    scaffold.run(self, output_dir, vars)
        return True
        return 0
    def show_scaffolds(self):
        scaffolds = sorted(self.scaffolds, key=lambda x: x.name)
@@ -98,7 +104,7 @@
                    ' '*(max_name-len(scaffold.name)), scaffold.summary))
        else:
            self.out('No scaffolds available')
        return True
        return 0
    def all_scaffolds(self):
        scaffolds = []
pyramid/scripts/prequest.py
New file
@@ -0,0 +1,150 @@
import optparse
import sys
import textwrap
from pyramid.compat import url_quote
from pyramid.request import Request
from pyramid.paster import get_app
def main(argv=sys.argv, quiet=False):
    command = PRequestCommand(argv, quiet)
    return command.run()
class PRequestCommand(object):
    description = """\
    Run a request for the described application.
    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.
    If the path is relative (doesn't begin with "/") it is interpreted as
    relative to "/".
    The variable "environ['paste.command_request']" will be set to "True" in
    the request's WSGI environment, so your application can distinguish these
    calls from normal requests.
    Note that you can pass arguments besides the options listed here; any
    unknown arguments will be passed to the application in
    "environ['QUERY_STRING']"
    """
    usage = "usage: %prog config_uri path_info [args/options]"
    parser = optparse.OptionParser(
        usage=usage,
        description=textwrap.dedent(description)
        )
    parser.add_option(
        '-n', '--app-name',
        dest='app_name',
        metavar= 'NAME',
        help="Load the named application from the config file (default 'main')",
        type="string",
        )
    parser.add_option(
        '--header',
        dest='headers',
        metavar='NAME:VALUE',
        type='string',
        action='append',
        help="Header to add to request (you can use this option multiple times)"
        )
    parser.add_option(
        '-d', '--display-headers',
        dest='display_headers',
        action='store_true',
        help='Display status and headers before the response body'
        )
    parser.add_option(
        '-m', '--method',
        dest='method',
        choices=['GET', 'HEAD', 'POST', 'DELETE'],
        type='choice',
        help='Request method type (GET, POST, DELETE)',
        )
    get_app = staticmethod(get_app)
    stdin = sys.stdin
    def __init__(self, argv, quiet=False):
        self.quiet = quiet
        self.options, self.args = self.parser.parse_args(argv[1:])
    def out(self, msg): # pragma: no cover
        if not self.quiet:
            print(msg)
    def run(self):
        if not len(self.args) >= 2:
            self.out('You must provide at least two arguments')
            return 2
        app_spec = self.args[0]
        path = self.args[1]
        if not path.startswith('/'):
            path = '/' + path
        headers = {}
        if self.options.headers:
            for item in self.options.headers:
                if ':' not in item:
                    self.out(
                        "Bad --header=%s option, value must be in the form "
                        "'name:value'" % item)
                    return 2
                name, value = item.split(':', 1)
                headers[name] = value.strip()
        app = self.get_app(app_spec, self.options.app_name)
        request_method = (self.options.method or 'GET').upper()
        qs = []
        for item in self.args[2:]:
            if '=' in item:
                k, v = item.split('=', 1)
                item = url_quote(k) + '=' + url_quote(v)
            else:
                item = url_quote(item)
            qs.append(item)
        qs = '&'.join(qs)
        environ = {
            'REQUEST_METHOD': request_method,
            'SCRIPT_NAME': '',           # may be empty if app is at the root
            'PATH_INFO': path,             # may be empty if at root of app
            'SERVER_NAME': 'localhost',  # always mandatory
            'SERVER_PORT': '80',         # always mandatory
            'SERVER_PROTOCOL': 'HTTP/1.0',
            'CONTENT_TYPE': 'text/plain',
            'wsgi.run_once': True,
            'wsgi.multithread': False,
            'wsgi.multiprocess': False,
            'wsgi.errors': sys.stderr,
            'wsgi.url_scheme': 'http',
            'wsgi.version': (1, 0),
            'QUERY_STRING': qs,
            'HTTP_ACCEPT': 'text/plain;q=1.0, */*;q=0.1',
            'paste.command_request': True,
            }
        if request_method == 'POST':
            environ['wsgi.input'] = self.stdin
            environ['CONTENT_LENGTH'] = '-1'
        for name, value in headers.items():
            if name.lower() == 'content-type':
                name = 'CONTENT_TYPE'
            else:
                name = 'HTTP_'+name.upper().replace('-', '_')
            environ[name] = value
        request = Request.blank(path, environ=environ)
        response = request.get_response(app)
        if self.options.display_headers:
            self.out(response.status)
            for name, value in response.headerlist:
                self.out('%s: %s' % (name, value))
        self.out(response.ubody)
        return 0
pyramid/scripts/proutes.py
@@ -1,34 +1,34 @@
import optparse
import sys
import textwrap
from pyramid.paster import bootstrap
def main(argv=sys.argv, quiet=False):
    command = PRoutesCommand(argv, quiet)
    command.run()
    return command.run()
class PRoutesCommand(object):
    """Print all URL dispatch routes used by a Pyramid application in the
    description = """\
    Print all URL dispatch routes used by a Pyramid application in the
    order in which they are evaluated.  Each route includes the name of the
    route, the pattern of the route, and the view callable which will be
    invoked when the route is matched.
    This command accepts one positional argument:
    ``config_uri`` -- specifies the PasteDeploy config file to use for the
    interactive shell. The format is ``inifile#name``. If the name is left
    off, ``main`` will be assumed.
    Example::
        $ proutes myapp.ini#main
    This command accepts one positional argument named "config_uri".  It
    specifies the PasteDeploy config file to use for the interactive
    shell. The format is "inifile#name". If the name is left off, "main"
    will be assumed.  Example: "proutes myapp.ini".
    """
    bootstrap = (bootstrap,)
    summary = "Print all URL dispatch routes related to a Pyramid application"
    stdout = sys.stdout
    usage = '%prog config_uri'
    parser = optparse.OptionParser()
    parser = optparse.OptionParser(
        usage,
        description=textwrap.dedent(description)
        )
    def __init__(self, argv, quiet=False):
        self.options, self.args = self.parser.parse_args(argv[1:])
@@ -46,7 +46,7 @@
    def run(self, quiet=False):
        if not self.args:
            self.out('requires a config file argument')
            return
            return 2
        from pyramid.interfaces import IRouteRequest
        from pyramid.interfaces import IViewClassifier
        from pyramid.interfaces import IView
@@ -59,7 +59,7 @@
            routes = mapper.get_routes()
            fmt = '%-15s %-30s %-25s'
            if not routes:
                return
                return 0
            self.out(fmt % ('Name', 'Pattern', 'View'))
            self.out(
                fmt % ('-'*len('Name'), '-'*len('Pattern'), '-'*len('View')))
@@ -77,4 +77,5 @@
                        (IViewClassifier, request_iface, Interface),
                        IView, name='', default=None)
                    self.out(fmt % (route.name, pattern, view_callable))
        return 0
pyramid/scripts/pserve.py
@@ -16,6 +16,7 @@
import re
import subprocess
import sys
import textwrap
import threading
import time
import traceback
@@ -28,17 +29,14 @@
def main(argv=sys.argv, quiet=False):
    command = PServeCommand(argv, quiet=quiet)
    command.run()
    return command.run()
class DaemonizeException(Exception):
    pass
class PServeCommand(object):
    usage = 'CONFIG_FILE [start|stop|restart|status] [var=value]'
    takes_config_file = 1
    summary = ("Serve the application described in CONFIG_FILE or control "
               "daemon status"),
    usage = '%prog config_uri [start|stop|restart|status] [var=value]'
    description = """\
    This command serves a web application that uses a PasteDeploy
    configuration file for the server and application.
@@ -51,7 +49,10 @@
    """
    verbose = 1
    parser = optparse.OptionParser()
    parser = optparse.OptionParser(
        usage,
        description=textwrap.dedent(description)
        )
    parser.add_option(
        '-n', '--app-name',
        dest='app_name',
@@ -158,7 +159,7 @@
        if not self.args:
            self.out('You must give a config file')
            return
            return 2
        app_spec = self.args[0]
        if (len(self.args) > 1
            and self.args[1] in self.possible_subcommands):
@@ -181,7 +182,7 @@
        if cmd not in (None, 'start', 'stop', 'restart', 'status'):
            self.out(
                'Error: must give start|stop|restart (not %s)' % cmd)
            return
            return 2
        if cmd == 'status' or self.options.show_status:
            return self.show_status()
@@ -244,7 +245,7 @@
            except DaemonizeException as ex:
                if self.verbose > 0:
                    self.out(str(ex))
                return
                return 2
        if (self.options.monitor_restart
            and not os.environ.get(self._monitor_environ_key)):
pyramid/scripts/pshell.py
@@ -1,6 +1,7 @@
from code import interact
import optparse
import sys
import textwrap
from pyramid.compat import configparser
from pyramid.util import DottedNameResolver
@@ -10,32 +11,29 @@
def main(argv=sys.argv, quiet=False):
    command = PShellCommand(argv, quiet)
    command.run()
    return command.run()
class PShellCommand(object):
    """Open an interactive shell with a :app:`Pyramid` app loaded.
    usage = '%prog config_uri'
    description = """\
    Open an interactive shell with a Pyramid app loaded.  This command
    accepts one positional argument named "config_uri" which specifies the
    PasteDeploy config file to use for the interactive shell. The format is
    "inifile#name". If the name is left off, the Pyramid default application
    will be assumed.  Example: "pshell myapp.ini#main"
    This command accepts one positional argument:
    ``config_uri`` -- specifies the PasteDeploy config file to use for the
    interactive shell. The format is ``inifile#name``. If the name is left
    off, ``main`` will be assumed.
    Example::
        $ pshell myapp.ini#main
    .. note:: If you do not point the loader directly at the section of the
              ini file containing your :app:`Pyramid` application, the
              command will attempt to find the app for you. If you are
              loading a pipeline that contains more than one :app:`Pyramid`
              application within it, the loader will use the last one.
    If you do not point the loader directly at the section of the ini file
    containing your Pyramid application, the command will attempt to
    find the app for you. If you are loading a pipeline that contains more
    than one Pyramid application within it, the loader will use the
    last one.
    """
    bootstrap = (bootstrap,) # for testing
    summary = "Open an interactive shell with a Pyramid application loaded"
    parser = optparse.OptionParser()
    parser = optparse.OptionParser(
        usage,
        description=textwrap.dedent(description)
        )
    parser.add_option('-p', '--python-shell',
                      action='store', type='string', dest='python_shell',
                      default='', help='ipython | bpython | python')
@@ -82,7 +80,7 @@
    def run(self, shell=None):
        if not self.args:
            self.out('Requires a config file argument')
            return
            return 2
        config_uri = self.args[0]
        config_file = config_uri.split('#', 1)[0]
        setup_logging(config_file)
pyramid/scripts/ptweens.py
@@ -1,5 +1,6 @@
import optparse
import sys
import textwrap
from pyramid.interfaces import ITweens
@@ -9,31 +10,29 @@
def main(argv=sys.argv, quiet=False):
    command = PTweensCommand(argv, quiet)
    command.run()
    return command.run()
class PTweensCommand(object):
    """Print all implicit and explicit :term:`tween` objects used by a
    Pyramid application.  The handler output includes whether the system is
    using an explicit tweens ordering (will be true when the
    ``pyramid.tweens`` setting is used) or an implicit tweens ordering (will
    be true when the ``pyramid.tweens`` setting is *not* used).
    usage = '%prog config_uri'
    description = """\
    Print all implicit and explicit tween objects used by a Pyramid
    application.  The handler output includes whether the system is using an
    explicit tweens ordering (will be true when the "pyramid.tweens"
    deployment setting is used) or an implicit tweens ordering (will be true
    when the "pyramid.tweens" deployment setting is *not* used).
    This command accepts one positional argument:
    ``config_uri`` -- specifies the PasteDeploy config file to use for the
    interactive shell. The format is ``inifile#name``. If the name is left
    off, ``main`` will be assumed.
    Example::
        $ ptweens myapp.ini#main
    This command accepts one positional argument named "config_uri" which
    specifies the PasteDeploy config file to use for the interactive
    shell. The format is "inifile#name". If the name is left off, "main"
    will be assumed.  Example: "ptweens myapp.ini#main".
    """
    summary = "Print all tweens related to a Pyramid application"
    parser = optparse.OptionParser(
        usage,
        description=textwrap.dedent(description),
        )
    stdout = sys.stdout
    parser = optparse.OptionParser()
    bootstrap = (bootstrap,) # testing
    def __init__(self, argv, quiet=False):
@@ -61,7 +60,7 @@
    def run(self):
        if not self.args:
            self.out('Requires a config file argument')
            return
            return 2
        config_uri = self.args[0]
        env = self.bootstrap[0](config_uri)
        registry = env['registry']
@@ -86,3 +85,4 @@
                self.out('Implicit Tween Chain')
                self.out('')
                self.show_chain(tweens.implicit())
        return 0
pyramid/scripts/pviews.py
@@ -1,36 +1,34 @@
import optparse
import sys
import textwrap
from pyramid.interfaces import IMultiView
from pyramid.paster import bootstrap
def main(argv=sys.argv, quiet=False):
    command = PViewsCommand(argv, quiet)
    command.run()
    return command.run()
class PViewsCommand(object):
    """Print, for a given URL, the views that might match. Underneath each
    usage = '%prog config_uri url'
    description = """\
    Print, for a given URL, the views that might match. Underneath each
    potentially matching route, list the predicates required. Underneath
    each route+predicate set, print each view that might match and its
    predicates.
    This command accepts two positional arguments:
    ``config_uri`` -- specifies the PasteDeploy config file to use for the
    interactive shell. The format is ``inifile#name``. If the name is left
    off, ``main`` will be assumed.
    ``url`` -- specifies the URL that will be used to find matching views.
    Example::
        $ proutes myapp.ini#main url
    This command accepts two positional arguments: "config_uri" specifies the
    PasteDeploy config file to use for the interactive shell. The format is
    "inifile#name". If the name is left off, "main" will be assumed.  "url"
    specifies the path info portion of a URL that will be used to find
    matching views.  Example: "proutes myapp.ini#main /url"
    """
    summary = "Print all views in an application that might match a URL"
    stdout = sys.stdout
    parser = optparse.OptionParser()
    parser = optparse.OptionParser(
        usage,
        description=textwrap.dedent(description)
        )
    bootstrap = (bootstrap,) # testing
@@ -231,7 +229,7 @@
    def run(self):
        if len(self.args) < 2:
            self.out('Command requires a config file arg and a url arg')
            return
            return 2
        config_uri, url = self.args
        if not url.startswith('/'):
            url = '/%s' % url
@@ -256,3 +254,5 @@
            else:
                self.out("    Not found.")
        self.out('')
        return 0
pyramid/tests/test_scaffolds/test_template.py
@@ -7,34 +7,34 @@
        from pyramid.scaffolds.template import Template
        return Template(name)
    def test_template_renderer_success(self):
    def test_render_template_success(self):
        inst = self._makeOne()
        result = inst.template_renderer('{{a}} {{b}}', {'a':'1', 'b':'2'})
        result = inst.render_template('{{a}} {{b}}', {'a':'1', 'b':'2'})
        self.assertEqual(result, bytes_('1 2'))
        
    def test_template_renderer_expr_failure(self):
    def test_render_template_expr_failure(self):
        inst = self._makeOne()
        self.assertRaises(AttributeError, inst.template_renderer,
        self.assertRaises(AttributeError, inst.render_template,
                          '{{a.foo}}', {'a':'1', 'b':'2'})
    def test_template_renderer_expr_success(self):
    def test_render_template_expr_success(self):
        inst = self._makeOne()
        result = inst.template_renderer('{{a.lower()}}', {'a':'A'})
        result = inst.render_template('{{a.lower()}}', {'a':'A'})
        self.assertEqual(result, b'a')
    def test_template_renderer_expr_success_via_pipe(self):
    def test_render_template_expr_success_via_pipe(self):
        inst = self._makeOne()
        result = inst.template_renderer('{{b|c|a.lower()}}', {'a':'A'})
        result = inst.render_template('{{b|c|a.lower()}}', {'a':'A'})
        self.assertEqual(result, b'a')
    def test_template_renderer_expr_success_via_pipe2(self):
    def test_render_template_expr_success_via_pipe2(self):
        inst = self._makeOne()
        result = inst.template_renderer('{{b|a.lower()|c}}', {'a':'A'})
        result = inst.render_template('{{b|a.lower()|c}}', {'a':'A'})
        self.assertEqual(result, b'a')
    def test_template_renderer_expr_value_is_None(self):
    def test_render_template_expr_value_is_None(self):
        inst = self._makeOne()
        result = inst.template_renderer('{{a}}', {'a':None})
        result = inst.render_template('{{a}}', {'a':None})
        self.assertEqual(result, b'')
    def test_module_dir(self):
@@ -83,7 +83,7 @@
        self.assertEqual(copydir.output_dir, 'output dir')
        self.assertEqual(copydir.vars, {'a':1})
        self.assertEqual(copydir.kw,
                         {'template_renderer':inst.template_renderer,
                         {'template_renderer':inst.render_template,
                          'indent':1,
                          'verbosity':1,
                          'simulate':False,
@@ -117,6 +117,10 @@
        inst.run(command, 'output dir', {'a':1})
        self.assertEqual(L, ['output dir'])
    def test_check_vars(self):
        inst = self._makeOne()
        self.assertRaises(RuntimeError, inst.check_vars, 'one', 'two')
class DummyCopydir(object):
    def copy_dir(self, template_dir, output_dir, vars, **kw):
        self.template_dir = template_dir
pyramid/tests/test_scripts/test_pcreate.py
@@ -22,7 +22,7 @@
    def test_run_show_scaffolds_exist(self):
        cmd = self._makeOne('-l')
        result = cmd.run()
        self.assertEqual(result, True)
        self.assertEqual(result, 0)
        out = self.out_.getvalue()
        self.assertTrue(out.startswith('Available scaffolds'))
        
@@ -30,14 +30,14 @@
        cmd = self._makeOne('-l')
        cmd.scaffolds = []
        result = cmd.run()
        self.assertEqual(result, True)
        self.assertEqual(result, 0)
        out = self.out_.getvalue()
        self.assertTrue(out.startswith('No scaffolds available'))
        
    def test_run_no_scaffold_name(self):
        cmd = self._makeOne()
        result = cmd.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 2)
        out = self.out_.getvalue()
        self.assertTrue(out.startswith(
            'You must provide at least one scaffold name'))
@@ -45,14 +45,14 @@
    def test_no_project_name(self):
        cmd = self._makeOne('-s', 'dummy')
        result = cmd.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 2)
        out = self.out_.getvalue()
        self.assertTrue(out.startswith('You must provide a project name'))
    def test_unknown_scaffold_name(self):
        cmd = self._makeOne('-s', 'dummyXX', 'distro')
        result = cmd.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 2)
        out = self.out_.getvalue()
        self.assertTrue(out.startswith('Unavailable scaffolds'))
@@ -62,7 +62,7 @@
        scaffold = DummyScaffold('dummy')
        cmd.scaffolds = [scaffold]
        result = cmd.run()
        self.assertEqual(result, True)
        self.assertEqual(result, 0)
        self.assertEqual(
            scaffold.output_dir,
            os.path.normpath(os.path.join(os.getcwd(), 'Distro'))
@@ -78,7 +78,7 @@
        scaffold = DummyScaffold('dummy')
        cmd.scaffolds = [scaffold]
        result = cmd.run()
        self.assertEqual(result, True)
        self.assertEqual(result, 0)
        self.assertEqual(
            scaffold.output_dir,
            os.path.normpath(os.path.join(os.getcwd(), 'Distro'))
@@ -94,7 +94,7 @@
        scaffold2 = DummyScaffold('dummy2')
        cmd.scaffolds = [scaffold1, scaffold2]
        result = cmd.run()
        self.assertEqual(result, True)
        self.assertEqual(result, 0)
        self.assertEqual(
            scaffold1.output_dir,
            os.path.normpath(os.path.join(os.getcwd(), 'Distro'))
@@ -117,7 +117,7 @@
    def test_it(self):
        result = self._callFUT(['pcreate'])
        self.assertEqual(result, None)
        self.assertEqual(result, 2)
class DummyScaffold(object):
    def __init__(self, name):
pyramid/tests/test_scripts/test_prequest.py
New file
@@ -0,0 +1,141 @@
import unittest
class TestPRequestCommand(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.scripts.prequest import PRequestCommand
        return PRequestCommand
    def _makeOne(self, argv):
        cmd = self._getTargetClass()(argv)
        cmd.get_app = self.get_app
        self._out = []
        cmd.out = self.out
        return cmd
    def get_app(self, spec, app_name=None):
        self._spec = spec
        self._app_name = app_name
        def helloworld(environ, start_request):
            self._environ = environ
            self._path_info = environ['PATH_INFO']
            start_request('200 OK', [])
            return [b'abc']
        return helloworld
    def out(self, msg):
        self._out.append(msg)
    def test_command_not_enough_args(self):
        command = self._makeOne([])
        command.run()
        self.assertEqual(self._out, ['You must provide at least two arguments'])
    def test_command_two_args(self):
        command = self._makeOne(['', 'development.ini', '/'])
        command.run()
        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_path_doesnt_start_with_slash(self):
        command = self._makeOne(['', 'development.ini', 'abc'])
        command.run()
        self.assertEqual(self._path_info, '/abc')
        self.assertEqual(self._spec, 'development.ini')
        self.assertEqual(self._app_name, None)
        self.assertEqual(self._out, ['abc'])
    def test_command_has_bad_config_header(self):
        command = self._makeOne(
            ['', '--header=name','development.ini', '/'])
        command.run()
        self.assertEqual(
            self._out[0],
            ("Bad --header=name option, value must be in the form "
             "'name:value'"))
    def test_command_has_good_header_var(self):
        command = self._makeOne(
            ['', '--header=name:value','development.ini', '/'])
        command.run()
        self.assertEqual(self._environ['HTTP_NAME'], 'value')
        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', '/'])
        command.run()
        self.assertEqual(self._environ['CONTENT_TYPE'], 'app/foo')
        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_multiple_header_vars(self):
        command = self._makeOne(
            ['',
             '--header=name:value',
             '--header=name2:value2',
             'development.ini',
             '/'])
        command.run()
        self.assertEqual(self._environ['HTTP_NAME'], 'value')
        self.assertEqual(self._environ['HTTP_NAME2'], 'value2')
        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_get(self):
        command = self._makeOne(['', '--method=GET', 'development.ini', '/'])
        command.run()
        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_post(self):
        from pyramid.compat import NativeIO
        command = self._makeOne(['', '--method=POST', 'development.ini', '/'])
        stdin = NativeIO()
        command.stdin = stdin
        command.run()
        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_extra_args_used_in_query_string(self):
        command = self._makeOne(['', 'development.ini', '/', 'a=1%','b=2','c'])
        command.run()
        self.assertEqual(self._environ['QUERY_STRING'], 'a=1%25&b=2&c')
        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_display_headers(self):
        command = self._makeOne(
            ['', '--display-headers', 'development.ini', '/'])
        command.run()
        self.assertEqual(self._path_info, '/')
        self.assertEqual(self._spec, 'development.ini')
        self.assertEqual(self._app_name, None)
        self.assertEqual(
            self._out,
            ['200 OK', 'Content-Type: text/html; charset=UTF-8', 'abc'])
class Test_main(unittest.TestCase):
    def _callFUT(self, argv):
        from pyramid.scripts.prequest import main
        return main(argv, True)
    def test_it(self):
        result = self._callFUT(['prequest'])
        self.assertEqual(result, 2)
pyramid/tests/test_scripts/test_proutes.py
@@ -19,7 +19,7 @@
        L = []
        command.out = L.append
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L, [])
    def test_no_mapper(self):
@@ -28,7 +28,7 @@
        L = []
        command.out = L.append
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L, [])
    def test_single_route_no_route_registered(self):
@@ -39,7 +39,7 @@
        L = []
        command.out = L.append
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>'])
@@ -51,7 +51,7 @@
        L = []
        command.out = L.append
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>'])
@@ -72,7 +72,7 @@
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        self.assertEqual(L[-1].split()[:3], ['a', '/a', 'None'])
@@ -98,7 +98,7 @@
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        self.assertEqual(L[-1].split()[:4], ['a', '/a', '<function', 'view'])
        
@@ -127,7 +127,7 @@
        command.out = L.append
        command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(len(L), 3)
        self.assertEqual(L[-1].split()[:3], ['a', '/a', '<unknown>'])
@@ -146,5 +146,5 @@
    def test_it(self):
        result = self._callFUT(['proutes'])
        self.assertEqual(result, None)
        self.assertEqual(result, 2)
pyramid/tests/test_scripts/test_pserve.py
@@ -23,7 +23,7 @@
    def test_run_no_args(self):
        inst = self._makeOne()
        result = inst.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 2)
        self.assertEqual(self.out_.getvalue(), 'You must give a config file')
    def test_run_stop_daemon_no_such_pid_file(self):
@@ -73,7 +73,7 @@
    def test_it(self):
        result = self._callFUT(['pserve'])
        self.assertEqual(result, None)
        self.assertEqual(result, 2)
class TestLazyWriter(unittest.TestCase):
    def _makeOne(self, filename, mode='w'):
pyramid/tests/test_scripts/test_pshell.py
@@ -337,5 +337,5 @@
    def test_it(self):
        result = self._callFUT(['pshell'])
        self.assertEqual(result, None)
        self.assertEqual(result, 2)
pyramid/tests/test_scripts/test_ptweens.py
@@ -18,7 +18,7 @@
        L = []
        command.out = L.append
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L, [])
    def test_command_implicit_tweens_only(self):
@@ -28,7 +28,7 @@
        L = []
        command.out = L.append
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(
           L[0],
           '"pyramid.tweens" config value NOT set (implicitly ordered tweens '
@@ -41,7 +41,7 @@
        L = []
        command.out = L.append
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(
           L[0],
           '"pyramid.tweens" config value set (explicitly ordered tweens used)')
@@ -58,4 +58,4 @@
    def test_it(self):
        result = self._callFUT(['ptweens'])
        self.assertEqual(result, None)
        self.assertEqual(result, 2)
pyramid/tests/test_scripts/test_pviews.py
@@ -231,7 +231,7 @@
        command._find_view = lambda arg1, arg2: None
        command.args = ('/foo/bar/myapp.ini#myapp', '/a')
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L[1], 'URL = /a')
        self.assertEqual(L[3], '    Not found.')
@@ -244,7 +244,7 @@
        command._find_view = lambda arg1, arg2: None
        command.args = ('/foo/bar/myapp.ini#myapp', 'a')
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L[1], 'URL = /a')
        self.assertEqual(L[3], '    Not found.')
@@ -258,7 +258,7 @@
        command._find_view = lambda arg1, arg2: view
        command.args = ('/foo/bar/myapp.ini#myapp', '/a')
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L[1], 'URL = /a')
        self.assertEqual(L[3], '    context: context')
        self.assertEqual(L[4], '    view name: a')
@@ -276,7 +276,7 @@
        command._find_view = lambda arg1, arg2: view
        command.args = ('/foo/bar/myapp.ini#myapp', '/a')
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L[1], 'URL = /a')
        self.assertEqual(L[3], '    context: context')
        self.assertEqual(L[4], '    view name: a')
@@ -294,7 +294,7 @@
        command._find_view = lambda arg1, arg2: view
        command.args = ('/foo/bar/myapp.ini#myapp', '/a')
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L[1], 'URL = /a')
        self.assertEqual(L[3], '    context: context')
        self.assertEqual(L[4], '    view name: a')
@@ -315,7 +315,7 @@
        command._find_view = lambda arg1, arg2: view
        command.args = ('/foo/bar/myapp.ini#myapp', '/a')
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L[1], 'URL = /a')
        self.assertEqual(L[3], '    context: context')
        self.assertEqual(L[4], '    view name: a')
@@ -335,7 +335,7 @@
        command._find_view = lambda arg1, arg2: view
        command.args = ('/foo/bar/myapp.ini#myapp', '/a')
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L[1], 'URL = /a')
        self.assertEqual(L[3], '    context: context')
        self.assertEqual(L[4], '    view name: a')
@@ -363,7 +363,7 @@
        command._find_view = lambda arg1, arg2: multiview2
        command.args = ('/foo/bar/myapp.ini#myapp', '/a')
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L[1], 'URL = /a')
        self.assertEqual(L[3], '    context: context')
        self.assertEqual(L[4], '    view name: a')
@@ -386,7 +386,7 @@
        command._find_view = lambda arg1, arg2: view
        command.args = ('/foo/bar/myapp.ini#myapp', '/a')
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L[1], 'URL = /a')
        self.assertEqual(L[3], '    context: context')
        self.assertEqual(L[4], '    view name: a')
@@ -412,7 +412,7 @@
        command._find_view = lambda arg1, arg2: multiview
        command.args = ('/foo/bar/myapp.ini#myapp', '/a')
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L[1], 'URL = /a')
        self.assertEqual(L[3], '    context: context')
        self.assertEqual(L[4], '    view name: a')
@@ -433,7 +433,7 @@
        command._find_view = lambda arg1, arg2: multiview
        command.args = ('/foo/bar/myapp.ini#myapp', '/a')
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L[1], 'URL = /a')
        self.assertEqual(L[3], '    context: context')
        self.assertEqual(L[4], '    view name: a')
@@ -457,7 +457,7 @@
        command._find_view = lambda arg1, arg2: multiview
        command.args = ('/foo/bar/myapp.ini#myapp', '/a')
        result = command.run()
        self.assertEqual(result, None)
        self.assertEqual(result, 0)
        self.assertEqual(L[1], 'URL = /a')
        self.assertEqual(L[3], '    context: context')
        self.assertEqual(L[4], '    view name: a')
@@ -472,4 +472,4 @@
    def test_it(self):
        result = self._callFUT(['pviews'])
        self.assertEqual(result, None)
        self.assertEqual(result, 2)
setup.py
@@ -56,7 +56,7 @@
        ])
setup(name='pyramid',
      version='1.4dev',
      version='1.3a2',
      description=('The Pyramid web application development framework, a '
                   'Pylons project'),
      long_description=README + '\n\n' +  CHANGES,
@@ -98,6 +98,7 @@
        proutes = pyramid.scripts.proutes:main
        pviews = pyramid.scripts.pviews:main
        ptweens = pyramid.scripts.ptweens:main
        prequest = pyramid.scripts.prequest:main
        [paste.server_runner]
        wsgiref = pyramid.scripts.pserve:wsgiref_server_runner
        cherrypy = pyramid.scripts.pserve:cherrypy_server_runner