Allow stdin data with password prompt; added tests

Closes #70
This commit is contained in:
Jakub Roztocil 2012-07-16 23:41:27 +02:00
parent 85b3a016eb
commit 681b652bf9
4 changed files with 82 additions and 25 deletions

View File

@ -118,6 +118,7 @@ Flags
^^^^^
Most of the flags mirror the arguments understood by ``requests.request``. See ``http -h`` for more details::
$ http --help
usage: http [-h] [--version] [--json | --form] [--traceback]
[--pretty | --ugly]
[--print OUTPUT_OPTIONS | --verbose | --headers | --body]
@ -147,13 +148,15 @@ Most of the flags mirror the arguments understood by ``requests.request``. See `
optional arguments:
-h, --help show this help message and exit
--version show program's version number and exit
--json, -j (default) Data items are serialized as a JSON object.
The Content-Type and Accept headers are set to
application/json (if not set via the command line).
--form, -f Data items are serialized as form fields. The Content-
Type is set to application/x-www-form-urlencoded (if
not specifid). The presence of any file fields results
into a multipart/form-data request.
--json, -j (default) Data items from the command line are
serialized as a JSON object. The Content-Type and
Accept headers are set to application/json (if not
specified).
--form, -f Data items from the command line are serialized as
form fields. The Content-Type is set to application/x
-www-form-urlencoded (if not specified). The presence
of any file fields results into a multipart/form-data
request.
--traceback Print exception traceback should one occur.
--pretty If stdout is a terminal, the response is prettified by
default (colorized and indented if it is JSON). This
@ -180,8 +183,8 @@ Most of the flags mirror the arguments understood by ``requests.request``. See `
make sure that the $TERM environment variable is set
to "xterm-256color" or similar (e.g., via `export TERM
=xterm-256color' in your ~/.bashrc).
--auth AUTH, -a AUTH username:password. If the password is omitted, HTTPie
will prompt for it.
--auth AUTH, -a AUTH username:password. If the password is omitted (-a
username), HTTPie will prompt for it.
--auth-type {basic,digest}
The authentication mechanism to be used. Defaults to
"basic".
@ -223,7 +226,10 @@ Changelog
---------
* `0.2.3dev <https://github.com/jkbr/httpie/compare/0.2.2...master>`_
* --auth now prompts for a password if none is provided.
* Added support for request payloads from a file path with automatic ``Content-Type`` (``http URL @/path``).
* Fixed missing query string when displaing the request headers via ``--verbose``.
* Fixed Content-Type for requests with no data.
* `0.2.2 <https://github.com/jkbr/httpie/compare/0.2.1...0.2.2>`_ (2012-06-24)
* The ``METHOD`` positional argument can now be omitted (defaults to ``GET``, or to ``POST`` with data).
* Fixed --verbose --form.

View File

@ -123,10 +123,10 @@ parser.add_argument(
# ``requests.request`` keyword arguments.
parser.add_argument(
'--auth', '-a', type=cliparse.AuthCredentials(cliparse.SEP_COMMON),
'--auth', '-a', type=cliparse.AuthCredentialsType(cliparse.SEP_COMMON),
help=_('''
username:password.
If the password is omitted, HTTPie will prompt for it.
If the password is omitted (-a username), HTTPie will prompt for it.
'''),
)

View File

@ -8,9 +8,7 @@ import re
import json
import argparse
import mimetypes
from collections import namedtuple
from getpass import getpass
import getpass
try:
from collections import OrderedDict
@ -54,13 +52,22 @@ class Parser(argparse.ArgumentParser):
def parse_args(self, args=None, namespace=None,
stdin=sys.stdin,
stdin_isatty=sys.stdin.isatty()):
args = super(Parser, self).parse_args(args, namespace)
self._validate_output_options(args)
self._validate_auth_options(args)
self._guess_method(args, stdin_isatty)
self._parse_items(args)
if not stdin_isatty:
self._body_from_file(args, stdin)
if args.auth and not args.auth.has_password():
# stdin has already been read (if not a tty) so
# it's save to prompt now.
args.auth.prompt_password()
return args
def _body_from_file(self, args, f):
@ -161,12 +168,24 @@ class ParseError(Exception):
pass
KeyValue = namedtuple('KeyValue', ['key', 'value', 'sep', 'orig'])
class KeyValue(object):
"""Base key-value pair parsed from CLI."""
def __init__(self, key, value, sep, orig):
self.key = key
self.value = value
self.sep = sep
self.orig = orig
def __eq__(self, other):
return self.__dict__ == other.__dict__
class KeyValueType(object):
"""A type used with `argparse`."""
key_value_class = KeyValue
def __init__(self, *separators):
self.separators = separators
self.escapes = ['\\\\' + sep for sep in separators]
@ -202,21 +221,43 @@ class KeyValueType(object):
for sepstr in self.separators:
key = key.replace('\\' + sepstr, sepstr)
value = value.replace('\\' + sepstr, sepstr)
return KeyValue(key=key, value=value, sep=sep, orig=string)
return self.key_value_class(key=key, value=value, sep=sep, orig=string)
class AuthCredentials(KeyValueType):
class AuthCredentials(KeyValue):
"""
Represents parsed credentials.
"""
def _getpass(self, prompt):
return getpass.getpass(prompt)
def has_password(self):
return self.value is not None
def prompt_password(self):
try:
self.value = self._getpass("Password for user '%s': " % self.key)
except (EOFError, KeyboardInterrupt):
sys.stderr.write('\n')
sys.exit(0)
class AuthCredentialsType(KeyValueType):
key_value_class = AuthCredentials
def __call__(self, string):
try:
return super(AuthCredentials, self).__call__(string)
return super(AuthCredentialsType, self).__call__(string)
except argparse.ArgumentTypeError:
try:
password = getpass("Password for user '%s': " % string)
except (EOFError, KeyboardInterrupt):
sys.stderr.write('\n')
sys.exit(0)
return KeyValue(key=string, value=password, sep=SEP_COMMON,
orig=string)
# No password provided, will prompt for it later.
return self.key_value_class(
key=string,
value=None,
sep=SEP_COMMON,
orig=string
)
def parse_items(items, data=None, headers=None, files=None):

View File

@ -286,6 +286,16 @@ class AuthTest(BaseTestCase):
self.assertIn('"authenticated": true', r)
self.assertIn('"user": "user"', r)
def test_password_prompt(self):
cliparse.AuthCredentials._getpass = lambda self, prompt: 'password'
r = http('--auth', 'user',
'GET', 'httpbin.org/basic-auth/user/password')
self.assertIn('HTTP/1.1 200', r)
self.assertIn('"authenticated": true', r)
self.assertIn('"user": "user"', r)
#################################################################
# CLI argument parsing related tests.