Added exit status constants, cleaned up main().

This commit is contained in:
Jakub Roztocil 2012-08-04 19:12:51 +02:00
parent 94c77c9bfc
commit 4e58a3849a
5 changed files with 149 additions and 115 deletions

View File

@ -5,3 +5,12 @@ HTTPie - cURL for humans.
__author__ = 'Jakub Roztocil'
__version__ = '0.2.7dev'
__licence__ = 'BSD'
class EXIT:
OK = 0
ERROR = 1
# Used only when requested:
ERROR_HTTP_3XX = 3
ERROR_HTTP_4XX = 4
ERROR_HTTP_5XX = 5

View File

@ -13,27 +13,22 @@ Invocation flow:
import sys
import json
import errno
from itertools import chain
from functools import partial
import requests
import requests.auth
from requests.compat import str
from .models import HTTPRequest, HTTPResponse, Environment
from .output import (OutputProcessor, RawStream, PrettyStream,
BufferedPrettyStream, EncodedStream)
from .input import (OUT_REQ_BODY, OUT_REQ_HEAD,
OUT_RESP_HEAD, OUT_RESP_BODY)
from .cli import parser
from .models import Environment
from .output import output_stream, write
from . import EXIT
FORM = 'application/x-www-form-urlencoded; charset=utf-8'
JSON = 'application/json; charset=utf-8'
def get_response(args, env):
def get_response(args):
"""Send the request and return a `request.Response`."""
auto_json = args.data and not args.form
@ -78,71 +73,19 @@ def get_response(args, env):
)
def output_stream(args, env, request, response):
"""Format parts of the `request`-`response` exchange
according to `args` and `env` and return `bytes`.
"""
# Pick the right stream type for this exchange based on `env` and `args`.
if not env.stdout_isatty and not args.prettify:
Stream = partial(
RawStream,
chunk_size=RawStream.CHUNK_SIZE_BY_LINE
if args.stream
else RawStream.CHUNK_SIZE)
elif args.prettify:
Stream = partial(
PrettyStream if args.stream else BufferedPrettyStream,
processor=OutputProcessor(env, pygments_style=args.style),
env=env)
else:
Stream = partial(EncodedStream, env=env)
req_h = OUT_REQ_HEAD in args.output_options
req_b = OUT_REQ_BODY in args.output_options
resp_h = OUT_RESP_HEAD in args.output_options
resp_b = OUT_RESP_BODY in args.output_options
req = req_h or req_b
resp = resp_h or resp_b
output = []
if req:
output.append(Stream(
msg=HTTPRequest(request),
with_headers=req_h,
with_body=req_b))
if req and resp:
output.append([b'\n\n\n'])
if resp:
output.append(Stream(
msg=HTTPResponse(response),
with_headers=resp_h,
with_body=resp_b))
if env.stdout_isatty:
output.append([b'\n\n'])
return chain(*output)
def get_exist_status(code, allow_redirects=False):
"""Translate HTTP status code to exit status."""
if 300 <= code <= 399 and not allow_redirects:
# Redirect
return 3
return EXIT.ERROR_HTTP_3XX
elif 400 <= code <= 499:
# Client Error
return 4
return EXIT.ERROR_HTTP_4XX
elif 500 <= code <= 599:
# Server Error
return 5
return EXIT.ERROR_HTTP_5XX
else:
return 0
return EXIT.OK
def main(args=sys.argv[1:], env=Environment()):
@ -151,57 +94,50 @@ def main(args=sys.argv[1:], env=Environment()):
Return exit status.
"""
debug = '--debug' in args
if env.is_windows and not env.stdout_isatty:
env.stderr.write(
'http: error: Output redirection is not supported on Windows.'
' Please use `--output FILE\' instead.\n')
return 1
def error(msg, *args):
msg = msg % args
env.stderr.write('http: error: %s\n' % msg)
debug = '--debug' in args
status = EXIT.OK
try:
args = parser.parse_args(args=args, env=env)
response = get_response(args, env)
status = 0
response = get_response(args)
if args.check_status:
status = get_exist_status(response.status_code,
args.allow_redirects)
if status and not env.stdout_isatty:
err = 'http error: %s %s\n' % (
response.raw.status, response.raw.reason)
env.stderr.write(err)
error('%s %s', response.raw.status, response.raw.reason)
stream = output_stream(args, env, response.request, response)
try:
# We are writing bytes so we use buffer on Python 3
buffer = env.stdout.buffer
except AttributeError:
buffer = env.stdout
try:
for chunk in output_stream(args, env, response.request, response):
buffer.write(chunk)
if env.stdout_isatty or args.stream:
env.stdout.flush()
write(stream=stream,
outfile=env.stdout,
flush=env.stdout_isatty or args.stream)
except IOError as e:
if debug:
raise
if e.errno == errno.EPIPE:
if not debug and e.errno == errno.EPIPE:
# Ignore broken pipes unless --debug.
env.stderr.write('\n')
else:
env.stderr.write(str(e) + '\n')
return 1
raise
except (KeyboardInterrupt, SystemExit):
if debug:
raise
env.stderr.write('\n')
return 1
status = EXIT.ERROR
except Exception as e:
# TODO: distinguish between expected and unexpected errors.
# network errors vs. bugs, etc.
if debug:
raise
env.stderr.write(str(e) + '\n')
return 1
error('%s: %s', type(e).__name__, str(e))
status = EXIT.ERROR
return status

View File

@ -95,6 +95,10 @@ class Parser(argparse.ArgumentParser):
self.env = env
if env.is_windows and not env.stdout_isatty:
self.error('Output redirection is not supported on Windows.'
' Please use `--output FILE\' instead.')
args = super(Parser, self).parse_args(args, namespace)
if args.output:
@ -131,9 +135,6 @@ class Parser(argparse.ArgumentParser):
None: self.env.stderr
}.get(file, file)
#if isinstance(message, str):
# message = message.encode('utf8')
super(Parser, self)._print_message(message, file)
def _body_from_file(self, args, fd):

View File

@ -2,6 +2,8 @@
"""
import json
from functools import partial
from itertools import chain
import pygments
from pygments import token, lexer
@ -13,7 +15,9 @@ from pygments.util import ClassNotFound
from requests.compat import is_windows
from .solarized import Solarized256Style
from .models import Environment
from .models import HTTPRequest, HTTPResponse, Environment
from .input import (OUT_REQ_BODY, OUT_REQ_HEAD,
OUT_RESP_HEAD, OUT_RESP_BODY)
# Colors on Windows via colorama aren't that great and fruity
@ -43,6 +47,82 @@ class BinarySuppressedError(Exception):
# Output Streams
###############################################################################
def write(stream, outfile, flush):
"""Write the output stream."""
try:
# Writing bytes so we use the buffer interface (Python 3).
buf = outfile.buffer
except AttributeError:
buf = outfile
for chunk in stream:
buf.write(chunk)
if flush:
outfile.flush()
def output_stream(args, env, request, response):
"""Build and return a chain of iterators over the `request`-`response`
exchange each of which yields `bytes` chunks.
"""
Stream = make_stream(env, args)
req_h = OUT_REQ_HEAD in args.output_options
req_b = OUT_REQ_BODY in args.output_options
resp_h = OUT_RESP_HEAD in args.output_options
resp_b = OUT_RESP_BODY in args.output_options
req = req_h or req_b
resp = resp_h or resp_b
output = []
if req:
output.append(Stream(
msg=HTTPRequest(request),
with_headers=req_h,
with_body=req_b))
if req and resp:
output.append([b'\n\n\n'])
if resp:
output.append(Stream(
msg=HTTPResponse(response),
with_headers=resp_h,
with_body=resp_b))
if env.stdout_isatty:
output.append([b'\n\n'])
return chain(*output)
def make_stream(env, args):
"""Pick the right stream type for based on `env` and `args`.
and wrap it with a partial with the type-specific args.
"""
if not env.stdout_isatty and not args.prettify:
Stream = partial(
RawStream,
chunk_size=RawStream.CHUNK_SIZE_BY_LINE
if args.stream
else RawStream.CHUNK_SIZE)
elif args.prettify:
Stream = partial(
PrettyStream if args.stream else BufferedPrettyStream,
processor=OutputProcessor(env, pygments_style=args.style),
env=env)
else:
Stream = partial(EncodedStream, env=env)
return Stream
class BaseStream(object):
"""Base HTTP message stream class."""

View File

@ -25,21 +25,26 @@ import json
import argparse
import tempfile
import unittest
try:
from unittest import skipIf
except ImportError:
def skipIf(cond, test_method):
if cond:
return test_method
return lambda self: None
try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen
try:
from unittest import skipIf
except ImportError:
def skipIf(cond, reason):
def decorator(test_method):
if cond:
return lambda self: None
return test_method
return decorator
from requests.compat import is_windows, is_py26, bytes, str
#################################################################
# Utils/setup
#################################################################
@ -48,6 +53,7 @@ from requests.compat import is_windows, is_py26, bytes, str
TESTS_ROOT = os.path.abspath(os.path.dirname(__file__))
sys.path.insert(0, os.path.realpath(os.path.join(TESTS_ROOT, '..')))
from httpie import EXIT
from httpie import input
from httpie.models import Environment
from httpie.core import main
@ -108,8 +114,10 @@ class TestEnvironment(Environment):
super(TestEnvironment, self).__init__(**kwargs)
class BytesResponse(bytes): pass
class StrResponse(str): pass
class BytesResponse(bytes):
stderr = json = exit_status = None
class StrResponse(str):
stderr = json = exit_status = None
def http(*args, **kwargs):
@ -139,7 +147,7 @@ def http(*args, **kwargs):
sys.stderr.write(env.stderr.read())
raise
except SystemExit:
exit_status = 1
exit_status = EXIT.ERROR
env.stdout.seek(0)
env.stderr.seek(0)
@ -792,7 +800,7 @@ class ExitStatusTest(BaseTestCase):
httpbin('/status/200')
)
self.assertIn(OK, r)
self.assertEqual(r.exit_status, 0)
self.assertEqual(r.exit_status, EXIT.OK)
def test_error_response_exits_0_without_check_status(self):
r = http(
@ -800,7 +808,7 @@ class ExitStatusTest(BaseTestCase):
httpbin('/status/500')
)
self.assertIn('HTTP/1.1 500', r)
self.assertEqual(r.exit_status, 0)
self.assertEqual(r.exit_status, EXIT.OK)
def test_3xx_check_status_exits_3_and_stderr_when_stdout_redirected(self):
r = http(
@ -811,7 +819,7 @@ class ExitStatusTest(BaseTestCase):
env=TestEnvironment(stdout_isatty=False,)
)
self.assertIn('HTTP/1.1 301', r)
self.assertEqual(r.exit_status, 3)
self.assertEqual(r.exit_status, EXIT.ERROR_HTTP_3XX)
self.assertIn('301 moved permanently', r.stderr.lower())
def test_3xx_check_status_redirects_allowed_exits_0(self):
@ -823,7 +831,7 @@ class ExitStatusTest(BaseTestCase):
)
# The redirect will be followed so 200 is expected.
self.assertIn('HTTP/1.1 200 OK', r)
self.assertEqual(r.exit_status, 0)
self.assertEqual(r.exit_status, EXIT.OK)
def test_4xx_check_status_exits_4(self):
r = http(
@ -832,7 +840,7 @@ class ExitStatusTest(BaseTestCase):
httpbin('/status/401')
)
self.assertIn('HTTP/1.1 401', r)
self.assertEqual(r.exit_status, 4)
self.assertEqual(r.exit_status, EXIT.ERROR_HTTP_4XX)
# Also stderr should be empty since stdout isn't redirected.
self.assertTrue(not r.stderr)
@ -843,7 +851,7 @@ class ExitStatusTest(BaseTestCase):
httpbin('/status/500')
)
self.assertIn('HTTP/1.1 500', r)
self.assertEqual(r.exit_status, 5)
self.assertEqual(r.exit_status, EXIT.ERROR_HTTP_5XX)
class FakeWindowsTest(BaseTestCase):
@ -855,7 +863,7 @@ class FakeWindowsTest(BaseTestCase):
httpbin('/get'),
env=env
)
self.assertNotEqual(r.exit_status, 0)
self.assertNotEqual(r.exit_status, EXIT.OK)
self.assertIn('Windows', r.stderr)
self.assertIn('--output', r.stderr)