mirror of
https://github.com/httpie/cli.git
synced 2025-06-24 19:41:23 +02:00
0.3.0
This commit is contained in:
parent
a41dd7ac6d
commit
e25d64a610
44
README.rst
44
README.rst
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
''')
|
''')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
3
setup.py
3
setup.py
@ -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,
|
||||||
|
@ -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',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user