diff options
| author | Chris McDonough <chrism@plope.com> | 2011-12-15 17:29:01 -0500 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-12-15 17:29:01 -0500 |
| commit | c8061ee1d797cb666e1d45e19765ede565d21915 (patch) | |
| tree | bc0ec7302616907c71024f820195b54ff8e6d9c0 | |
| parent | 4eab20d35162b86dd0168ba86e9fd9e51050071a (diff) | |
| download | pyramid-c8061ee1d797cb666e1d45e19765ede565d21915.tar.gz pyramid-c8061ee1d797cb666e1d45e19765ede565d21915.tar.bz2 pyramid-c8061ee1d797cb666e1d45e19765ede565d21915.zip | |
finish prequest feature
| -rw-r--r-- | CHANGES.txt | 10 | ||||
| -rw-r--r-- | docs/narr/commandline.rst | 65 | ||||
| -rw-r--r-- | docs/whatsnew-1.3.rst | 9 | ||||
| -rw-r--r-- | pyramid/scripts/prequest.py | 60 | ||||
| -rw-r--r-- | pyramid/tests/test_scripts/test_prequest.py | 141 |
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) |
