summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-12-15 17:29:01 -0500
committerChris McDonough <chrism@plope.com>2011-12-15 17:29:01 -0500
commitc8061ee1d797cb666e1d45e19765ede565d21915 (patch)
treebc0ec7302616907c71024f820195b54ff8e6d9c0
parent4eab20d35162b86dd0168ba86e9fd9e51050071a (diff)
downloadpyramid-c8061ee1d797cb666e1d45e19765ede565d21915.tar.gz
pyramid-c8061ee1d797cb666e1d45e19765ede565d21915.tar.bz2
pyramid-c8061ee1d797cb666e1d45e19765ede565d21915.zip
finish prequest feature
-rw-r--r--CHANGES.txt10
-rw-r--r--docs/narr/commandline.rst65
-rw-r--r--docs/whatsnew-1.3.rst9
-rw-r--r--pyramid/scripts/prequest.py60
-rw-r--r--pyramid/tests/test_scripts/test_prequest.py141
5 files changed, 236 insertions, 49 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 74f79e5ae..c8a156d2e 100644
--- a/CHANGES.txt
+++ b/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)
==================
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index 66ef46671..b9aa2c8c3 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -121,7 +121,8 @@ The Interactive Shell
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 @@ For example:
.. 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 @@ configured without any explicit tweens:
.. 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 @@ is used:
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
diff --git a/docs/whatsnew-1.3.rst b/docs/whatsnew-1.3.rst
index b0afacfe6..a69efd268 100644
--- a/docs/whatsnew-1.3.rst
+++ b/docs/whatsnew-1.3.rst
@@ -81,9 +81,9 @@ 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
@@ -296,6 +296,9 @@ Documentation Enhancements
- 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
------------------
diff --git a/pyramid/scripts/prequest.py b/pyramid/scripts/prequest.py
index 85339f743..6a860c900 100644
--- a/pyramid/scripts/prequest.py
+++ b/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 @@ class PRequestCommand(object):
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 @@ class PRequestCommand(object):
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 @@ class PRequestCommand(object):
}
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 @@ class PRequestCommand(object):
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
diff --git a/pyramid/tests/test_scripts/test_prequest.py b/pyramid/tests/test_scripts/test_prequest.py
new file mode 100644
index 000000000..34c4b3591
--- /dev/null
+++ b/pyramid/tests/test_scripts/test_prequest.py
@@ -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)