Chris McDonough
2011-12-15 c8061ee1d797cb666e1d45e19765ede565d21915
finish prequest feature
1 files added
4 files modified
285 ■■■■ changed files
CHANGES.txt 10 ●●●●● patch | view | raw | blame | history
docs/narr/commandline.rst 65 ●●●●● patch | view | raw | blame | history
docs/whatsnew-1.3.rst 9 ●●●●● patch | view | raw | blame | history
pyramid/scripts/prequest.py 60 ●●●● patch | view | raw | blame | history
pyramid/tests/test_scripts/test_prequest.py 141 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -1,3 +1,13 @@
Next release
============
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".
1.3a2 (2011-12-14)
==================
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
@@ -416,6 +417,64 @@
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:
Writing a Script
docs/whatsnew-1.3.rst
@@ -81,9 +81,9 @@
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
@@ -296,6 +296,9 @@
- Added a narrative docs chapter named :ref:`scaffolding_chapter`.
- Added a description of the ``prequest`` command-line script at
  :ref:`invoking_a_request`.
Dependency Changes
------------------
pyramid/scripts/prequest.py
@@ -1,16 +1,14 @@
import optparse
import os
import re
import sys
import textwrap
from pyramid.compat import url_quote
from pyramid.request import Request
from paste.deploy import loadapp
from pyramid.paster import get_app
def main(argv=sys.argv, quiet=False):
    command = PRequestCommand(argv, quiet)
    command.run()
    return command.run()
class PRequestCommand(object):
    description = """\
@@ -47,14 +45,6 @@
        type="string",
        )
    parser.add_option(
        '-c', '--config-var',
        dest='config_vars',
        metavar='NAME:VALUE',
        action='append',
        help="Variable to make available in the config for %()s substitution "
        "(you can use this option multiple times)"
        )
    parser.add_option(
        '--header',
        dest='headers',
        metavar='NAME:VALUE',
@@ -71,59 +61,43 @@
    parser.add_option(
        '-m', '--method',
        dest='method',
        choices=['GET', 'POST'],
        choices=['GET', 'HEAD', 'POST', 'DELETE'],
        type='choice',
        help='Request method type (GET or POST)',
        help='Request method type (GET, POST, DELETE)',
        )
    _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
    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, delim='\n'): # pragma: no cover
    def out(self, msg): # pragma: no cover
        if not self.quiet:
            sys.stdout.write(msg+delim)
            sys.stdout.flush()
            print(msg)
    def run(self):
        if not len(self.args) >= 2:
            self.out('You must provide at least two arguments')
            return
            return 2
        app_spec = self.args[0]
        path = self.args[1]
        if not path.startswith('/'):
            path = '/' + path 
        vars = {}
        if self.options.config_vars:
            for item in self.options.config_vars:
                if ':' not in item:
                    raise ValueError(
                        "Bad option, should be name:value "
                        ": --config-var=%s" % item)
                name, value = item.split(':', 1)
                vars[name] = value
        headers = {}
        if self.options.headers:
            for item in self.options.headers:
                if ':' not in item:
                    raise ValueError(
                        "Bad option, should be name:value : --header=%s" % 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()
        if not self._scheme_re.search(app_spec):
            app_spec = 'config:' + app_spec
        if self.options.app_name:
            if '#' in app_spec:
                app_spec = app_spec.split('#', 1)[0]
            app_spec = app_spec + '#' + self.options.app_name
        app = loadapp(app_spec, relative_to=os.getcwd(), global_conf=vars)
        app = self.get_app(app_spec, self.options.app_name)
        request_method = (self.options.method or 'GET').upper()
        qs = []
@@ -156,7 +130,7 @@
            }
        if request_method == 'POST':
            environ['wsgi.input'] = sys.stdin
            environ['wsgi.input'] = self.stdin
            environ['CONTENT_LENGTH'] = '-1'
        for name, value in headers.items():
@@ -172,5 +146,5 @@
            self.out(response.status)
            for name, value in response.headerlist:
                self.out('%s: %s' % (name, value))
        self.out(response.body, '')
        self.out(response.ubody)
        return 0
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)