Added docstrings, refactored input.

This commit is contained in:
Jakub Roztocil 2012-07-26 06:37:03 +02:00
parent f26f2f1438
commit f45cc0eec0
8 changed files with 129 additions and 101 deletions

View File

@ -76,7 +76,7 @@ Raw JSON fields (``field:=value``)
This item type is needed when ``Content-Type`` is JSON and a field's value This item type is needed when ``Content-Type`` is JSON and a field's value
is a ``Boolean``, ``Number``, nested ``Object`` or an ``Array``, because is a ``Boolean``, ``Number``, nested ``Object`` or an ``Array``, because
simple data items are always serialized as ``String``. simple data items are always serialized as ``String``.
E.g. ``pies:=[1,2,3]``,or ``'meals=["ham", "spam"]'`` (mind the quotes). E.g. ``pies:=[1,2,3]``, or ``'meals=["ham", "spam"]'`` (mind the quotes).
File fields (``field@/path/to/file``) File fields (``field@/path/to/file``)
Only available with ``-f`` / ``--form``. Use ``@`` as the separator, e.g., Only available with ``-f`` / ``--form``. Use ``@`` as the separator, e.g.,

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """The main entry point. Invoke as `http' or `python -m httpie'.
The main entry point. Invoke as `http' or `python -m httpie'.
""" """
import sys import sys

View File

@ -1,22 +1,24 @@
""" """CLI arguments definition.
CLI definition.
NOTE: the CLI interface may change before reaching v1.0.
""" """
from . import __doc__ from . import __doc__
from . import __version__ from . import __version__
from . import cliparse
from .output import AVAILABLE_STYLES from .output import AVAILABLE_STYLES
from .input import (Parser, AuthCredentialsArgType, KeyValueArgType,
PRETTIFY_STDOUT_TTY_ONLY,
SEP_PROXY, SEP_CREDENTIALS, SEP_GROUP_ITEMS,
OUT_REQ_HEAD, OUT_REQ_BODY, OUT_RESP_HEAD,
OUT_RESP_BODY, OUTPUT_OPTIONS)
def _(text): def _(text):
"""Normalize white space.""" """Normalize whitespace."""
return ' '.join(text.strip().split()) return ' '.join(text.strip().split())
desc = '%s <http://httpie.org>' parser = Parser(description='%s <http://httpie.org>' % __doc__.strip())
parser = cliparse.Parser(
description=desc % __doc__.strip(),
)
parser.add_argument('--version', action='version', version=__version__) parser.add_argument('--version', action='version', version=__version__)
@ -58,7 +60,7 @@ parser.add_argument(
prettify = parser.add_mutually_exclusive_group(required=False) prettify = parser.add_mutually_exclusive_group(required=False)
prettify.add_argument( prettify.add_argument(
'--pretty', dest='prettify', action='store_true', '--pretty', dest='prettify', action='store_true',
default=cliparse.PRETTIFY_STDOUT_TTY_ONLY, default=PRETTIFY_STDOUT_TTY_ONLY,
help=_(''' help=_('''
If stdout is a terminal, the response is prettified If stdout is a terminal, the response is prettified
by default (colorized and indented if it is JSON). by default (colorized and indented if it is JSON).
@ -85,35 +87,35 @@ output_options.add_argument('--print', '-p', dest='output_options',
If the output is piped to another program or to a file, If the output is piped to another program or to a file,
then only the body is printed by default. then only the body is printed by default.
'''.format( '''.format(
request_headers=cliparse.OUT_REQ_HEAD, request_headers=OUT_REQ_HEAD,
request_body=cliparse.OUT_REQ_BODY, request_body=OUT_REQ_BODY,
response_headers=cliparse.OUT_RESP_HEAD, response_headers=OUT_RESP_HEAD,
response_body=cliparse.OUT_RESP_BODY, response_body=OUT_RESP_BODY,
)) ))
) )
output_options.add_argument( output_options.add_argument(
'--verbose', '-v', dest='output_options', '--verbose', '-v', dest='output_options',
action='store_const', const=''.join(cliparse.OUTPUT_OPTIONS), action='store_const', const=''.join(OUTPUT_OPTIONS),
help=_(''' help=_('''
Print the whole request as well as the response. Print the whole request as well as the response.
Shortcut for --print={0}. Shortcut for --print={0}.
'''.format(''.join(cliparse.OUTPUT_OPTIONS))) '''.format(''.join(OUTPUT_OPTIONS)))
) )
output_options.add_argument( output_options.add_argument(
'--headers', '-h', dest='output_options', '--headers', '-h', dest='output_options',
action='store_const', const=cliparse.OUT_RESP_HEAD, action='store_const', const=OUT_RESP_HEAD,
help=_(''' help=_('''
Print only the response headers. Print only the response headers.
Shortcut for --print={0}. Shortcut for --print={0}.
'''.format(cliparse.OUT_RESP_HEAD)) '''.format(OUT_RESP_HEAD))
) )
output_options.add_argument( output_options.add_argument(
'--body', '-b', dest='output_options', '--body', '-b', dest='output_options',
action='store_const', const=cliparse.OUT_RESP_BODY, action='store_const', const=OUT_RESP_BODY,
help=_(''' help=_('''
Print only the response body. Print only the response body.
Shortcut for --print={0}. Shortcut for --print={0}.
'''.format(cliparse.OUT_RESP_BODY)) '''.format(OUT_RESP_BODY))
) )
parser.add_argument( parser.add_argument(
@ -149,7 +151,7 @@ parser.add_argument(
# ``requests.request`` keyword arguments. # ``requests.request`` keyword arguments.
parser.add_argument( parser.add_argument(
'--auth', '-a', '--auth', '-a',
type=cliparse.AuthCredentialsArgType(cliparse.SEP_CREDENTIALS), type=AuthCredentialsArgType(SEP_CREDENTIALS),
help=_(''' help=_('''
username:password. username:password.
If only the username is provided (-a username), If only the username is provided (-a username),
@ -177,7 +179,7 @@ parser.add_argument(
) )
parser.add_argument( parser.add_argument(
'--proxy', default=[], action='append', '--proxy', default=[], action='append',
type=cliparse.KeyValueArgType(cliparse.SEP_PROXY), type=KeyValueArgType(SEP_PROXY),
help=_(''' help=_('''
String mapping protocol to the URL of the proxy String mapping protocol to the URL of the proxy
(e.g. http:foo.bar:3128). (e.g. http:foo.bar:3128).
@ -224,7 +226,7 @@ parser.add_argument(
parser.add_argument( parser.add_argument(
'items', nargs='*', 'items', nargs='*',
metavar='ITEM', metavar='ITEM',
type=cliparse.KeyValueArgType(*cliparse.SEP_GROUP_ITEMS), type=KeyValueArgType(*SEP_GROUP_ITEMS),
help=_(''' help=_('''
A key-value pair whose type is defined by the A key-value pair whose type is defined by the
separator used. It can be an HTTP header (header:value), separator used. It can be an HTTP header (header:value),

View File

@ -1,3 +1,13 @@
"""This module provides the main functionality of HTTPie.
Invocation flow:
1. Read, validate and process the input (args, `stdin`).
2. Create a request and send it, get the response.
3. Process and format the requested parts of the request-response exchange.
4. Write to `stdout` and exit.
"""
import sys import sys
import json import json
@ -7,8 +17,10 @@ from requests.compat import str
from .models import HTTPMessage, Environment from .models import HTTPMessage, Environment
from .output import OutputProcessor from .output import OutputProcessor
from . import cliparse from .input import (PRETTIFY_STDOUT_TTY_ONLY,
from . import cli OUT_REQ_BODY, OUT_REQ_HEAD,
OUT_RESP_HEAD, OUT_RESP_BODY)
from .cli import parser
TYPE_FORM = 'application/x-www-form-urlencoded; charset=utf-8' TYPE_FORM = 'application/x-www-form-urlencoded; charset=utf-8'
@ -16,6 +28,7 @@ TYPE_JSON = 'application/json; charset=utf-8'
def get_response(args, env): def get_response(args, env):
"""Send the request and return a `request.Response`."""
auto_json = args.data and not args.form auto_json = args.data and not args.form
if args.json or auto_json: if args.json or auto_json:
@ -69,23 +82,20 @@ def get_response(args, env):
sys.exit(1) sys.exit(1)
def get_output(args, env, response): def get_output(args, env, request, response):
"""Format parts of the `request`-`response` exchange
according to `args` and `env` and return a `unicode`.
do_prettify = ( """
args.prettify is True or do_prettify = (args.prettify is True
(args.prettify == cliparse.PRETTIFY_STDOUT_TTY_ONLY or (args.prettify == PRETTIFY_STDOUT_TTY_ONLY
and env.stdout_isatty) and env.stdout_isatty))
)
do_output_request = ( do_output_request = (OUT_REQ_HEAD in args.output_options
cliparse.OUT_REQ_HEAD in args.output_options or OUT_REQ_BODY in args.output_options)
or cliparse.OUT_REQ_BODY in args.output_options
)
do_output_response = ( do_output_response = (OUT_RESP_HEAD in args.output_options
cliparse.OUT_RESP_HEAD in args.output_options or OUT_RESP_BODY in args.output_options)
or cliparse.OUT_RESP_BODY in args.output_options
)
prettifier = None prettifier = None
if do_prettify: if do_prettify:
@ -95,10 +105,11 @@ def get_output(args, env, response):
buf = [] buf = []
if do_output_request: if do_output_request:
req = HTTPMessage.from_request(response.request).format( req_msg = HTTPMessage.from_request(request)
req = req_msg.format(
prettifier=prettifier, prettifier=prettifier,
with_headers=cliparse.OUT_REQ_HEAD in args.output_options, with_headers=OUT_REQ_HEAD in args.output_options,
with_body=cliparse.OUT_REQ_BODY in args.output_options with_body=OUT_REQ_BODY in args.output_options
) )
buf.append(req) buf.append(req)
buf.append('\n') buf.append('\n')
@ -106,10 +117,11 @@ def get_output(args, env, response):
buf.append('\n') buf.append('\n')
if do_output_response: if do_output_response:
resp = HTTPMessage.from_response(response).format( resp_msg = HTTPMessage.from_response(response)
resp = resp_msg.format(
prettifier=prettifier, prettifier=prettifier,
with_headers=cliparse.OUT_RESP_HEAD in args.output_options, with_headers=OUT_RESP_HEAD in args.output_options,
with_body=cliparse.OUT_RESP_BODY in args.output_options with_body=OUT_RESP_BODY in args.output_options
) )
buf.append(resp) buf.append(resp)
buf.append('\n') buf.append('\n')
@ -118,10 +130,7 @@ def get_output(args, env, response):
def get_exist_status(code, allow_redirects=False): def get_exist_status(code, allow_redirects=False):
""" """Translate HTTP status code to exit status."""
Translate HTTP status code to exit status.
"""
if 300 <= code <= 399 and not allow_redirects: if 300 <= code <= 399 and not allow_redirects:
# Redirect # Redirect
return 3 return 3
@ -136,13 +145,12 @@ def get_exist_status(code, allow_redirects=False):
def main(args=sys.argv[1:], env=Environment()): def main(args=sys.argv[1:], env=Environment()):
""" """Run the main program and write the output to ``env.stdout``.
Run the main program and write the output to ``env.stdout``.
Return exit status. Return exit status.
""" """
args = cli.parser.parse_args(args=args, env=env) args = parser.parse_args(args=args, env=env)
response = get_response(args, env) response = get_response(args, env)
status = 0 status = 0
@ -155,7 +163,7 @@ def main(args=sys.argv[1:], env=Environment()):
response.raw.status, response.raw.reason) response.raw.status, response.raw.reason)
env.stderr.write(err.encode('utf8')) env.stderr.write(err.encode('utf8'))
output = get_output(args, env, response) output = get_output(args, env, response.request, response)
output_bytes = output.encode('utf8') output_bytes = output.encode('utf8')
f = getattr(env.stdout, 'buffer', env.stdout) f = getattr(env.stdout, 'buffer', env.stdout)
f.write(output_bytes) f.write(output_bytes)

View File

@ -1,5 +1,4 @@
""" """Parsing and processing of CLI input (args, auth credentials, files, stdin).
CLI argument parsing logic.
""" """
import os import os
@ -73,6 +72,12 @@ DEFAULT_UA = 'HTTPie/%s' % __version__
class Parser(argparse.ArgumentParser): class Parser(argparse.ArgumentParser):
"""Adds additional logic to `argparse.ArgumentParser`.
Handles all input (CLI args, file args, stdin), applies defaults,
and performs extra validation.
"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['add_help'] = False kwargs['add_help'] = False
@ -101,14 +106,18 @@ class Parser(argparse.ArgumentParser):
return args return args
def _body_from_file(self, args, f): def _body_from_file(self, args, f):
"""Use the content of `f` as the `request.data`.
There can only be one source of request data.
"""
if args.data: if args.data:
self.error('Request body (from stdin or a file) and request ' self.error('Request body (from stdin or a file) and request '
'data (key=value) cannot be mixed.') 'data (key=value) cannot be mixed.')
args.data = f.read() args.data = f.read()
def _guess_method(self, args, env): def _guess_method(self, args, env):
""" """Set `args.method` if not specified to either POST or GET
Set `args.method` if not specified to either POST or GET
based on whether the request has data or not. based on whether the request has data or not.
""" """
@ -143,9 +152,8 @@ class Parser(argparse.ArgumentParser):
args.method = HTTP_POST if has_data else HTTP_GET args.method = HTTP_POST if has_data else HTTP_GET
def _parse_items(self, args): def _parse_items(self, args):
""" """Parse `args.items` into `args.headers`, `args.data`,
Parse `args.items` into `args.headers`, `args.`, and `args.files`.
`args.data`, `args.`, and `args.files`.
""" """
args.headers = CaseInsensitiveDict() args.headers = CaseInsensitiveDict()
@ -191,6 +199,11 @@ class Parser(argparse.ArgumentParser):
args.headers['Content-Type'] = content_type args.headers['Content-Type'] = content_type
def _process_output_options(self, args, env): def _process_output_options(self, args, env):
"""Apply defaults to output options or validate the provided ones.
The default output options are stdout-type-sensitive.
"""
if not args.output_options: if not args.output_options:
args.output_options = (OUTPUT_OPTIONS_DEFAULT if env.stdout_isatty args.output_options = (OUTPUT_OPTIONS_DEFAULT if env.stdout_isatty
else OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED) else OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED)
@ -218,8 +231,7 @@ class KeyValue(object):
class KeyValueArgType(object): class KeyValueArgType(object):
""" """A key-value pair argument type used with `argparse`.
A key-value pair argument type used with `argparse`.
Parses a key-value arg and constructs a `KeyValue` instance. Parses a key-value arg and constructs a `KeyValue` instance.
Used for headers, form data, and other key-value pair types. Used for headers, form data, and other key-value pair types.
@ -232,8 +244,7 @@ class KeyValueArgType(object):
self.separators = separators self.separators = separators
def __call__(self, string): def __call__(self, string):
""" """Parse `string` and return `self.key_value_class()` instance.
Parse `string` and return `self.key_value_class()` instance.
The best of `self.separators` is determined (first found, longest). The best of `self.separators` is determined (first found, longest).
Back slash escaped characters aren't considered as separators Back slash escaped characters aren't considered as separators
@ -243,12 +254,14 @@ class KeyValueArgType(object):
""" """
class Escaped(str): class Escaped(str):
pass """Represents an escaped character."""
def tokenize(s): def tokenize(s):
""" """Tokenize `s`. There are only two token types - strings
r'foo\=bar\\baz' and escaped characters:
=> ['foo', Escaped('='), 'bar', Escaped('\'), 'baz']
>>> tokenize(r'foo\=bar\\baz')
['foo', Escaped('='), 'bar', Escaped('\\'), 'baz']
""" """
tokens = [''] tokens = ['']
@ -305,10 +318,8 @@ class KeyValueArgType(object):
class AuthCredentials(KeyValue): class AuthCredentials(KeyValue):
""" """Represents parsed credentials."""
Represents parsed credentials.
"""
def _getpass(self, prompt): def _getpass(self, prompt):
# To allow mocking. # To allow mocking.
return getpass.getpass(prompt) return getpass.getpass(prompt)
@ -325,10 +336,16 @@ class AuthCredentials(KeyValue):
class AuthCredentialsArgType(KeyValueArgType): class AuthCredentialsArgType(KeyValueArgType):
"""A key-value arg type that parses credentials."""
key_value_class = AuthCredentials key_value_class = AuthCredentials
def __call__(self, string): def __call__(self, string):
"""Parse credentials from `string`.
("username" or "username:password").
"""
try: try:
return super(AuthCredentialsArgType, self).__call__(string) return super(AuthCredentialsArgType, self).__call__(string)
except argparse.ArgumentTypeError: except argparse.ArgumentTypeError:
@ -342,11 +359,11 @@ class AuthCredentialsArgType(KeyValueArgType):
class ParamDict(OrderedDict): class ParamDict(OrderedDict):
"""Multi-value dict for URL parameters and form data."""
#noinspection PyMethodOverriding #noinspection PyMethodOverriding
def __setitem__(self, key, value): def __setitem__(self, key, value):
""" """ If `key` is assigned more than once, `self[key]` holds a
If `key` is assigned more than once, `self[key]` holds a
`list` of all the values. `list` of all the values.
This allows having multiple fields with the same name in form This allows having multiple fields with the same name in form
@ -365,12 +382,10 @@ class ParamDict(OrderedDict):
def parse_items(items, data=None, headers=None, files=None, params=None): def parse_items(items, data=None, headers=None, files=None, params=None):
""" """Parse `KeyValue` `items` into `data`, `headers`, `files`,
Parse `KeyValue` `items` into `data`, `headers`, `files`,
and `params`. and `params`.
""" """
if headers is None: if headers is None:
headers = CaseInsensitiveDict() headers = CaseInsensitiveDict()
if data is None: if data is None:

View File

@ -4,6 +4,12 @@ from requests.compat import urlparse, is_windows
class Environment(object): class Environment(object):
"""Holds information about the execution context.
Groups various aspects of the environment in a changeable object
and allows for mocking.
"""
stdin_isatty = sys.stdin.isatty() stdin_isatty = sys.stdin.isatty()
stdin = sys.stdin stdin = sys.stdin

View File

@ -1,5 +1,4 @@
""" """Output processing and formatting.
Colorizing of HTTP messages and content processing.
""" """
import re import re
@ -22,8 +21,7 @@ AVAILABLE_STYLES = [DEFAULT_STYLE] + list(STYLE_MAP.keys())
class HTTPLexer(lexer.RegexLexer): class HTTPLexer(lexer.RegexLexer):
""" """Simplified HTTP lexer for Pygments.
Simplified HTTP lexer for Pygments.
It only operates on headers and provides a stronger contrast between It only operates on headers and provides a stronger contrast between
their names and values than the original one bundled with Pygments their names and values than the original one bundled with Pygments

View File

@ -37,7 +37,7 @@ from requests.compat import is_py26, is_py3, str
TESTS_ROOT = os.path.dirname(__file__) TESTS_ROOT = os.path.dirname(__file__)
sys.path.insert(0, os.path.realpath(os.path.join(TESTS_ROOT, '..'))) sys.path.insert(0, os.path.realpath(os.path.join(TESTS_ROOT, '..')))
from httpie import cliparse from httpie import input
from httpie.models import Environment from httpie.models import Environment
from httpie.core import main, get_output from httpie.core import main, get_output
@ -506,7 +506,7 @@ class VerboseFlagTest(BaseTestCase):
class MultipartFormDataFileUploadTest(BaseTestCase): class MultipartFormDataFileUploadTest(BaseTestCase):
def test_non_existent_file_raises_parse_error(self): def test_non_existent_file_raises_parse_error(self):
self.assertRaises(cliparse.ParseError, http, self.assertRaises(input.ParseError, http,
'--form', '--form',
'--traceback', '--traceback',
'POST', 'POST',
@ -595,7 +595,7 @@ class AuthTest(BaseTestCase):
def test_password_prompt(self): def test_password_prompt(self):
cliparse.AuthCredentials._getpass = lambda self, prompt: 'password' input.AuthCredentials._getpass = lambda self, prompt: 'password'
r = http( r = http(
'--auth', '--auth',
@ -681,12 +681,12 @@ class ExitStatusTest(BaseTestCase):
class ItemParsingTest(BaseTestCase): class ItemParsingTest(BaseTestCase):
def setUp(self): def setUp(self):
self.key_value_type = cliparse.KeyValueArgType( self.key_value_type = input.KeyValueArgType(
cliparse.SEP_HEADERS, input.SEP_HEADERS,
cliparse.SEP_QUERY, input.SEP_QUERY,
cliparse.SEP_DATA, input.SEP_DATA,
cliparse.SEP_DATA_RAW_JSON, input.SEP_DATA_RAW_JSON,
cliparse.SEP_FILES, input.SEP_FILES,
) )
def test_invalid_items(self): def test_invalid_items(self):
@ -696,7 +696,7 @@ class ItemParsingTest(BaseTestCase):
lambda: self.key_value_type(item)) lambda: self.key_value_type(item))
def test_escape(self): def test_escape(self):
headers, data, files, params = cliparse.parse_items([ headers, data, files, params = input.parse_items([
# headers # headers
self.key_value_type('foo\\:bar:baz'), self.key_value_type('foo\\:bar:baz'),
self.key_value_type('jack\\@jill:hill'), self.key_value_type('jack\\@jill:hill'),
@ -715,7 +715,7 @@ class ItemParsingTest(BaseTestCase):
self.assertIn('bar@baz', files) self.assertIn('bar@baz', files)
def test_escape_longsep(self): def test_escape_longsep(self):
headers, data, files, params = cliparse.parse_items([ headers, data, files, params = input.parse_items([
self.key_value_type('bob\\:==foo'), self.key_value_type('bob\\:==foo'),
]) ])
self.assertDictEqual(params, { self.assertDictEqual(params, {
@ -723,7 +723,7 @@ class ItemParsingTest(BaseTestCase):
}) })
def test_valid_items(self): def test_valid_items(self):
headers, data, files, params = cliparse.parse_items([ headers, data, files, params = input.parse_items([
self.key_value_type('string=value'), self.key_value_type('string=value'),
self.key_value_type('header:value'), self.key_value_type('header:value'),
self.key_value_type('list:=["a", 1, {}, false]'), self.key_value_type('list:=["a", 1, {}, false]'),
@ -754,7 +754,7 @@ class ItemParsingTest(BaseTestCase):
class ArgumentParserTestCase(unittest.TestCase): class ArgumentParserTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self.parser = cliparse.Parser() self.parser = input.Parser()
def test_guess_when_method_set_and_valid(self): def test_guess_when_method_set_and_valid(self):
args = argparse.Namespace() args = argparse.Namespace()
@ -795,7 +795,7 @@ class ArgumentParserTestCase(unittest.TestCase):
self.assertEquals(args.url, 'http://example.com/') self.assertEquals(args.url, 'http://example.com/')
self.assertEquals( self.assertEquals(
args.items, args.items,
[cliparse.KeyValue( [input.KeyValue(
key='data', value='field', sep='=', orig='data=field')]) key='data', value='field', sep='=', orig='data=field')])
def test_guess_when_method_set_but_invalid_and_header_field(self): def test_guess_when_method_set_but_invalid_and_header_field(self):
@ -813,7 +813,7 @@ class ArgumentParserTestCase(unittest.TestCase):
self.assertEquals(args.url, 'http://example.com/') self.assertEquals(args.url, 'http://example.com/')
self.assertEquals( self.assertEquals(
args.items, args.items,
[cliparse.KeyValue( [input.KeyValue(
key='test', value='header', sep=':', orig='test:header')]) key='test', value='header', sep=':', orig='test:header')])
def test_guess_when_method_set_but_invalid_and_item_exists(self): def test_guess_when_method_set_but_invalid_and_item_exists(self):
@ -821,16 +821,16 @@ class ArgumentParserTestCase(unittest.TestCase):
args.method = 'http://example.com/' args.method = 'http://example.com/'
args.url = 'new_item=a' args.url = 'new_item=a'
args.items = [ args.items = [
cliparse.KeyValue( input.KeyValue(
key='old_item', value='b', sep='=', orig='old_item=b') key='old_item', value='b', sep='=', orig='old_item=b')
] ]
self.parser._guess_method(args, Environment()) self.parser._guess_method(args, Environment())
self.assertEquals(args.items, [ self.assertEquals(args.items, [
cliparse.KeyValue( input.KeyValue(
key='new_item', value='a', sep='=', orig='new_item=a'), key='new_item', value='a', sep='=', orig='new_item=a'),
cliparse.KeyValue(key input.KeyValue(key
='old_item', value='b', sep='=', orig='old_item=b'), ='old_item', value='b', sep='=', orig='old_item=b'),
]) ])
@ -879,7 +879,7 @@ class UnicodeOutputTestCase(BaseTestCase):
args.style = 'default' args.style = 'default'
# colorized output contains escape sequences # colorized output contains escape sequences
output = get_output(args, Environment(), response) output = get_output(args, Environment(), response.request, response)
for key, value in response_dict.items(): for key, value in response_dict.items():
self.assertIn(key, output) self.assertIn(key, output)