This commit is contained in:
Jakub Roztocil 2012-09-21 05:43:34 +02:00
parent a41dd7ac6d
commit e25d64a610
7 changed files with 64 additions and 53 deletions

View File

@ -1,8 +1,8 @@
*********************** ****************************************
HTTPie: cURL for Humans HTTPie: a CLI, cURL-like tool for humans
*********************** ****************************************
v0.2.8-alpha (`stable version`_) v0.3.0
HTTPie is a **command line HTTP client** whose goal is to make CLI interaction HTTPie is a **command line HTTP client** whose goal is to make CLI interaction
with web services as **human-friendly** as possible. It provides a with web services as **human-friendly** as possible. It provides a
@ -42,6 +42,7 @@ Main Features
* HTTPS, proxies, and authentication * HTTPS, proxies, and authentication
* Arbitrary request data * Arbitrary request data
* Custom headers * Custom headers
* Persistent sessions
* Python 2.6, 2.7 and 3.x support * Python 2.6, 2.7 and 3.x support
* Linux, Mac OS X and Windows support * Linux, Mac OS X and Windows support
* Documentation * Documentation
@ -825,34 +826,35 @@ Streamed output by small chunks alá ``tail -f``:
Sessions Sessions
======== ========
HTTPie supports named, per-host sessions, where custom headers, authorization, By default, every request is completely independent of the previous ones.
and cookies (manually specified or sent by the server) persist between requests:
HTTPie supports persistent sessions, where custom headers, authorization,
and cookies (manually specified or sent by the server) persist between
requests. Sessions are named and host-bound.
Create a new session named ``user1``:
.. code-block:: bash .. code-block:: bash
$ http --session user1 -a user1:password example.org X-Foo:Bar $ http --session=user1 -a user1:password example.org X-Foo:Bar
Now you can refer to the session by its name: Now you can refer to the session by its name:
.. code-block:: bash .. code-block:: bash
$ http --session user1 example.org $ http --session=user1 example.org
To create or reuse a different session, simple specify a different name:
To switch to another session simple pass a different name:
.. code-block:: bash .. code-block:: bash
$ http --session user2 -a user2:password example.org X-Bar:Foo $ http --session=user2 -a user2:password example.org X-Bar:Foo
To use a session without updating it from the request/response exchange To use a session without updating it from the request/response exchange
once it is created, specify the session name via once it is created, specify the session name via
``--session-read-only=SESSION_NAME`` instead. ``--session-read-only=SESSION_NAME`` instead.
You can view and manipulate existing sessions via the ``httpie`` management Sessions are stored as JSON files in ``~/.httpie/sessions/<host>/<name>.json``
command, see ``httpie --help``.
Sessions are stored as JSON in ``~/.httpie/sessions/<host>/<name>.json``
(``%APPDATA%\httpie\sessions\<host>\<name>.json`` on Windows). (``%APPDATA%\httpie\sessions\<host>\<name>.json`` on Windows).
See also `config`_. See also `config`_.
@ -862,8 +864,8 @@ See also `config`_.
Config Config
====== ======
HTTPie provides a simple configuration file containing a JSON HTTPie uses a simple configuration file that contains a JSON object with the
object with the following keys: following keys:
========================= ================================================= ========================= =================================================
``__version__`` HTTPie automatically sets this to its version. ``__version__`` HTTPie automatically sets this to its version.
@ -1025,9 +1027,9 @@ Changelog
*You can click a version name to see a diff with the previous one.* *You can click a version name to see a diff with the previous one.*
* `0.2.8-alpha`_ * `0.3.0`_ (2012-09-21)
* Allow output redirection on Windows. * Allow output redirection on Windows.
* Added config file. * Added configuration file.
* Added persistent session support. * Added persistent session support.
* Renamed ``--allow-redirects`` to ``--follow``. * Renamed ``--allow-redirects`` to ``--follow``.
* Improved the usability of ``http --help``. * Improved the usability of ``http --help``.
@ -1117,7 +1119,7 @@ Changelog
.. _0.2.5: https://github.com/jkbr/httpie/compare/0.2.2...0.2.5 .. _0.2.5: https://github.com/jkbr/httpie/compare/0.2.2...0.2.5
.. _0.2.6: https://github.com/jkbr/httpie/compare/0.2.5...0.2.6 .. _0.2.6: https://github.com/jkbr/httpie/compare/0.2.5...0.2.6
.. _0.2.7: https://github.com/jkbr/httpie/compare/0.2.5...0.2.7 .. _0.2.7: https://github.com/jkbr/httpie/compare/0.2.5...0.2.7
.. _0.2.8-alpha: https://github.com/jkbr/httpie/compare/0.2.7...master .. _0.3.0: https://github.com/jkbr/httpie/compare/0.2.7...0.3.0
.. _stable version: https://github.com/jkbr/httpie/tree/0.2.7#readme .. _stable version: https://github.com/jkbr/httpie/tree/0.3.0#readme
.. _AUTHORS.rst: https://github.com/jkbr/httpie/blob/master/AUTHORS.rst .. _AUTHORS.rst: https://github.com/jkbr/httpie/blob/master/AUTHORS.rst
.. _LICENSE: https://github.com/jkbr/httpie/blob/master/LICENSE .. _LICENSE: https://github.com/jkbr/httpie/blob/master/LICENSE

View File

@ -1,13 +1,13 @@
""" """
HTTPie - cURL for humans. HTTPie - a CLI, cURL-like tool for humans.
""" """
__author__ = 'Jakub Roztocil' __author__ = 'Jakub Roztocil'
__version__ = '0.2.8-alpha' __version__ = '0.3.0'
__licence__ = 'BSD' __licence__ = 'BSD'
class EXIT: class exit:
OK = 0 OK = 0
ERROR = 1 ERROR = 1
ERROR_TIMEOUT = 2 ERROR_TIMEOUT = 2

View File

@ -1,6 +1,8 @@
"""CLI arguments definition. """CLI arguments definition.
NOTE: the CLI interface may change before reaching v1.0. NOTE: the CLI interface may change before reaching v1.0.
TODO: make the options config friendly, i.e., no mutually exclusive groups to
allow options overwriting.
""" """
from argparse import FileType, OPTIONAL, ZERO_OR_MORE, SUPPRESS from argparse import FileType, OPTIONAL, ZERO_OR_MORE, SUPPRESS
@ -9,7 +11,7 @@ from requests.compat import is_windows
from . import __doc__ from . import __doc__
from . import __version__ from . import __version__
from .config import DEFAULT_CONFIG_DIR from .sessions import DEFAULT_SESSIONS_DIR
from .output import AVAILABLE_STYLES, DEFAULT_STYLE from .output import AVAILABLE_STYLES, DEFAULT_STYLE
from .input import (Parser, AuthCredentialsArgType, KeyValueArgType, from .input import (Parser, AuthCredentialsArgType, KeyValueArgType,
SEP_PROXY, SEP_CREDENTIALS, SEP_GROUP_ITEMS, SEP_PROXY, SEP_CREDENTIALS, SEP_GROUP_ITEMS,
@ -231,14 +233,14 @@ sessions.add_argument(
Create, or reuse and update a session. Create, or reuse and update a session.
Withing a session, custom headers, auth credential, as well as any Withing a session, custom headers, auth credential, as well as any
cookies sent by the server persist between requests. cookies sent by the server persist between requests.
You can use the `httpie' management command to manipulate Session files are stored in %s/<HOST>/<SESSION_NAME>.json.
and inspect existing sessions. See `httpie --help'. ''' % DEFAULT_SESSIONS_DIR)
''')
) )
sessions.add_argument( sessions.add_argument(
'--session-read-only', metavar='SESSION_NAME', '--session-read-only', metavar='SESSION_NAME',
help=_(''' help=_('''
Create or reuse a session, but do not update it once saved. Create or read a session without updating it form the
request/response exchange.
''') ''')
) )

View File

@ -23,22 +23,22 @@ from .cli import parser
from .client import get_response from .client import get_response
from .models import Environment from .models import Environment
from .output import output_stream, write, write_with_colors_win_p3k from .output import output_stream, write, write_with_colors_win_p3k
from . import EXIT from . import exit
def get_exist_status(code, follow=False): def get_exist_status(code, follow=False):
"""Translate HTTP status code to exit status.""" """Translate HTTP status code to exit status."""
if 300 <= code <= 399 and not follow: if 300 <= code <= 399 and not follow:
# Redirect # Redirect
return EXIT.ERROR_HTTP_3XX return exit.ERROR_HTTP_3XX
elif 400 <= code <= 499: elif 400 <= code <= 499:
# Client Error # Client Error
return EXIT.ERROR_HTTP_4XX return exit.ERROR_HTTP_4XX
elif 500 <= code <= 599: elif 500 <= code <= 599:
# Server Error # Server Error
return EXIT.ERROR_HTTP_5XX return exit.ERROR_HTTP_5XX
else: else:
return EXIT.OK return exit.OK
def print_debug_info(env): def print_debug_info(env):
@ -66,12 +66,12 @@ def main(args=sys.argv[1:], env=Environment()):
debug = '--debug' in args debug = '--debug' in args
traceback = debug or '--traceback' in args traceback = debug or '--traceback' in args
status = EXIT.OK status = exit.OK
if debug: if debug:
print_debug_info(env) print_debug_info(env)
if args == ['--debug']: if args == ['--debug']:
sys.exit(EXIT.OK) sys.exit(exit.OK)
try: try:
args = parser.parse_args(args=args, env=env) args = parser.parse_args(args=args, env=env)
@ -108,9 +108,9 @@ def main(args=sys.argv[1:], env=Environment()):
if traceback: if traceback:
raise raise
env.stderr.write('\n') env.stderr.write('\n')
status = EXIT.ERROR status = exit.ERROR
except requests.Timeout: except requests.Timeout:
status = EXIT.ERROR_TIMEOUT status = exit.ERROR_TIMEOUT
error('Request timed out (%ss).', args.timeout) error('Request timed out (%ss).', args.timeout)
except Exception as e: except Exception as e:
# TODO: distinguish between expected and unexpected errors. # TODO: distinguish between expected and unexpected errors.
@ -118,6 +118,6 @@ def main(args=sys.argv[1:], env=Environment()):
if traceback: if traceback:
raise raise
error('%s: %s', type(e).__name__, str(e)) error('%s: %s', type(e).__name__, str(e))
status = EXIT.ERROR status = exit.ERROR
return status return status

View File

@ -20,6 +20,7 @@ from .output import PygmentsProcessor
SESSIONS_DIR_NAME = 'sessions' SESSIONS_DIR_NAME = 'sessions'
DEFAULT_SESSIONS_DIR = os.path.join(DEFAULT_CONFIG_DIR, SESSIONS_DIR_NAME)
def get_response(name, request_kwargs, config_dir, read_only=False): def get_response(name, request_kwargs, config_dir, read_only=False):
@ -92,8 +93,8 @@ class Host(object):
@classmethod @classmethod
def all(cls): def all(cls):
"""Return a generator yielding a host at a time.""" """Return a generator yielding a host at a time."""
for name in sorted(glob.glob1(SESSIONS_DIR, '*')): for name in sorted(glob.glob1(DEFAULT_SESSIONS_DIR, '*')):
if os.path.isdir(os.path.join(SESSIONS_DIR, name)): if os.path.isdir(os.path.join(DEFAULT_SESSIONS_DIR, name)):
yield Host(name) yield Host(name)
@ -156,6 +157,9 @@ class Session(BaseConfigDict):
} }
# The commands are disabled for now.
# TODO: write tests for the commands.
def list_command(args): def list_command(args):
if args.host: if args.host:
for name, path in Host(args.host): for name, path in Host(args.host):

View File

@ -47,7 +47,8 @@ setup(
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'http = httpie.__main__:main', 'http = httpie.__main__:main',
'httpie = httpie.manage:main', # Not ready yet.
# 'httpie = httpie.manage:main',
], ],
}, },
install_requires=requirements, install_requires=requirements,

View File

@ -19,6 +19,7 @@ To make it run faster and offline you can::
HTTPBIN_URL=http://localhost:5000 tox HTTPBIN_URL=http://localhost:5000 tox
""" """
from functools import partial
import subprocess import subprocess
import os import os
import sys import sys
@ -28,6 +29,7 @@ import tempfile
import unittest import unittest
import shutil import shutil
from requests.compat import urlparse
try: try:
from urllib.request import urlopen from urllib.request import urlopen
except ImportError: except ImportError:
@ -55,7 +57,7 @@ from requests.compat import is_windows, is_py26, bytes, str
TESTS_ROOT = os.path.abspath(os.path.dirname(__file__)) TESTS_ROOT = os.path.abspath(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 EXIT from httpie import exit
from httpie import input from httpie import input
from httpie.models import Environment from httpie.models import Environment
from httpie.core import main from httpie.core import main
@ -183,7 +185,7 @@ def http(*args, **kwargs):
sys.stderr.write(env.stderr.read()) sys.stderr.write(env.stderr.read())
raise raise
except SystemExit: except SystemExit:
exit_status = EXIT.ERROR exit_status = exit.ERROR
env.stdout.seek(0) env.stdout.seek(0)
env.stderr.seek(0) env.stderr.seek(0)
@ -865,7 +867,7 @@ class ExitStatusTest(BaseTestCase):
httpbin('/status/200') httpbin('/status/200')
) )
self.assertIn(OK, r) self.assertIn(OK, r)
self.assertEqual(r.exit_status, EXIT.OK) self.assertEqual(r.exit_status, exit.OK)
def test_error_response_exits_0_without_check_status(self): def test_error_response_exits_0_without_check_status(self):
r = http( r = http(
@ -873,7 +875,7 @@ class ExitStatusTest(BaseTestCase):
httpbin('/status/500') httpbin('/status/500')
) )
self.assertIn('HTTP/1.1 500', r) self.assertIn('HTTP/1.1 500', r)
self.assertEqual(r.exit_status, EXIT.OK) self.assertEqual(r.exit_status, exit.OK)
self.assertTrue(not r.stderr) self.assertTrue(not r.stderr)
def test_timeout_exit_status(self): def test_timeout_exit_status(self):
@ -882,7 +884,7 @@ class ExitStatusTest(BaseTestCase):
'GET', 'GET',
httpbin('/delay/1') httpbin('/delay/1')
) )
self.assertEqual(r.exit_status, EXIT.ERROR_TIMEOUT) self.assertEqual(r.exit_status, exit.ERROR_TIMEOUT)
def test_3xx_check_status_exits_3_and_stderr_when_stdout_redirected(self): def test_3xx_check_status_exits_3_and_stderr_when_stdout_redirected(self):
r = http( r = http(
@ -893,7 +895,7 @@ class ExitStatusTest(BaseTestCase):
env=TestEnvironment(stdout_isatty=False,) env=TestEnvironment(stdout_isatty=False,)
) )
self.assertIn('HTTP/1.1 301', r) self.assertIn('HTTP/1.1 301', r)
self.assertEqual(r.exit_status, EXIT.ERROR_HTTP_3XX) self.assertEqual(r.exit_status, exit.ERROR_HTTP_3XX)
self.assertIn('301 moved permanently', r.stderr.lower()) self.assertIn('301 moved permanently', r.stderr.lower())
@skipIf(requests_version == '0.13.6', @skipIf(requests_version == '0.13.6',
@ -907,7 +909,7 @@ class ExitStatusTest(BaseTestCase):
) )
# The redirect will be followed so 200 is expected. # The redirect will be followed so 200 is expected.
self.assertIn('HTTP/1.1 200 OK', r) self.assertIn('HTTP/1.1 200 OK', r)
self.assertEqual(r.exit_status, EXIT.OK) self.assertEqual(r.exit_status, exit.OK)
def test_4xx_check_status_exits_4(self): def test_4xx_check_status_exits_4(self):
r = http( r = http(
@ -916,7 +918,7 @@ class ExitStatusTest(BaseTestCase):
httpbin('/status/401') httpbin('/status/401')
) )
self.assertIn('HTTP/1.1 401', r) self.assertIn('HTTP/1.1 401', r)
self.assertEqual(r.exit_status, EXIT.ERROR_HTTP_4XX) self.assertEqual(r.exit_status, exit.ERROR_HTTP_4XX)
# Also stderr should be empty since stdout isn't redirected. # Also stderr should be empty since stdout isn't redirected.
self.assertTrue(not r.stderr) self.assertTrue(not r.stderr)
@ -927,7 +929,7 @@ class ExitStatusTest(BaseTestCase):
httpbin('/status/500') httpbin('/status/500')
) )
self.assertIn('HTTP/1.1 500', r) self.assertIn('HTTP/1.1 500', r)
self.assertEqual(r.exit_status, EXIT.ERROR_HTTP_5XX) self.assertEqual(r.exit_status, exit.ERROR_HTTP_5XX)
class WindowsOnlyTests(BaseTestCase): class WindowsOnlyTests(BaseTestCase):
@ -1267,7 +1269,7 @@ class SessionTest(BaseTestCase):
shutil.rmtree(self.config_dir) shutil.rmtree(self.config_dir)
def test_session_create(self): def test_session_create(self):
# Verify that the has been created # Verify that the session has been created.
r = http( r = http(
'--session=test', '--session=test',
'GET', 'GET',
@ -1316,7 +1318,7 @@ class SessionTest(BaseTestCase):
self.assertNotEqual(r1.json['headers']['Authorization'], self.assertNotEqual(r1.json['headers']['Authorization'],
r3.json['headers']['Authorization']) r3.json['headers']['Authorization'])
def test_session_only(self): def test_session_read_only(self):
# Get a response from the original session. # Get a response from the original session.
r1 = http( r1 = http(
'--session=test', '--session=test',