1
0
mirror of https://github.com/httpie/cli.git synced 2024-11-24 08:22:22 +02:00

Clean up.

This commit is contained in:
Jakub Roztocil 2012-07-17 07:01:30 +02:00
parent 40948dbd2e
commit c2d70e2bb1
5 changed files with 152 additions and 53 deletions

View File

@ -1,19 +1,28 @@
HTTPie: cURL for humans HTTPie: cURL for humans
======================= =======================
**HTTPie is a CLI HTTP utility** built out of frustration with existing tools. The goal is to make CLI interaction with HTTP-based services as human-friendly as possible. **HTTPie is a CLI HTTP utility** built out of frustration with existing tools.
The goal is to make CLI interaction with HTTP-based services as
human-friendly as possible.
HTTPie does so by providing an ``http`` command that allows for issuing arbitrary HTTP requests using a **simple and natural syntax** and displaying **colorized responses**: HTTPie does so by providing an ``http`` command that allows for issuing
arbitrary HTTP requests using a **simple and natural syntax** and displaying
**colorized responses**:
.. image:: https://github.com/jkbr/httpie/raw/master/httpie.png .. image:: https://github.com/jkbr/httpie/raw/master/httpie.png
:alt: HTTPie compared to cURL :alt: HTTPie compared to cURL
Under the hood, HTTPie uses the excellent `Requests <http://python-requests.org>`_ and `Pygments <http://pygments.org/>`_ Python libraries. Python 2.6+ is supported (including 3.x). Under the hood, HTTPie uses the excellent
`Requests <http://python-requests.org>`_ and
`Pygments <http://pygments.org/>`_ Python libraries. Python 2.6+ is supported
(including 3.x).
Installation Installation
------------ ------------
The latest **stable version** of HTTPie can always be installed (or updated to) via `pip <http://www.pip-installer.org/en/latest/index.html>`_:: The latest **stable version** of HTTPie can always be installed
(or updated to) via
`pip <http://www.pip-installer.org/en/latest/index.html>`_::
pip install -U httpie pip install -U httpie
@ -29,7 +38,9 @@ Or, you can install the **development version** directly from GitHub:
pip install -U https://github.com/jkbr/httpie/tarball/master pip install -U https://github.com/jkbr/httpie/tarball/master
There are packages available for `Ubuntu <http://packages.ubuntu.com/quantal/httpie>`_ and `Debian <http://packages.debian.org/wheezy/httpie>`_. There are packages available for
`Ubuntu <http://packages.ubuntu.com/quantal/httpie>`_ and
`Debian <http://packages.debian.org/wheezy/httpie>`_.
Usage Usage
@ -46,16 +57,25 @@ Synopsis::
There are four types of key-value pair items available: There are four types of key-value pair items available:
Headers (``Name:Value``) Headers (``Name:Value``)
Arbitrary HTTP headers. The ``:`` character is used to separate a header's name from its value, e.g., ``X-API-Token:123``. Arbitrary HTTP headers. The ``:`` character is used to separate a header's
name from its value, e.g., ``X-API-Token:123``.
Simple data fields (``field=value``) Simple data fields (``field=value``)
Data items are included in the request body. Depending on the ``Content-Type``, they are automatically serialized as a JSON ``Object`` (default) or ``application/x-www-form-urlencoded`` (the ``-f`` flag). Data items use ``=`` as the separator, e.g., ``hello=world``. Data items are included in the request body. Depending on the
``Content-Type``, they are automatically serialized as a JSON ``Object``
(default) or ``application/x-www-form-urlencoded`` (the ``-f`` flag).
Data items use ``=`` as the separator, e.g., ``hello=world``.
Raw JSON fields (``field:=value``) Raw JSON fields (``field:=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 simple data items are always serialized as ``String``. E.g. ``pies:=[1,2,3]``. 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
simple data items are always serialized as ``String``.
E.g. ``pies:=[1,2,3]``.
File fields (``field@/path/to/file``) File fields (``field@/path/to/file``)
Only available with ``-f`` / ``--form``. Use ``@`` as the separator, e.g., ``screenshot@/path/to/file.png``. The presence of a file field results into a ``multipart/form-data`` request. Only available with ``-f`` / ``--form``. Use ``@`` as the separator, e.g.,
``screenshot@/path/to/file.png``. The presence of a file field results into
a ``multipart/form-data`` request.
Examples Examples
@ -75,7 +95,8 @@ The following request is issued::
{"name": "John", "email": "john@example.org", "age": 29} {"name": "John", "email": "john@example.org", "age": 29}
It can easily be changed to a **form** request using the ``-f`` (or ``--form``) flag, which produces:: It can easily be changed to a **form** request using the ``-f``
(or ``--form``) flag, which produces::
PATCH /person/1 HTTP/1.1 PATCH /person/1 HTTP/1.1
User-Agent: HTTPie/0.1 User-Agent: HTTPie/0.1
@ -84,7 +105,9 @@ It can easily be changed to a **form** request using the ``-f`` (or ``--form``)
age=29&name=John&email=john%40example.org age=29&name=John&email=john%40example.org
It is also possible to send ``multipart/form-data`` requests, i.e., to simulate a **file upload form** submission. It is done using the ``--form`` / ``-f`` flag and passing one or more file fields:: It is also possible to send ``multipart/form-data`` requests, i.e., to
simulate a **file upload form** submission. It is done using the
``--form`` / ``-f`` flag and passing one or more file fields::
http -f POST example.com/jobs name=John cv@~/Documents/cv.pdf http -f POST example.com/jobs name=John cv@~/Documents/cv.pdf
@ -95,28 +118,38 @@ The above will send the same request as if the following HTML form were submitte
<input type="file" name="cv" /> <input type="file" name="cv" />
</form> </form>
A whole request body can be passed in via **``stdin``** instead, in which case it will be used with no further processing:: A whole request body can be passed in via **``stdin``** instead, in which
case it will be used with no further processing::
echo '{"name": "John"}' | http PATCH example.com/person/1 X-API-Token:123 echo '{"name": "John"}' | http PATCH example.com/person/1 X-API-Token:123
# Or: # Or:
http POST example.com/person/1 X-API-Token:123 < person.json http POST example.com/person/1 X-API-Token:123 < person.json
That can be used for **piping services together**. The following example ``GET``-s JSON data from the Github API and ``POST``-s it to httpbin.org:: That can be used for **piping services together**. The following example
``GET``-s JSON data from the Github API and ``POST``-s it to httpbin.org::
http -b GET https://api.github.com/repos/jkbr/httpie | http POST httpbin.org/post http -b GET https://api.github.com/repos/jkbr/httpie | http POST httpbin.org/post
The above can be further simplified by omitting ``GET`` and ``POST`` because they are both default here. The first command has no request data, whereas the second one does via ``stdin``:: The above can be further simplified by omitting ``GET`` and ``POST`` because
they are both default here. The first command has no request data, whereas
the second one does via ``stdin``::
http -b https://api.github.com/repos/jkbr/httpie | http httpbin.org/post http -b https://api.github.com/repos/jkbr/httpie | http httpbin.org/post
An alternative to ``stdin`` is to pass a file name whose content will be used as the request body. It has the advantage that the ``Content-Type`` header will automatically be set to the appropriate value based on the filename extension (using the ``mimetypes`` module). Therefore, the following will request will send the verbatim contents of the file with ``Content-Type: application/xml``:: An alternative to ``stdin`` is to pass a file name whose content will be used
as the request body. It has the advantage that the ``Content-Type`` header
will automatically be set to the appropriate value based on the filename
extension (using the ``mimetypes`` module). Therefore, the following will
request will send the verbatim contents of the file with
``Content-Type: application/xml``::
http PUT httpbin.org/put @/data/file.xml http PUT httpbin.org/put @/data/file.xml
Flags Flags
^^^^^ ^^^^^
Most of the flags mirror the arguments understood by ``requests.request``. See ``http -h`` for more details:: Most of the flags mirror the arguments understood by ``requests.request``.
See ``http -h`` for more details::
$ http --help $ http --help
usage: http [-h] [--version] [--json | --form] [--traceback] usage: http [-h] [--version] [--json | --form] [--traceback]
@ -206,15 +239,24 @@ Contribute
`View contributors on GitHub <https://github.com/jkbr/httpie/contributors>`_. `View contributors on GitHub <https://github.com/jkbr/httpie/contributors>`_.
If you have found a bug or have a feature request, the `issue tracker <https://github.com/jkbr/httpie/issues?state=open>`_ is the place to start a discussion about it. If you have found a bug or have a feature request, the
`issue tracker <https://github.com/jkbr/httpie/issues?state=open>`_ is the
place to start a discussion about it.
To contribute code or documentation, please first browse the existing issues to see if the feature/bug has previously been discussed. Then fork `the repository <https://github.com/jkbr/httpie>`_, make changes in your develop branch and submit a pull request. Note: Pull requests with tests and documentation are 53.6% more awesome :) To contribute code or documentation, please first browse the existing issues
to see if the feature/bug has previously been discussed. Then fork
`the repository <https://github.com/jkbr/httpie>`_, make changes in your
develop branch and submit a pull request. Note: Pull requests with tests and
documentation are 53.6% more awesome :)
Before a pull requests is submitted, it's a good idea to run the existing suite of tests:: Before a pull requests is submitted, it's a good idea to run the existing
suite of tests::
python setup.py test python setup.py test
`Tox <http://tox.testrun.org/>`_ can used to conveniently run tests in all of the `supported Python environments <https://github.com/jkbr/httpie/blob/master/tox.ini>`_:: `Tox <http://tox.testrun.org/>`_ can used to conveniently run tests in all of
the
`supported Python environments <https://github.com/jkbr/httpie/blob/master/tox.ini>`_::
# Install tox # Install tox
pip install tox pip install tox
@ -228,23 +270,30 @@ Changelog
* `0.2.6dev <https://github.com/jkbr/httpie/compare/0.2.5...master>`_ * `0.2.6dev <https://github.com/jkbr/httpie/compare/0.2.5...master>`_
* Added support for terminal colors under Windows. * Added support for terminal colors under Windows.
* `0.2.5 <https://github.com/jkbr/httpie/compare/0.2.2...0.2.5>`_ (2012-07-17) * `0.2.5 <https://github.com/jkbr/httpie/compare/0.2.2...0.2.5>`_ (2012-07-17)
* Unicode characters in prettified JSON now don't get escaped to improve readability. * Unicode characters in prettified JSON now don't get escaped for
improved readability.
* --auth now prompts for a password if only a username provided. * --auth now prompts for a password if only a username provided.
* Added support for request payloads from a file path with automatic ``Content-Type`` (``http URL @/path``). * Added support for request payloads from a file path with automatic
* Fixed missing query string when displaing the request headers via ``--verbose``. ``Content-Type`` (``http URL @/path``).
* Fixed missing query string when displaing the request headers via
``--verbose``.
* Fixed Content-Type for requests with no data. * 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) * `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). * The ``METHOD`` positional argument can now be omitted (defaults to
``GET``, or to ``POST`` with data).
* Fixed --verbose --form. * Fixed --verbose --form.
* Added support for `Tox <http://tox.testrun.org/>`_. * Added support for `Tox <http://tox.testrun.org/>`_.
* `0.2.1 <https://github.com/jkbr/httpie/compare/0.2.0...0.2.1>`_ (2012-06-13) * `0.2.1 <https://github.com/jkbr/httpie/compare/0.2.0...0.2.1>`_ (2012-06-13)
* Added compatibility with ``requests-0.12.1``. * Added compatibility with ``requests-0.12.1``.
* Dropped custom JSON and HTTP lexers in favor of the ones newly included in ``pygments-1.5``. * Dropped custom JSON and HTTP lexers in favor of the ones newly included
in ``pygments-1.5``.
* `0.2.0 <https://github.com/jkbr/httpie/compare/0.1.6...0.2.0>`_ (2012-04-25) * `0.2.0 <https://github.com/jkbr/httpie/compare/0.1.6...0.2.0>`_ (2012-04-25)
* Added Python 3 support. * Added Python 3 support.
* Added the ability to print the HTTP request as well as the response (see ``--print`` and ``--verbose``). * Added the ability to print the HTTP request as well as the response
(see ``--print`` and ``--verbose``).
* Added support for Digest authentication. * Added support for Digest authentication.
* Added file upload support (``http -f POST file_field_name@/path/to/file``). * Added file upload support
(``http -f POST file_field_name@/path/to/file``).
* Improved syntax highlighting for JSON. * Improved syntax highlighting for JSON.
* Added support for field name escaping. * Added support for field name escaping.
* Many bug fixes. * Many bug fixes.

View File

@ -25,7 +25,8 @@ group_type = parser.add_mutually_exclusive_group(required=False)
group_type.add_argument( group_type.add_argument(
'--json', '-j', action='store_true', '--json', '-j', action='store_true',
help=_(''' help=_('''
(default) Data items from the command line are serialized as a JSON object. (default) Data items from the command
line are serialized as a JSON object.
The Content-Type and Accept headers The Content-Type and Accept headers
are set to application/json (if not specified). are set to application/json (if not specified).
''') ''')
@ -34,8 +35,10 @@ group_type.add_argument(
'--form', '-f', action='store_true', '--form', '-f', action='store_true',
help=_(''' help=_('''
Data items from the command line are serialized as form fields. 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 Content-Type is set to application/x-www-form-urlencoded
The presence of any file fields results into a multipart/form-data request. (if not specified).
The presence of any file fields results
into a multipart/form-data request.
''') ''')
) )
@ -126,13 +129,17 @@ parser.add_argument(
'--auth', '-a', type=cliparse.AuthCredentialsType(cliparse.SEP_COMMON), '--auth', '-a', type=cliparse.AuthCredentialsType(cliparse.SEP_COMMON),
help=_(''' help=_('''
username:password. username:password.
If only the username is provided (-a username), HTTPie will prompt for the password. If only the username is provided (-a username),
HTTPie will prompt for the password.
'''), '''),
) )
parser.add_argument( parser.add_argument(
'--auth-type', choices=['basic', 'digest'], '--auth-type', choices=['basic', 'digest'],
help=_('The authentication mechanism to be used. Defaults to "basic".') help=_('''
The authentication mechanism to be used.
Defaults to "basic".
''')
) )
parser.add_argument( parser.add_argument(
@ -179,8 +186,9 @@ parser.add_argument(
help=_(''' help=_('''
The HTTP method to be used for the request The HTTP method to be used for the request
(GET, POST, PUT, DELETE, PATCH, ...). (GET, POST, PUT, DELETE, PATCH, ...).
If this argument is omitted, then HTTPie will guess the HTTP method. If this argument is omitted, then HTTPie
If there is some data to be sent, then it will be POST, otherwise GET. will guess the HTTP method. If there is some
data to be sent, then it will be POST, otherwise GET.
''') ''')
) )
parser.add_argument( parser.add_argument(
@ -200,11 +208,12 @@ parser.add_argument(
cliparse.SEP_FILES cliparse.SEP_FILES
), ),
help=_(''' help=_('''
A key-value pair whose type is defined by the separator used. It can be an A key-value pair whose type is defined by the
HTTP header (header:value), separator used. It can be an HTTP header (header:value),
a data field to be used in the request body (field_name=value), a data field to be used in the request body (field_name=value),
a raw JSON data field (field_name:=value), a raw JSON data field (field_name:=value),
or a file field (field_name@/path/to/file). or a file field (field_name@/path/to/file).
You can use a backslash to escape a colliding separator in the field name. You can use a backslash to escape a colliding
separator in the field name.
''') ''')
) )

View File

@ -155,9 +155,12 @@ class Parser(argparse.ArgumentParser):
args.headers['Content-Type'] = content_type args.headers['Content-Type'] = content_type
def _validate_output_options(self, args): def _validate_output_options(self, args):
unknown_output_options = set(args.output_options) - set(OUTPUT_OPTIONS) unknown = set(args.output_options) - set(OUTPUT_OPTIONS)
if unknown_output_options: if unknown:
self.error('Unknown output options: %s' % ','.join(unknown_output_options)) self.error(
'Unknown output options: %s' %
','.join(unknown)
)
def _validate_auth_options(self, args): def _validate_auth_options(self, args):
if args.auth_type and not args.auth: if args.auth_type and not args.auth:

View File

@ -45,7 +45,8 @@ class PrettyHttp(object):
def body(self, content, content_type): def body(self, content, content_type):
content_type = content_type.split(';')[0] content_type = content_type.split(';')[0]
application_match = re.match(application_content_type_re, content_type) application_match = re.match(application_content_type_re,
content_type)
if application_match: if application_match:
# Strip vendor and extensions from Content-Type # Strip vendor and extensions from Content-Type
vendor, extension = application_match.groups() vendor, extension = application_match.groups()

View File

@ -156,10 +156,15 @@ class AutoContentTypeAndAcceptHeadersTest(BaseTestCase):
def test_POST_form_auto_Content_Type(self): def test_POST_form_auto_Content_Type(self):
r = http('-f', 'POST', 'http://httpbin.org/post') r = http('-f', 'POST', 'http://httpbin.org/post')
self.assertIn('HTTP/1.1 200', r) self.assertIn('HTTP/1.1 200', r)
self.assertIn('"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"', r) self.assertIn(
'"Content-Type":'
' "application/x-www-form-urlencoded; charset=utf-8"',
r
)
def test_POST_form_Content_Type_override(self): def test_POST_form_Content_Type_override(self):
r = http('-f', 'POST', 'http://httpbin.org/post', 'Content-Type:application/xml') r = http('-f', 'POST', 'http://httpbin.org/post',
'Content-Type:application/xml')
self.assertIn('HTTP/1.1 200', r) self.assertIn('HTTP/1.1 200', r)
self.assertIn('"Content-Type": "application/xml"', r) self.assertIn('"Content-Type": "application/xml"', r)
@ -203,24 +208,46 @@ class PrettyFlagTest(BaseTestCase):
self.assertNotIn(TERMINAL_COLOR_PRESENCE_CHECK, r) self.assertNotIn(TERMINAL_COLOR_PRESENCE_CHECK, r)
def test_force_pretty(self): def test_force_pretty(self):
r = http('--pretty', 'GET', 'http://httpbin.org/get', stdout_isatty=False) r = http(
'--pretty',
'GET',
'http://httpbin.org/get',
stdout_isatty=False
)
self.assertIn(TERMINAL_COLOR_PRESENCE_CHECK, r) self.assertIn(TERMINAL_COLOR_PRESENCE_CHECK, r)
def test_force_ugly(self): def test_force_ugly(self):
r = http('--ugly', 'GET', 'http://httpbin.org/get', stdout_isatty=True) r = http(
'--ugly',
'GET',
'http://httpbin.org/get',
stdout_isatty=True
)
self.assertNotIn(TERMINAL_COLOR_PRESENCE_CHECK, r) self.assertNotIn(TERMINAL_COLOR_PRESENCE_CHECK, r)
class VerboseFlagTest(BaseTestCase): class VerboseFlagTest(BaseTestCase):
def test_verbose(self): def test_verbose(self):
r = http('--verbose', 'GET', 'http://httpbin.org/get', 'test-header:__test__') r = http(
'--verbose',
'GET',
'http://httpbin.org/get',
'test-header:__test__'
)
self.assertIn('HTTP/1.1 200', r) self.assertIn('HTTP/1.1 200', r)
self.assertEqual(r.count('__test__'), 2) self.assertEqual(r.count('__test__'), 2)
def test_verbose_form(self): def test_verbose_form(self):
# https://github.com/jkbr/httpie/issues/53 # https://github.com/jkbr/httpie/issues/53
r = http('--verbose', '--form', 'POST', 'http://httpbin.org/post', 'foo=bar', 'baz=bar') r = http(
'--verbose',
'--form',
'POST',
'http://httpbin.org/post',
'foo=bar',
'baz=bar'
)
self.assertIn('HTTP/1.1 200', r) self.assertIn('HTTP/1.1 200', r)
self.assertIn('foo=bar&baz=bar', r) self.assertIn('foo=bar&baz=bar', r)
@ -253,7 +280,12 @@ class RequestBodyFromFilePathTest(BaseTestCase):
self.assertIn('"Content-Type": "text/plain"', r) self.assertIn('"Content-Type": "text/plain"', r)
def test_request_body_from_file_by_path_with_explicit_content_type(self): def test_request_body_from_file_by_path_with_explicit_content_type(self):
r = http('POST', 'http://httpbin.org/post', '@' + TEST_FILE_PATH, 'Content-Type:x-foo/bar') r = http(
'POST',
'http://httpbin.org/post',
'@' + TEST_FILE_PATH,
'Content-Type:x-foo/bar'
)
self.assertIn('HTTP/1.1 200', r) self.assertIn('HTTP/1.1 200', r)
self.assertIn(TEST_FILE_CONTENT, r) self.assertIn(TEST_FILE_CONTENT, r)
self.assertIn('"Content-Type": "x-foo/bar"', r) self.assertIn('"Content-Type": "x-foo/bar"', r)
@ -413,7 +445,8 @@ 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(key='data', value='field', sep='=', orig='data=field')]) [cliparse.KeyValue(
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):
args = argparse.Namespace() args = argparse.Namespace()
@ -427,21 +460,25 @@ 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(key='test', value='header', sep=':', orig='test:header')]) [cliparse.KeyValue(
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):
args = argparse.Namespace() args = argparse.Namespace()
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(key='old_item', value='b', sep='=', orig='old_item=b') cliparse.KeyValue(
key='old_item', value='b', sep='=', orig='old_item=b')
] ]
self.parser._guess_method(args) self.parser._guess_method(args)
self.assertEquals(args.items, [ self.assertEquals(args.items, [
cliparse.KeyValue(key='new_item', value='a', sep='=', orig='new_item=a'), cliparse.KeyValue(
cliparse.KeyValue(key='old_item', value='b', sep='=', orig='old_item=b'), key='new_item', value='a', sep='=', orig='new_item=a'),
cliparse.KeyValue(key
='old_item', value='b', sep='=', orig='old_item=b'),
]) ])