Compare commits

...

96 Commits
0.9.9 ... 1.0.0

Author SHA1 Message Date
2e96d7ffbb Update CHANGELOG.rst 2018-11-02 16:28:17 +01:00
b5625e3d75 v1.0.0 2018-11-02 16:24:35 +01:00
932d3224f4 Cleanup 2018-11-02 16:23:17 +01:00
b596fedf13 exit 0 constant: OK => SUCCESS to avoid confusion w/ HTTP 200 OK 2018-11-02 16:07:39 +01:00
96444f3345 Changelog 2018-11-02 15:13:53 +01:00
89b66f1608 Merge remote-tracking branch 'origin/master' 2018-11-02 14:58:08 +01:00
a7d570916d #722: Add support for tls1.3 (#724)
* #722: Add support for tls1.3

* #722: Document the potential support for tls1.3
2018-11-02 14:57:53 +01:00
ab5a50cee8 Finish --style=auto for terminal ANSI colors and make it the default.
Previously (only in the development version), this was called 'preset'.
2018-11-02 14:53:05 +01:00
91961c6b51 Fixed some lines (#723) 2018-10-31 19:17:44 +01:00
256ea7d49d Add prog parameter to HTTPieArgumentParser (#715) 2018-10-30 18:41:56 +01:00
2cd6ea3050 Fix some broken documentation links (#703) 2018-09-07 19:10:04 +02:00
37dddf5bf7 Fix for broken Travis builds on macOS with Python 3.7 (#704) (#705) 2018-09-07 19:09:30 +02:00
e508c631f2 Fix Tox using different Python than expected on macOS (#688) (#706) 2018-09-07 19:08:37 +02:00
55530c8c6d fixed output for escaping rules (#700) 2018-09-03 20:04:18 +02:00
eb929cbc04 Travis CI: Add Python 3.7 on linux to the testing (#690)
* Travis CI: Add Python 3.7 on linux to the testing

`sudo: true` and `dist: xenial` are currently required https://github.com/travis-ci/travis-ci/issues/9069

* NEWEST_PYTHON=3.7
2018-07-25 14:02:00 +02:00
2490bb25ca Add v 0.9.9 CHANGELOG link 2018-07-22 17:58:52 +02:00
2038fa02e3 Mention v0.9.9 in CHANGELOG
#620
2018-07-22 17:57:52 +02:00
59d51ad513 Travis 2018-07-12 21:52:01 +02:00
61568f1def Travis 2018-07-12 21:46:00 +02:00
f93f4fa7c7 Travis CI Python versions; install fix 2018-07-12 21:33:12 +02:00
bf73b5701e Fix travis.yml syntax 2018-07-12 21:23:32 +02:00
7917f1b40c Build fixes and clean-up
* reflect Python 3.7 release
* fix `pycodestyle` errors
* update `pycodestyle` config
* move `pytest` and `pycodestyle` config to `setup.cfg`
* add `make pycodestyle`
* add `make coveralls`
* etc.
2018-07-12 21:16:16 +02:00
a50660cc70 Test --timeout with longer delay
test_timeout_exit_status fails on Python 2.7
https://travis-ci.org/jakubroztocil/httpie/jobs/390072675#L325
2018-07-12 00:39:31 +02:00
749b1e2aca Fix pytest configuration 2018-06-09 11:59:34 +02:00
137889a267 Doc improvements 2018-05-30 14:07:52 +02:00
c9c6f0fae5 Formatting 2018-05-30 14:02:46 +02:00
6fd1ea0e5a Section ordering 2018-05-30 13:56:35 +02:00
8f7676a2a9 Add Cookies section to the docs 2018-05-30 13:55:06 +02:00
87e661c5f1 Support using styles from Pygments plugins (#663)
`pygments.styles.STYLE_MAP` contains only styles built directly into
Pygments library. To list all available styles (including styles
registered by plugins), one should use `get_all_styles` generator.

For respective Pygments documentation, see:
http://pygments.org/docs/styles/#getting-a-list-of-available-styles
2018-04-14 15:25:59 -05:00
8ca333dda0 Use parentheses in describing sessions (#664)
It's a little more readable than using em dashes.
2018-04-11 20:04:02 -05:00
0f4dce98c7 Make default HTTP headers case-insensitive
Cloase #644
2018-02-22 12:52:57 +01:00
05547224ce Remove a Python 2.6 mention from extras_require 2017-12-28 18:33:31 +01:00
6301fee3d2 Upgrade to latest requests 2017-12-28 18:32:29 +01:00
a803e845a5 More robust urllib3 import 2017-12-28 18:32:12 +01:00
11be041e06 Rename TestEnvironment to MockEnvironment to avoid pytest warnings
Close #621
2017-12-28 18:17:48 +01:00
7f5fd130c5 Start using dict comprehensions 2017-12-28 18:15:17 +01:00
ec899d70b7 Removed Python 2.6 support
* Travis CI doesn't support it anymore.
* It had EOL more than 4 years ago
2017-12-28 18:03:37 +01:00
4d3b4fa0be Fix rst 2017-12-22 14:48:08 +01:00
27c557e983 Update README.rst
test
2017-12-22 14:40:48 +01:00
7f24f7d34c Delete appveyor.yml 2017-12-22 14:36:13 +01:00
4b61108005 Remove AppVeyor II. 2017-12-22 14:35:23 +01:00
8b189725fd Remove AppVeyor
@appveyor  https://help.appveyor.com/discussions/problems/10507-pip-install-fails-with-access-is-denied-error
2017-12-22 14:34:20 +01:00
1719ebded6 Fix README (#641) 2017-12-22 03:37:04 +01:00
c5d6a4ad8e OS X => macOS
Close #634
2017-12-17 19:45:46 +01:00
91e1fe2d0f appveyor fix attempt II. 2017-12-13 21:32:37 +01:00
ca7f41de53 appveyor fix attempt 2017-12-13 21:29:51 +01:00
46e24dd6b5 Use function as source of styles for Fish completion 2017-12-13 21:22:53 +01:00
803127e8c9 Remove duplicate option from Fish completion list 2017-12-13 21:22:53 +01:00
4c138959ea Merge pull request #633 from darshanime/version_number_fix
fix env version attribute
2017-12-13 21:18:38 +01:00
91a28973bd Merge pull request #631 from CrazyPython/patch-2
Fix Travis Build by removing 2.6
2017-12-13 21:16:05 +01:00
02b28093a8 Merge pull request #630 from CrazyPython/patch-1
Clarify error message
2017-12-13 21:15:43 +01:00
d64e7d8a6a Merge pull request #638 from gtback/update-contributing-rst
Update CONTRIBUTING.rst to include correct Makefile targets.
2017-12-13 21:14:31 +01:00
8841b8bf46 Update CONTRIBUTING.rst to include correct Makefile targets. 2017-12-07 04:39:32 +00:00
6472ca55e1 fix env version attribute 2017-11-18 19:01:26 +05:30
37c3307018 Remove 2.6 2017-11-14 09:18:10 -05:00
0aab796960 Clarify error message 2017-11-13 07:23:52 -05:00
95c33e31a2 Merge pull request #614 from watersalesman/master
List DNF as Fedora package manager in README
2017-10-04 12:09:07 -05:00
9af833da30 List DNF as Fedora package manager in README 2017-10-02 16:55:35 -04:00
dfe6245cd6 Update AppVeyor 2017-09-07 13:57:15 +02:00
555761f3cb Update copyright year 2017-09-06 01:42:16 +02:00
643735ef23 Fix Gitter link
Close #590
2017-09-06 01:14:56 +02:00
7a45f14542 Merge pull request #584 from scorphus/hotfix/new-requests
Support requests>=2.14.0
2017-07-20 07:54:08 +02:00
e993f83355 Merge pull request #589 from alappe/patch-1
Update README.rst, fix typo…
2017-07-20 07:53:30 +02:00
d726a4cd92 Merge pull request #591 from DavidOliver/patch-1
Fix sentence on overriding default timeout in readme
2017-07-20 07:52:57 +02:00
8d3f09497b Fix sentence on overriding default timeout in readme 2017-06-30 14:54:49 +02:00
31c78c2885 Update README.rst 2017-06-26 13:00:46 +02:00
9776a6dea0 Support requests>=2.14.0
From that release onwards, `cert_verify` raises `IOError` [1].

    1: https://github.com/kennethreitz/requests/commit/7d8b87c
2017-05-17 20:31:10 -03:00
f1d4861fae Merge pull request #568 from dsego/dsego/ansi-colors
Follow terminal ANSI color styles

Close #524
2017-03-12 22:44:05 +01:00
d99e1ff492 Fix link 2017-03-12 13:31:03 +01:00
a196d1d451 Travis cache: pip 2017-03-12 13:18:39 +01:00
02209c2db1 Oops, remove semicolons 2017-03-11 18:12:00 +01:00
9886f01f91 New style option that applies the terminal ANSI color scheme 2017-03-11 18:00:35 +01:00
a4f796fe69 Revert "Follow terminal ANSI color styles"
This reverts commit b0fde07cfd.
2017-03-11 16:58:50 +01:00
c948f98b05 Update links 2017-03-10 11:27:38 +01:00
b0fde07cfd Follow terminal ANSI color styles
Removes the default solarized color scheme and custom http lexer class.
2017-03-06 01:05:50 +01:00
f74670fac1 Update README.rst 2017-03-01 12:40:26 +01:00
7321b9fa4e Add --verify true/false tests and CHANGELOG 2017-02-17 00:56:07 +01:00
cf8d5eb3e8 Merge pull request #560 from hangtwenty/dummyproof-cli-param-verify
Add --verify=(true|false) as an alternative to (yes|no) and make the boolean value case-insensitive
2017-02-17 00:43:22 +01:00
64af72eb88 Turn --verify=False/True to --verify=no/yes
One way to address #559 -- https://github.com/jkbrzt/httpie/issues/559
-- instead of warning or throwing an error, just accept "True" and "False"
as synonyms of yes/no

(Updated to reflect feedback given at https://github.com/jkbrzt/httpie/pull/560 )
2017-02-13 18:30:55 -06:00
de38f86730 Merge pull request #558 from RobDesideri/patch-1
Update pip official website url
2017-02-09 21:13:47 +01:00
244ad15c92 Update pip official website url 2017-02-09 15:25:07 +01:00
586f45e634 Merge pull request #494 from keik/patch-1
Fix typo
2017-02-07 20:50:43 +01:00
b1b4743663 Merge pull request #555 from rootulp/patch-1
Gitter Badge: flat-square style
2017-02-07 20:50:01 +01:00
5600b4a2d3 Merge pull request #557 from robertbenjamin/fix-doc-typo
Update README.rst
2017-02-07 20:49:34 +01:00
9261167a1f Fix typo in the docs 2017-02-02 11:45:58 -08:00
519654e21b Gitter Badge: flat-square style
To match the other badges
2017-01-22 20:58:58 +00:00
4840499a43 Merge pull request #552 from duboviy/master
Add Python 3.6 support
2017-01-08 19:57:56 +01:00
ee6cdf4ab3 Update setup.py 2017-01-08 16:20:53 +02:00
98003f545d Update appveyor.yml 2017-01-08 16:19:26 +02:00
0046ed73c6 Update .travis.yml 2017-01-08 16:18:19 +02:00
66a6475064 Update tox.ini 2017-01-08 16:12:31 +02:00
97804802c0 Alternatives 2016-12-17 03:10:52 +01:00
c9296a9a45 Added link to httpcat 2016-12-17 03:06:48 +01:00
64a41c2601 README 2016-12-17 03:04:59 +01:00
0af6ae1be4 Fix PyPi README rendering
Close #540
2016-12-09 00:26:55 +01:00
6b06d92a59 Fix typo 2016-07-27 09:54:26 +09:00
51 changed files with 653 additions and 612 deletions

View File

@ -1 +0,0 @@
; needs to exist otherwise `$ coveralls` fails

4
.gitignore vendored
View File

@ -2,12 +2,12 @@
.idea/ .idea/
__pycache__/ __pycache__/
dist/ dist/
httpie.egg-info/
build/ build/
*.egg-info *.egg-info
.cache/ .cache/
.tox .tox/
.coverage .coverage
*.pyc *.pyc
*.egg *.egg
htmlcov htmlcov
.pytest_cache/

View File

@ -1,94 +1,96 @@
# https://travis-ci.org/jkbrzt/httpie # <https://travis-ci.org/jakubroztocil/httpie>
sudo: false sudo: false
language: python language: python
os: os:
- linux - linux
env: env:
global: global:
- NEWEST_PYTHON=3.5 - NEWEST_PYTHON=3.7
python: python:
- 2.6 # <https://docs.travis-ci.com/user/languages/python/>
- 2.7 - 2.7
- pypy
- 3.4 # Python 3.4 fails installing packages
# <https://travis-ci.org/jakubroztocil/httpie/jobs/403263566#L636>
# - 3.4
- 3.5 - 3.5
# Currently fails because of a Flask issue - 3.6
# - pypy3 # - 3.7 # is done in the matrix below as described in travis-ci/travis-ci#9069
- pypy
# pypy3 currently fails because of a Flask issue
# - pypy3
cache: pip
matrix: matrix:
include: include:
# Add manually defined OS X builds
# Manually defined OS X builds # <https://docs.travis-ci.com/user/multi-os/#Python-example-(unsupported-languages)>
# https://docs.travis-ci.com/user/multi-os/#Python-example-(unsupported-languages)
# Stock OSX Python
- os: osx - os: osx
language: generic language: generic
env: env:
# Stock OSX Python
- TOXENV=py27-osx-builtin
- BREW_PYTHON_PACKAGE=
- os: osx
language: generic
env:
# Latest Python 2.7 from Homebrew
- TOXENV=py27 - TOXENV=py27
- BREW_PYTHON_PACKAGE=python@2
# Latest Python 2.x from Homebrew
- os: osx - os: osx
language: generic language: generic
env: env:
- TOXENV=py27 # Latest Python 3.x from Homebrew
- BREW_INSTALL=python - TOXENV=py37 # <= needs to be kept up-to-date to reflect latest minor version
- BREW_PYTHON_PACKAGE=python@3
# Latest Python 3.x from Homebrew # Travis Python 3.7 must run sudo on
- os: osx
language: generic
env:
- TOXENV=py35
- BREW_INSTALL=python3
# Python Codestyle
- os: linux - os: linux
python: 3.5 python: 3.7
env: CODESTYLE=true env: TOXENV=py37
sudo: true # Required for Python 3.7
dist: xenial # Required for Python 3.7
# Add a codestyle-only build
- os: linux
python: 3.6
env: CODESTYLE_ONLY=true
install: install:
- | - |
if [[ $TRAVIS_OS_NAME == 'osx' ]]; then if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
if [[ -n "$BREW_INSTALL" ]]; then if [[ -n "$BREW_PYTHON_PACKAGE" ]]; then
brew update brew update
brew install "$BREW_INSTALL" if ! brew list --versions "$BREW_PYTHON_PACKAGE" >/dev/null; then
brew install "$BREW_PYTHON_PACKAGE"
elif ! brew outdated "$BREW_PYTHON_PACKAGE"; then
brew upgrade "$BREW_PYTHON_PACKAGE"
fi
fi
sudo pip2 install tox
fi fi
sudo pip install tox
fi
if [[ $CODESTYLE ]]; then
pip install pycodestyle
fi
script: script:
- | - |
if [[ $TRAVIS_OS_NAME == 'linux' ]]; then if [[ $TRAVIS_OS_NAME == 'linux' ]]; then
if [[ $CODESTYLE ]]; then if [[ $CODESTYLE_ONLY ]]; then
# 241 - multiple spaces after , make pycodestyle
# 501 - line too long else
pycodestyle --ignore=E241,E501 make test
fi
else else
make PATH="/usr/local/bin:$PATH" tox -e "$TOXENV"
fi fi
else
PATH="/usr/local/bin:$PATH" tox -e "$TOXENV"
fi
after_success: after_success:
- | - |
if [[ $TRAVIS_PYTHON_VERSION == $NEWEST_PYTHON && $TRAVIS_OS_NAME == 'linux' ]]; then if [[ $TRAVIS_PYTHON_VERSION == $NEWEST_PYTHON && $TRAVIS_OS_NAME == 'linux' ]]; then
pip install python-coveralls && coveralls make coveralls
fi fi
notifications: notifications:
webhooks: webhooks:
# options: [always|never|change] default: always
on_success: always
on_failure: always
on_start: always
urls: urls:
# https://gitter.im/jkbrzt/httpie # https://gitter.im/jkbrzt/httpie
- https://webhooks.gitter.im/e/c42fcd359a110d02830b - https://webhooks.gitter.im/e/c42fcd359a110d02830b
on_success: always # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: always # options: [always|never|change] default: always

View File

@ -2,13 +2,13 @@
HTTPie authors HTTPie authors
============== ==============
* `Jakub Roztocil <https://github.com/jkbrzt>`_ * `Jakub Roztocil <https://github.com/jakubroztocil>`_
Patches and ideas Patches and ideas
----------------- -----------------
`Complete list of contributors on GitHub <https://github.com/jkbrzt/httpie/graphs/contributors>`_ `Complete list of contributors on GitHub <https://github.com/jakubroztocil/httpie/graphs/contributors>`_
* `Cláudia T. Delgado <https://github.com/claudiatd>`_ (logo) * `Cláudia T. Delgado <https://github.com/claudiatd>`_ (logo)
* `Hank Gay <https://github.com/gthank>`_ * `Hank Gay <https://github.com/gthank>`_

View File

@ -6,9 +6,26 @@ This document records all notable changes to `HTTPie <http://httpie.org>`_.
This project adheres to `Semantic Versioning <http://semver.org/>`_. This project adheres to `Semantic Versioning <http://semver.org/>`_.
`1.0.0-dev`_ (unreleased) `1.0.0`_ (2018-11-02)
------------------------- -------------------------
* Added ``--style=auto`` which follows the terminal ANSI color styles.
* Added support for selecting TLS 1.3 via ``--ssl=tls1.3``
(available once implemented in upstream libraries).
* Added ``true``/``false`` as valid values for ``--verify``
(in addition to ``yes``/``no``) and the boolean value is case-insensitive.
* Changed the default ``--style`` from ``solarized`` to ``auto`` (on Windows it stays ``fruity``).
* Fixed default headers being incorrectly case-sensitive.
* Removed Python 2.6 support.
`0.9.9`_ (2016-12-08)
---------------------
* Fixed README.
`0.9.8`_ (2016-12-08) `0.9.8`_ (2016-12-08)
--------------------- ---------------------
@ -92,8 +109,8 @@ This project adheres to `Semantic Versioning <http://semver.org/>`_.
--------------------- ---------------------
* Added support for Requests transport adapter plugins * Added support for Requests transport adapter plugins
(see `httpie-unixsocket <https://github.com/msabramo/httpie-unixsocket>`_ (see `httpie-unixsocket <https://github.com/httpie/httpie-unixsocket>`_
and `httpie-http2 <https://github.com/jkbrzt/httpie-http2>`_) and `httpie-http2 <https://github.com/httpie/httpie-http2>`_)
`0.9.0`_ (2015-01-31) `0.9.0`_ (2015-01-31)
@ -297,29 +314,30 @@ This project adheres to `Semantic Versioning <http://semver.org/>`_.
* Initial public release * Initial public release
.. _`0.1`: https://github.com/jkbrzt/httpie/commit/b966efa .. _`0.1`: https://github.com/jakubroztocil/httpie/commit/b966efa
.. _0.1.4: https://github.com/jkbrzt/httpie/compare/b966efa...0.1.4 .. _0.1.4: https://github.com/jakubroztocil/httpie/compare/b966efa...0.1.4
.. _0.1.5: https://github.com/jkbrzt/httpie/compare/0.1.4...0.1.5 .. _0.1.5: https://github.com/jakubroztocil/httpie/compare/0.1.4...0.1.5
.. _0.1.6: https://github.com/jkbrzt/httpie/compare/0.1.5...0.1.6 .. _0.1.6: https://github.com/jakubroztocil/httpie/compare/0.1.5...0.1.6
.. _0.2.0: https://github.com/jkbrzt/httpie/compare/0.1.6...0.2.0 .. _0.2.0: https://github.com/jakubroztocil/httpie/compare/0.1.6...0.2.0
.. _0.2.1: https://github.com/jkbrzt/httpie/compare/0.2.0...0.2.1 .. _0.2.1: https://github.com/jakubroztocil/httpie/compare/0.2.0...0.2.1
.. _0.2.2: https://github.com/jkbrzt/httpie/compare/0.2.1...0.2.2 .. _0.2.2: https://github.com/jakubroztocil/httpie/compare/0.2.1...0.2.2
.. _0.2.5: https://github.com/jkbrzt/httpie/compare/0.2.2...0.2.5 .. _0.2.5: https://github.com/jakubroztocil/httpie/compare/0.2.2...0.2.5
.. _0.2.6: https://github.com/jkbrzt/httpie/compare/0.2.5...0.2.6 .. _0.2.6: https://github.com/jakubroztocil/httpie/compare/0.2.5...0.2.6
.. _0.2.7: https://github.com/jkbrzt/httpie/compare/0.2.5...0.2.7 .. _0.2.7: https://github.com/jakubroztocil/httpie/compare/0.2.5...0.2.7
.. _0.3.0: https://github.com/jkbrzt/httpie/compare/0.2.7...0.3.0 .. _0.3.0: https://github.com/jakubroztocil/httpie/compare/0.2.7...0.3.0
.. _0.4.0: https://github.com/jkbrzt/httpie/compare/0.3.0...0.4.0 .. _0.4.0: https://github.com/jakubroztocil/httpie/compare/0.3.0...0.4.0
.. _0.4.1: https://github.com/jkbrzt/httpie/compare/0.4.0...0.4.1 .. _0.4.1: https://github.com/jakubroztocil/httpie/compare/0.4.0...0.4.1
.. _0.5.0: https://github.com/jkbrzt/httpie/compare/0.4.1...0.5.0 .. _0.5.0: https://github.com/jakubroztocil/httpie/compare/0.4.1...0.5.0
.. _0.5.1: https://github.com/jkbrzt/httpie/compare/0.5.0...0.5.1 .. _0.5.1: https://github.com/jakubroztocil/httpie/compare/0.5.0...0.5.1
.. _0.6.0: https://github.com/jkbrzt/httpie/compare/0.5.1...0.6.0 .. _0.6.0: https://github.com/jakubroztocil/httpie/compare/0.5.1...0.6.0
.. _0.7.1: https://github.com/jkbrzt/httpie/compare/0.6.0...0.7.1 .. _0.7.1: https://github.com/jakubroztocil/httpie/compare/0.6.0...0.7.1
.. _0.8.0: https://github.com/jkbrzt/httpie/compare/0.7.1...0.8.0 .. _0.8.0: https://github.com/jakubroztocil/httpie/compare/0.7.1...0.8.0
.. _0.9.0: https://github.com/jkbrzt/httpie/compare/0.8.0...0.9.0 .. _0.9.0: https://github.com/jakubroztocil/httpie/compare/0.8.0...0.9.0
.. _0.9.1: https://github.com/jkbrzt/httpie/compare/0.9.0...0.9.1 .. _0.9.1: https://github.com/jakubroztocil/httpie/compare/0.9.0...0.9.1
.. _0.9.2: https://github.com/jkbrzt/httpie/compare/0.9.1...0.9.2 .. _0.9.2: https://github.com/jakubroztocil/httpie/compare/0.9.1...0.9.2
.. _0.9.3: https://github.com/jkbrzt/httpie/compare/0.9.2...0.9.3 .. _0.9.3: https://github.com/jakubroztocil/httpie/compare/0.9.2...0.9.3
.. _0.9.4: https://github.com/jkbrzt/httpie/compare/0.9.3...0.9.4 .. _0.9.4: https://github.com/jakubroztocil/httpie/compare/0.9.3...0.9.4
.. _0.9.6: https://github.com/jkbrzt/httpie/compare/0.9.4...0.9.6 .. _0.9.6: https://github.com/jakubroztocil/httpie/compare/0.9.4...0.9.6
.. _0.9.8: https://github.com/jkbrzt/httpie/compare/0.9.6...0.9.8 .. _0.9.8: https://github.com/jakubroztocil/httpie/compare/0.9.6...0.9.8
.. _1.0.0-dev: https://github.com/jkbrzt/httpie/compare/0.9.8...master .. _0.9.9: https://github.com/jakubroztocil/httpie/compare/0.9.8...0.9.9
.. _1.0.0: https://github.com/jakubroztocil/httpie/compare/0.9.9...1.0.0

View File

@ -25,14 +25,14 @@ to your bug report, e.g.:
Before working on a new feature or a bug, please browse `existing issues`_ Before working on a new feature or a bug, please browse `existing issues`_
to see whether it has been previously discussed. If the change in question to see whether it has been previously discussed. If the change in question
is a bigger one, it's always good to discuss before your starting working on is a bigger one, it's always good to discuss before you start working on
it. it.
Creating Development Environment Creating Development Environment
-------------------------------- --------------------------------
Go to https://github.com/jkbrzt/httpie and fork the project repository. Go to https://github.com/jakubroztocil/httpie and fork the project repository.
.. code-block:: bash .. code-block:: bash
@ -47,13 +47,14 @@ Go to https://github.com/jkbrzt/httpie and fork the project repository.
# Install dev. requirements and also HTTPie (in editable mode # Install dev. requirements and also HTTPie (in editable mode
# so that the `http' command will point to your working copy): # so that the `http' command will point to your working copy):
make make init
Making Changes Making Changes
-------------- --------------
Please make sure your changes conform to `Style Guide for Python Code`_ (PEP8). Please make sure your changes conform to `Style Guide for Python Code`_ (PEP8)
and that ``make pycodestyle`` passes.
Testing Testing
@ -71,18 +72,18 @@ Running all tests:
.. code-block:: bash .. code-block:: bash
# Run all tests on the current Python interpreter # Run all tests on the current Python interpreter with coverage
make test make test
# Run all tests on the current Python with coverage
make test-cover
# Run all tests in all of the supported and available Pythons via Tox # Run all tests in all of the supported and available Pythons via Tox
make test-tox make test-tox
# Run all tests for code as well as packaging, etc. # Run all tests for code as well as packaging, etc.
make test-all make test-all
# Test PEP8 compliance
make pycodestyle
Running specific tests: Running specific tests:
*********************** ***********************
@ -95,11 +96,11 @@ Running specific tests:
py.test tests/test_uploads.py::TestMultipartFormDataFileUpload::test_upload_ok py.test tests/test_uploads.py::TestMultipartFormDataFileUpload::test_upload_ok
# Run specific tests on the on all Pythons via Tox # Run specific tests on the on all Pythons via Tox
# (change to `tox -e py37' to limit Python version)
tox -- tests/test_uploads.py --verbose tox -- tests/test_uploads.py --verbose
tox -- tests/test_uploads.py::TestMultipartFormDataFileUpload --verbose tox -- tests/test_uploads.py::TestMultipartFormDataFileUpload --verbose
tox -- tests/test_uploads.py::TestMultipartFormDataFileUpload::test_upload_ok --verbose tox -- tests/test_uploads.py::TestMultipartFormDataFileUpload::test_upload_ok --verbose
----- -----
See `Makefile`_ for additional development utilities. See `Makefile`_ for additional development utilities.
@ -107,10 +108,10 @@ Don't forget to add yourself to `AUTHORS`_!
.. _Tox: http://tox.testrun.org .. _Tox: http://tox.testrun.org
.. _supported Python environments: https://github.com/jkbrzt/httpie/blob/master/tox.ini .. _supported Python environments: https://github.com/jakubroztocil/httpie/blob/master/tox.ini
.. _existing issues: https://github.com/jkbrzt/httpie/issues?state=open .. _existing issues: https://github.com/jakubroztocil/httpie/issues?state=open
.. _AUTHORS: https://github.com/jkbrzt/httpie/blob/master/AUTHORS.rst .. _AUTHORS: https://github.com/jakubroztocil/httpie/blob/master/AUTHORS.rst
.. _Makefile: https://github.com/jkbrzt/httpie/blob/master/Makefile .. _Makefile: https://github.com/jakubroztocil/httpie/blob/master/Makefile
.. _pytest: http://pytest.org/ .. _pytest: http://pytest.org/
.. _Style Guide for Python Code: http://python.org/dev/peps/pep-0008/ .. _Style Guide for Python Code: http://python.org/dev/peps/pep-0008/
.. _test suite: https://github.com/jkbrzt/httpie/tree/master/tests .. _test suite: https://github.com/jakubroztocil/httpie/tree/master/tests

View File

@ -1,4 +1,4 @@
Copyright © 2012-2016 Jakub Roztocil <jakub@roztocil.co> Copyright © 2012-2017 Jakub Roztocil <jakub@roztocil.co>
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:

View File

@ -1,6 +1,6 @@
# ###############################################################################
# See ./CONTRIBUTING.rst # See ./CONTRIBUTING.rst
# ###############################################################################
VERSION=$(shell grep __version__ httpie/__init__.py) VERSION=$(shell grep __version__ httpie/__init__.py)
REQUIREMENTS="requirements-dev.txt" REQUIREMENTS="requirements-dev.txt"
@ -20,6 +20,17 @@ init: uninstall-httpie
@echo @echo
clean:
@echo $(TAG)Cleaning up$(END)
rm -rf .tox *.egg dist build .coverage .cache .pytest_cache httpie.egg-info
find . -name '__pycache__' -delete -print -o -name '*.pyc' -delete -print
@echo
###############################################################################
# Testing
###############################################################################
test: init test: init
@echo $(TAG)Running tests on the current Python interpreter with coverage $(END) @echo $(TAG)Running tests on the current Python interpreter with coverage $(END)
@ -27,9 +38,8 @@ test: init
@echo @echo
test-tox: init # test-all is meant to test everything — even this Makefile
@echo $(TAG)Running tests on all Pythons via Tox$(END) test-all: uninstall-all clean init test test-tox test-dist pycodestyle
tox
@echo @echo
@ -37,6 +47,12 @@ test-dist: test-sdist test-bdist-wheel
@echo @echo
test-tox: init
@echo $(TAG)Running tests on all Pythons via Tox$(END)
tox
@echo
test-sdist: clean uninstall-httpie test-sdist: clean uninstall-httpie
@echo $(TAG)Testing sdist build an installation$(END) @echo $(TAG)Testing sdist build an installation$(END)
python setup.py sdist python setup.py sdist
@ -53,12 +69,26 @@ test-bdist-wheel: clean uninstall-httpie
@echo @echo
# This tests everything, even this Makefile. pycodestyle:
test-all: uninstall-all clean init test test-tox test-dist which pycodestyle || pip install pycodestyle
pycodestyle
@echo
coveralls:
which coveralls || pip install python-coveralls
coveralls
@echo
###############################################################################
# Publishing to PyPi
###############################################################################
publish: test-all publish-no-test publish: test-all publish-no-test
publish-no-test: publish-no-test:
@echo $(TAG)Testing wheel build an installation$(END) @echo $(TAG)Testing wheel build an installation$(END)
@echo "$(VERSION)" @echo "$(VERSION)"
@ -69,12 +99,10 @@ publish-no-test:
@echo @echo
clean:
@echo $(TAG)Cleaning up$(END)
rm -rf .tox *.egg dist build .coverage
find . -name '__pycache__' -delete -print -o -name '*.pyc' -delete -print
@echo
###############################################################################
# Uninstalling
###############################################################################
uninstall-httpie: uninstall-httpie:
@echo $(TAG)Uninstalling httpie$(END) @echo $(TAG)Uninstalling httpie$(END)
@ -96,5 +124,10 @@ uninstall-all: uninstall-httpie
- pip uninstall --yes -r $(REQUIREMENTS) - pip uninstall --yes -r $(REQUIREMENTS)
###############################################################################
# Utils
###############################################################################
homebrew-formula-vars: homebrew-formula-vars:
extras/get-homebrew-formula-vars.py extras/get-homebrew-formula-vars.py

View File

@ -11,7 +11,7 @@ generally interacting with HTTP servers.
.. class:: no-web .. class:: no-web
.. image:: https://raw.githubusercontent.com/jkbrzt/httpie/master/httpie.png .. image:: https://raw.githubusercontent.com/jakubroztocil/httpie/master/httpie.png
:alt: HTTPie compared to cURL :alt: HTTPie compared to cURL
:width: 100% :width: 100%
:align: center :align: center
@ -19,7 +19,7 @@ generally interacting with HTTP servers.
.. class:: no-web no-pdf .. class:: no-web no-pdf
|pypi| |unix_build| |windows_build| |coverage| |gitter| |pypi| |unix_build| |coverage| |gitter|
@ -27,9 +27,6 @@ generally interacting with HTTP servers.
.. section-numbering:: .. section-numbering::
.. raw:: pdf
PageBreak oneColumn
Main features Main features
@ -44,8 +41,8 @@ Main features
* Custom headers * Custom headers
* Persistent sessions * Persistent sessions
* Wget-like downloads * Wget-like downloads
* Python 2.6, 2.7 and 3.x support * Python 2.7 and 3.x support
* Linux, Mac OS X and Windows support * Linux, macOS and Windows support
* Plugins * Plugins
* Documentation * Documentation
* Test coverage * Test coverage
@ -77,16 +74,25 @@ Linux
----- -----
Most Linux distributions provide a package that can be installed using the Most Linux distributions provide a package that can be installed using the
system package manager, e.g.: system package manager, for example:
.. code-block:: bash .. code-block:: bash
# Debian-based distributions such as Ubuntu: # Debian, Ubuntu, etc.
$ apt-get install httpie $ apt-get install httpie
# RPM-based distributions: .. code-block:: bash
# Fedora
$ dnf install httpie
.. code-block:: bash
# CentOS, RHEL, ...
$ yum install httpie $ yum install httpie
.. code-block:: bash
# Arch Linux # Arch Linux
$ pacman -S httpie $ pacman -S httpie
@ -110,31 +116,51 @@ and always provides the latest version) is to use `pip`_:
``easy_install httpie`` as a fallback.) ``easy_install httpie`` as a fallback.)
Development version
-------------------
The latest development version can be installed directly from GitHub:
.. code-block:: bash
# Mac OS X via Homebrew
$ brew install httpie --HEAD
# Universal
$ pip install --upgrade https://github.com/jkbrzt/httpie/archive/master.tar.gz
Python version Python version
-------------- --------------
Although Python 2.6 and 2.7 are supported as well, it is recommended to install Although Python 2.7 is supported as well, it is strongly recommended to
HTTPie against the latest Python 3.x whenever possible. That will ensure that install HTTPie against the latest Python 3.x whenever possible. That will
some of the newer HTTP features, such as `SNI (Server Name Indication)`_, ensure that some of the newer HTTP features, such as
work out of the box. `SNI (Server Name Indication)`_, work out of the box.
Python 3 is the default for Homebrew installations starting with version 0.9.4. Python 3 is the default for Homebrew installations starting with version 0.9.4.
To see which version HTTPie uses, run ``http --debug``. To see which version HTTPie uses, run ``http --debug``.
Unstable version
----------------
You can also install the latest unreleased development version directly from
the ``master`` branch on GitHub. It is a work-in-progress of a future stable
release so the experience might be not as smooth.
|unix_build|
On macOS you can install it with Homebrew:
.. code-block:: bash
$ brew install httpie --HEAD
Otherwise with ``pip``:
.. code-block:: bash
$ pip install --upgrade https://github.com/jakubroztocil/httpie/archive/master.tar.gz
Verify that now we have the
`current development version identifier <https://github.com/jakubroztocil/httpie/blob/0af6ae1be444588bbc4747124e073423151178a0/httpie/__init__.py#L5>`_
with the ``-dev`` suffix, for example:
.. code-block:: bash
$ http --version
1.0.0-dev
Usage Usage
===== =====
@ -182,12 +208,12 @@ See the request that is being sent using one of the `output options`_:
Use `Github API`_ to post a comment on an Use `Github API`_ to post a comment on an
`issue <https://github.com/jkbrzt/httpie/issues/83>`_ `issue <https://github.com/jakubroztocil/httpie/issues/83>`_
with `authentication`_: with `authentication`_:
.. code-block:: bash .. code-block:: bash
$ http -a USERNAME POST https://api.github.com/repos/jkbrzt/httpie/issues/83/comments body='HTTPie is awesome! :heart:' $ http -a USERNAME POST https://api.github.com/repos/jakubroztocil/httpie/issues/83/comments body='HTTPie is awesome! :heart:'
Upload a file using `redirected input`_: Upload a file using `redirected input`_:
@ -261,7 +287,7 @@ and can be omitted from the argument ``http example.org`` works just fine.
Querystring parameters Querystring parameters
---------------------- ----------------------
If you find yourself manually constructing URLs with If you find yourself manually constructing URLs with querystring parameters
on the terminal, you may appreciate the ``param==value`` syntax for appending on the terminal, you may appreciate the ``param==value`` syntax for appending
URL parameters. With that, you don't have to worry about escaping the ``&`` URL parameters. With that, you don't have to worry about escaping the ``&``
separators for your shell. Also, special characters in parameter values, separators for your shell. Also, special characters in parameter values,
@ -372,8 +398,7 @@ their type is distinguished only by the separator used:
Note that data fields aren't the only way to specify request data: Note that data fields aren't the only way to specify request data:
`Redirected input`_ is a mechanism for passing arbitrary data request `Redirected input`_ is a mechanism for passing arbitrary request data.
request.
Escaping rules Escaping rules
@ -400,7 +425,7 @@ token ``--`` to prevent confusion with ``--arguments``:
Content-Type: application/json Content-Type: application/json
{ {
"-name-starting-with-dash": "value" "-name-starting-with-dash": "foo"
} }
@ -409,7 +434,7 @@ JSON
==== ====
JSON is the *lingua franca* of modern web services and it is also the JSON is the *lingua franca* of modern web services and it is also the
**implicit content type** HTTPie by default uses. **implicit content type** HTTPie uses by default.
Simple example: Simple example:
@ -594,7 +619,7 @@ There are a couple of default headers that HTTPie sets:
Any of thoseexcept for ``Host``can be overwritten and some of them unset. Any of these except ``Host`` can be overwritten and some of them unset.
@ -618,6 +643,53 @@ To send a header with an empty value, use ``Header;``:
$ http httpbin.org/headers 'Header;' $ http httpbin.org/headers 'Header;'
Cookies
=======
HTTP clients send cookies to the server as regular `HTTP headers`_. That means,
HTTPie does not offer any special syntax for specifying cookies — the usual
``Header:Value`` notation is used:
Send a single cookie:
.. code-block:: bash
$ http example.org Cookie:sessionid=foo
.. code-block:: http
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: sessionid=foo
Host: example.org
User-Agent: HTTPie/0.9.9
Send multiple cookies
(note the header is quoted to prevent the shell from interpreting the ``;``):
.. code-block:: bash
$ http example.org 'Cookie:sessionid=foo;another-cookie=bar'
.. code-block:: http
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: sessionid=foo;another-cookie=bar
Host: example.org
User-Agent: HTTPie/0.9.9
If you often deal with cookies in your requests, then chances are you'd appreciate
the `sessions`_ feature.
Authentication Authentication
============== ==============
@ -670,7 +742,7 @@ Password prompt
``.netrc`` ``.netrc``
---------- ----------
Authorization information from your ``~/.netrc`` file is honored as well: Authentication information from your ``~/.netrc`` file is honored as well:
.. code-block:: bash .. code-block:: bash
@ -791,7 +863,7 @@ In your ``~/.bash_profile``:
SOCKS SOCKS
----- -----
To enable SOCKS proxy support please install ``requests[socks]`` using ``pip``: Homebrew-installed HTTPie comes with SOCKS proxy support out of the box. To enable SOCKS proxy support for non-Homebrew installations, you'll need to install ``requests[socks]`` manually using ``pip``:
.. code-block:: bash .. code-block:: bash
@ -855,7 +927,7 @@ SSL version
Use the ``--ssl=<PROTOCOL>`` to specify the desired protocol version to use. Use the ``--ssl=<PROTOCOL>`` to specify the desired protocol version to use.
This will default to SSL v2.3 which will negotiate the highest protocol that both This will default to SSL v2.3 which will negotiate the highest protocol that both
the server and your installation of OpenSSL support. The available protocols the server and your installation of OpenSSL support. The available protocols
are ``ssl2.3``, ``ssl3``, ``tls1``, ``tls1.1``, ``tls1.2``. (The actually are ``ssl2.3``, ``ssl3``, ``tls1``, ``tls1.1``, ``tls1.2``, ``tls1.3``. (The actually
available set of protocols may vary depending on your OpenSSL installation.) available set of protocols may vary depending on your OpenSSL installation.)
.. code-block:: bash .. code-block:: bash
@ -895,7 +967,7 @@ be printed via several options:
``--headers, -h`` Only the response headers are printed. ``--headers, -h`` Only the response headers are printed.
``--body, -b`` Only the response body is printed. ``--body, -b`` Only the response body is printed.
``--verbose, -v`` Print the whole HTTP exchange (request and response). ``--verbose, -v`` Print the whole HTTP exchange (request and response).
This option also enables ``--all`` (see bellow). This option also enables ``--all`` (see below).
``--print, -p`` Selects parts of the HTTP exchange. ``--print, -p`` Selects parts of the HTTP exchange.
================= ===================================================== ================= =====================================================
@ -1035,7 +1107,7 @@ You can even pipe web services together using HTTPie:
.. code-block:: bash .. code-block:: bash
$ http GET https://api.github.com/repos/jkbrzt/httpie | http POST httpbin.org/post $ http GET https://api.github.com/repos/jakubroztocil/httpie | http POST httpbin.org/post
You can use ``cat`` to enter multiline data on the terminal: You can use ``cat`` to enter multiline data on the terminal:
@ -1212,7 +1284,7 @@ is being saved to a file.
.. code-block:: bash .. code-block:: bash
$ http --download https://github.com/jkbrzt/httpie/archive/master.tar.gz $ http --download https://github.com/jakubroztocil/httpie/archive/master.tar.gz
.. code-block:: http .. code-block:: http
@ -1242,7 +1314,7 @@ headers and progress are still shown in the terminal:
.. code-block:: bash .. code-block:: bash
$ http -d https://github.com/jkbrzt/httpie/archive/master.tar.gz | tar zxf - $ http -d https://github.com/jakubroztocil/httpie/archive/master.tar.gz | tar zxf -
@ -1320,8 +1392,8 @@ previous ones to the same host.
However, HTTPie also supports persistent However, HTTPie also supports persistent
sessions via the ``--session=SESSION_NAME_OR_PATH`` option. In a session, sessions via the ``--session=SESSION_NAME_OR_PATH`` option. In a session,
custom headersexcept for the ones starting with ``Content-`` or ``If-``, custom `HTTP headers`_ (except for the ones starting with ``Content-`` or ``If-``),
authorization, and cookies `authentication`_, and `cookies`_
(manually specified or sent by the server) persist between requests (manually specified or sent by the server) persist between requests
to the same host. to the same host.
@ -1335,11 +1407,12 @@ to the same host.
$ http --session=/tmp/session.json example.org $ http --session=/tmp/session.json example.org
All session data, including credentials, cookie data, All session data, including credentials, cookie data,
and custom headers are stored in plain text. and custom headers are stored in plain text.
That means session files can also be created and edited manually in a text That means session files can also be created and edited manually in a text
editor—they are regular JSON. editor—they are regular JSON. It also means that they can be read by anyone
who has access to the session file.
Named sessions Named sessions
-------------- --------------
@ -1352,8 +1425,8 @@ you can create a new session named ``user1`` for ``example.org``:
$ http --session=user1 -a user1:password example.org X-Foo:Bar $ http --session=user1 -a user1:password example.org X-Foo:Bar
From now onw, you can refer to the session by its name. When you choose to From now on, you can refer to the session by its name. When you choose to
use the session again, any the previously used authorization and HTTP headers use the session again, any previously specified authentication or HTTP headers
will automatically be set: will automatically be set:
.. code-block:: bash .. code-block:: bash
@ -1488,7 +1561,7 @@ expecting that the request body will be passed through.
And since there's no data nor ``EOF``, it will be stuck. So unless you're And since there's no data nor ``EOF``, it will be stuck. So unless you're
piping some data to HTTPie, this flag should be used in scripts. piping some data to HTTPie, this flag should be used in scripts.
Also, it's might be good to override the default ``30`` second ``--timeout`` to Also, it might be good to override the default ``30`` second ``--timeout`` to
something that suits you. something that suits you.
@ -1555,7 +1628,7 @@ Please use the following support channels:
to ask questions (please make sure to use the to ask questions (please make sure to use the
`httpie <http://stackoverflow.com/questions/tagged/httpie>`_ tag). `httpie <http://stackoverflow.com/questions/tagged/httpie>`_ tag).
* Tweet directly to `@clihttp <https://twitter.com/clihttp>`_. * Tweet directly to `@clihttp <https://twitter.com/clihttp>`_.
* You can also tweet directly to `@jkbrzt`_. * You can also tweet directly to `@jakubroztocil`_.
Related projects Related projects
@ -1585,16 +1658,25 @@ HTTPie plays exceptionally well with the following tools:
and command syntax highlighting and command syntax highlighting
Alternatives
~~~~~~~~~~~~
* `httpcat <https://github.com/jakubroztocil/httpcat>`_ — a lower-level sister utility
of HTTPie for constructing raw HTTP requests on the command line.
* `curl <https://curl.haxx.se>`_ — a "Swiss knife" command line tool and
an exceptional library for transferring data with URLs.
Contributing Contributing
------------ ------------
See `CONTRIBUTING.rst <https://github.com/jkbrzt/httpie/blob/master/CONTRIBUTING.rst>`_. See `CONTRIBUTING.rst <https://github.com/jakubroztocil/httpie/blob/master/CONTRIBUTING.rst>`_.
Change log Change log
---------- ----------
See `CHANGELOG <https://github.com/jkbrzt/httpie/blob/master/CHANGELOG.rst>`_. See `CHANGELOG <https://github.com/jakubroztocil/httpie/blob/master/CHANGELOG.rst>`_.
Artwork Artwork
@ -1606,22 +1688,22 @@ See `claudiatd/httpie-artwork`_
Licence Licence
------- -------
BSD-3-Clause: `LICENSE <https://github.com/jkbrzt/httpie/blob/master/LICENSE>`_. BSD-3-Clause: `LICENSE <https://github.com/jakubroztocil/httpie/blob/master/LICENSE>`_.
Authors Authors
------- -------
`Jakub Roztocil`_ (`@jkbrzt`_) created HTTPie and `these fine people`_ `Jakub Roztocil`_ (`@jakubroztocil`_) created HTTPie and `these fine people`_
have contributed. have contributed.
.. _pip: http://www.pip-installer.org/en/latest/index.html .. _pip: https://pip.pypa.io/en/stable/installing/
.. _Github API: http://developer.github.com/v3/issues/comments/#create-a-comment .. _Github API: http://developer.github.com/v3/issues/comments/#create-a-comment
.. _these fine people: https://github.com/jkbrzt/httpie/contributors .. _these fine people: https://github.com/jakubroztocil/httpie/contributors
.. _Jakub Roztocil: http://roztocil.co .. _Jakub Roztocil: https://roztocil.co
.. _@jkbrzt: https://twitter.com/jkbrzt .. _@jakubroztocil: https://twitter.com/jakubroztocil
.. _claudiatd/httpie-artwork: https://github.com/claudiatd/httpie-artwork .. _claudiatd/httpie-artwork: https://github.com/claudiatd/httpie-artwork
@ -1629,19 +1711,14 @@ have contributed.
:target: https://pypi.python.org/pypi/httpie :target: https://pypi.python.org/pypi/httpie
:alt: Latest version released on PyPi :alt: Latest version released on PyPi
.. |coverage| image:: https://img.shields.io/coveralls/jkbrzt/httpie/master.svg?style=flat-square&label=coverage .. |coverage| image:: https://img.shields.io/coveralls/jakubroztocil/httpie/master.svg?style=flat-square&label=coverage
:target: https://coveralls.io/r/jkbrzt/httpie?branch=master :target: https://coveralls.io/r/jakubroztocil/httpie?branch=master
:alt: Test coverage :alt: Test coverage
.. |unix_build| image:: https://img.shields.io/travis/jkbrzt/httpie/master.svg?style=flat-square&label=unix%20build .. |unix_build| image:: https://img.shields.io/travis/jakubroztocil/httpie/master.svg?style=flat-square&label=unix%20build
:target: http://travis-ci.org/jkbrzt/httpie :target: http://travis-ci.org/jakubroztocil/httpie
:alt: Build status of the master branch on Mac/Linux :alt: Build status of the master branch on Mac/Linux
.. |windows_build| image:: https://img.shields.io/appveyor/ci/jkbrzt/httpie.svg?style=flat-square&label=windows%20build .. |gitter| image:: https://img.shields.io/gitter/room/jkbrzt/httpie.svg?style=flat-square
:target: https://ci.appveyor.com/project/jkbrzt/httpie
:alt: Build status of the master branch on Windows
.. |gitter| image:: https://badges.gitter.im/jkbrzt/httpie.svg
:target: https://gitter.im/jkbrzt/httpie :target: https://gitter.im/jkbrzt/httpie
:alt: Chat on Gitter :alt: Chat on Gitter

View File

@ -1,23 +0,0 @@
# https://ci.appveyor.com/project/jkbrzt/httpie
build: false
environment:
matrix:
- PYTHON: "C:/Python27"
# Python 3.4 has outdated pip
# - PYTHON: "C:/Python34"
- PYTHON: "C:/Python35"
init:
- "ECHO %PYTHON%"
- ps: "ls C:/Python*"
install:
# FIXME: updating pip fails with PermissionError
# - "%PYTHON%/Scripts/pip.exe install -U pip setuptools"
- "%PYTHON%/Scripts/pip.exe install -e ."
test_script:
- "%PYTHON%/Scripts/pip.exe --version"
- "%PYTHON%/Scripts/http.exe --debug"
- "%PYTHON%/python.exe setup.py test"

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Generate URLs and file hashes to be included in the Homebrew formula Generate URLs and file hashes to be included in the Homebrew formula
after a new release of HTTPie is published on PyPi. after a new release of HTTPie has been published on PyPi.
https://github.com/Homebrew/homebrew-core/blob/master/Formula/httpie.rb https://github.com/Homebrew/homebrew-core/blob/master/Formula/httpie.rb
@ -17,7 +17,7 @@ PACKAGES = [
] ]
def get_info(package_name): def get_package_meta(package_name):
api_url = 'https://pypi.python.org/pypi/{}/json'.format(package_name) api_url = 'https://pypi.python.org/pypi/{}/json'.format(package_name)
resp = requests.get(api_url).json() resp = requests.get(api_url).json()
hasher = hashlib.sha256() hasher = hashlib.sha256()
@ -35,21 +35,23 @@ def get_info(package_name):
'{}: download not found: {}'.format(package_name, resp)) '{}: download not found: {}'.format(package_name, resp))
packages = { def main():
package_name: get_info(package_name) for package_name in PACKAGES package_meta_map = {
} package_name: get_package_meta(package_name)
for package_name in PACKAGES
}
httpie_meta = package_meta_map.pop('httpie')
print()
print(' url "{url}"'.format(url=httpie_meta['url']))
print(' sha256 "{sha256}"'.format(sha256=httpie_meta['sha256']))
print()
for dep_meta in package_meta_map.values():
print(' resource "{name}" do'.format(name=dep_meta['name']))
print(' url "{url}"'.format(url=dep_meta['url']))
print(' sha256 "{sha256}"'.format(sha256=dep_meta['sha256']))
print(' end')
print()
httpie_info = packages.pop('httpie') if __name__ == '__main__':
print(""" main()
url "{url}"
sha256 "{sha256}"
""".format(**httpie_info))
for package_info in packages.values():
print("""
resource "{name}" do
url "{url}"
sha256 "{sha256}"
end""".format(**package_info))

View File

@ -30,11 +30,10 @@ function __fish_httpie_styles
echo "xcode" echo "xcode"
end end
complete -x -c http -s s -l style -d 'Output coloring style (default is "monokai")' -A -a "autumn borland bw colorful default emacs friendly fruity igor manni monokai murphy native paraiso-dark paraiso-light pastie perldoc rrt solarized tango trac vim vs xcode" complete -x -c http -s s -l style -d 'Output coloring style (default is "monokai")' -A -a '(__fish_httpie_styles)'
complete -c http -s f -l form -d 'Data items from the command line are serialized as form fields' complete -c http -s f -l form -d 'Data items from the command line are serialized as form fields'
complete -c http -s j -l json -d '(default) Data items from the command line are serialized as a JSON object' complete -c http -s j -l json -d '(default) Data items from the command line are serialized as a JSON object'
complete -x -c http -l pretty -d 'Controls output processing' -a "all colors format none" -A complete -x -c http -l pretty -d 'Controls output processing' -a "all colors format none" -A
complete -x -c http -s s -l style -d 'Output coloring style (default is "monokai")' -A -a "autumn borland bw colorful default emacs friendly fruity igor manni monokai murphy native paraiso-dark paraiso-light pastie perldoc rrt solarized tango trac vim vs xcode"
complete -x -c http -s p -l print -d 'String specifying what the output should contain' complete -x -c http -s p -l print -d 'String specifying what the output should contain'
complete -c http -s v -l verbose -d 'Print the whole request as well as the response' complete -c http -s v -l verbose -d 'Print the whole request as well as the response'
complete -c http -s h -l headers -d 'Print only the response headers' complete -c http -s h -l headers -d 'Print only the response headers'

View File

@ -11,7 +11,7 @@ class Httpie < Formula
url "https://pypi.python.org/packages/85/95/7ccea3ae7fd1185e21629f6d14fa9c896d6250bb15fb492efa91edc741a2/httpie-0.9.8.tar.gz" url "https://pypi.python.org/packages/85/95/7ccea3ae7fd1185e21629f6d14fa9c896d6250bb15fb492efa91edc741a2/httpie-0.9.8.tar.gz"
sha256 "515870b15231530f56fe2164190581748e8799b66ef0fe36ec9da3396f0df6e1" sha256 "515870b15231530f56fe2164190581748e8799b66ef0fe36ec9da3396f0df6e1"
head "https://github.com/jkbrzt/httpie.git" head "https://github.com/jakubroztocil/httpie.git"
depends_on :python3 depends_on :python3

View File

@ -2,14 +2,14 @@
HTTPie - a CLI, cURL-like tool for humans. HTTPie - a CLI, cURL-like tool for humans.
""" """
__version__ = '1.0.0'
__author__ = 'Jakub Roztocil' __author__ = 'Jakub Roztocil'
__version__ = '0.9.8'
__licence__ = 'BSD' __licence__ = 'BSD'
class ExitStatus: class ExitStatus:
"""Exit status code constants.""" """Program exit code constants."""
OK = 0 SUCCESS = 0
ERROR = 1 ERROR = 1
PLUGIN_ERROR = 7 PLUGIN_ERROR = 7
@ -25,8 +25,8 @@ class ExitStatus:
ERROR_HTTP_5XX = 5 ERROR_HTTP_5XX = 5
EXIT_STATUS_LABELS = dict( EXIT_STATUS_LABELS = {
(value, key) value: key
for key, value in ExitStatus.__dict__.items() for key, value in ExitStatus.__dict__.items()
if key.isupper() if key.isupper()
) }

View File

@ -20,7 +20,9 @@ from httpie.input import (
PRETTY_STDOUT_TTY_ONLY, SessionNameValidator, PRETTY_STDOUT_TTY_ONLY, SessionNameValidator,
readable_file_arg, SSL_VERSION_ARG_MAPPING readable_file_arg, SSL_VERSION_ARG_MAPPING
) )
from httpie.output.formatters.colors import AVAILABLE_STYLES, DEFAULT_STYLE from httpie.output.formatters.colors import (
AVAILABLE_STYLES, DEFAULT_STYLE, AUTO_STYLE
)
from httpie.plugins import plugin_manager from httpie.plugins import plugin_manager
from httpie.plugins.builtin import BuiltinAuthPlugin from httpie.plugins.builtin import BuiltinAuthPlugin
from httpie.sessions import DEFAULT_SESSIONS_DIR from httpie.sessions import DEFAULT_SESSIONS_DIR
@ -46,6 +48,7 @@ class HTTPieHelpFormatter(RawDescriptionHelpFormatter):
parser = HTTPieArgumentParser( parser = HTTPieArgumentParser(
prog='http',
formatter_class=HTTPieHelpFormatter, formatter_class=HTTPieHelpFormatter,
description='%s <http://httpie.org>' % __doc__.strip(), description='%s <http://httpie.org>' % __doc__.strip(),
epilog=dedent(""" epilog=dedent("""
@ -54,7 +57,7 @@ parser = HTTPieArgumentParser(
Suggestions and bug reports are greatly appreciated: Suggestions and bug reports are greatly appreciated:
https://github.com/jkbrzt/httpie/issues https://github.com/jakubroztocil/httpie/issues
"""), """),
) )
@ -209,18 +212,21 @@ output_processing.add_argument(
help=""" help="""
Output coloring style (default is "{default}"). One of: Output coloring style (default is "{default}"). One of:
{available} {available_styles}
For this option to work properly, please make sure that the $TERM The "{auto_style}" style follows your terminal's ANSI color styles.
environment variable is set to "xterm-256color" or similar
For non-{auto_style} styles to work properly, please make sure that the
$TERM environment variable is set to "xterm-256color" or similar
(e.g., via `export TERM=xterm-256color' in your ~/.bashrc). (e.g., via `export TERM=xterm-256color' in your ~/.bashrc).
""".format( """.format(
default=DEFAULT_STYLE, default=DEFAULT_STYLE,
available='\n'.join( available_styles='\n'.join(
'{0}{1}'.format(8 * ' ', line.strip()) '{0}{1}'.format(8 * ' ', line.strip())
for line in wrap(', '.join(sorted(AVAILABLE_STYLES)), 60) for line in wrap(', '.join(sorted(AVAILABLE_STYLES)), 60)
).rstrip(), ).rstrip(),
auto_style=AUTO_STYLE,
) )
) )
@ -544,10 +550,10 @@ ssl.add_argument(
'--verify', '--verify',
default='yes', default='yes',
help=""" help="""
Set to "no" to skip checking the host's SSL certificate. You can also pass Set to "no" (or "false") to skip checking the host's SSL certificate.
the path to a CA_BUNDLE file for private certs. You can also set the Defaults to "yes" ("true"). You can also pass the path to a CA_BUNDLE file
REQUESTS_CA_BUNDLE environment variable. Defaults to "yes". for private certs. (Or you can set the REQUESTS_CA_BUNDLE environment
variable instead.)
""" """
) )
ssl.add_argument( ssl.add_argument(

View File

@ -3,7 +3,7 @@ import sys
import requests import requests
from requests.adapters import HTTPAdapter from requests.adapters import HTTPAdapter
from requests.packages import urllib3 from requests.structures import CaseInsensitiveDict
from httpie import sessions from httpie import sessions
from httpie import __version__ from httpie import __version__
@ -14,8 +14,10 @@ from httpie.utils import repr_dict_nice
try: try:
# https://urllib3.readthedocs.io/en/latest/security.html # https://urllib3.readthedocs.io/en/latest/security.html
# noinspection PyPackageRequirements
import urllib3
urllib3.disable_warnings() urllib3.disable_warnings()
except AttributeError: except (ImportError, AttributeError):
# In some rare cases, the user may have an old version of the requests # In some rare cases, the user may have an old version of the requests
# or urllib3, and there is no method called "disable_warnings." In these # or urllib3, and there is no method called "disable_warnings." In these
# cases, we don't need to call the method. # cases, we don't need to call the method.
@ -97,7 +99,7 @@ def finalize_headers(headers):
value = value.strip() value = value.strip()
if isinstance(value, str): if isinstance(value, str):
# See: https://github.com/jkbrzt/httpie/issues/212 # See: https://github.com/jakubroztocil/httpie/issues/212
value = value.encode('utf8') value = value.encode('utf8')
final_headers[name] = value final_headers[name] = value
@ -105,9 +107,9 @@ def finalize_headers(headers):
def get_default_headers(args): def get_default_headers(args):
default_headers = { default_headers = CaseInsensitiveDict({
'User-Agent': DEFAULT_UA 'User-Agent': DEFAULT_UA
} })
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:
@ -159,12 +161,14 @@ def get_requests_kwargs(args, base_headers=None):
'data': data, 'data': data,
'verify': { 'verify': {
'yes': True, 'yes': True,
'no': False 'true': True,
}.get(args.verify, args.verify), 'no': False,
'false': False,
}.get(args.verify.lower(), args.verify),
'cert': cert, 'cert': cert,
'timeout': args.timeout, 'timeout': args.timeout,
'auth': args.auth, 'auth': args.auth,
'proxies': dict((p.key, p.value) for p in args.proxy), 'proxies': {p.key: p.value for p in args.proxy},
'files': args.files, 'files': args.files,
'allow_redirects': args.follow, 'allow_redirects': args.follow,
'params': args.params, 'params': args.params,

View File

@ -1,12 +1,11 @@
""" """
Python 2.6, 2.7, and 3.x compatibility. Python 2.7, and 3.x compatibility.
""" """
import sys import sys
is_py2 = sys.version_info[0] == 2 is_py2 = sys.version_info[0] == 2
is_py26 = sys.version_info[:2] == (2, 6)
is_py27 = sys.version_info[:2] == (2, 7) is_py27 = sys.version_info[:2] == (2, 7)
is_py3 = sys.version_info[0] == 3 is_py3 = sys.version_info[0] == 3
is_pypy = 'pypy' in sys.version.lower() is_pypy = 'pypy' in sys.version.lower()
@ -38,141 +37,3 @@ try: # pragma: no cover
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
# noinspection PyCompatibility,PyUnresolvedReferences # noinspection PyCompatibility,PyUnresolvedReferences
from urllib2 import urlopen from urllib2 import urlopen
try: # pragma: no cover
from collections import OrderedDict
except ImportError: # pragma: no cover
# Python 2.6 OrderedDict class, needed for headers, parameters, etc .###
# <https://pypi.python.org/pypi/ordereddict/1.1>
# noinspection PyCompatibility,PyUnresolvedReferences
from UserDict import DictMixin
# noinspection PyShadowingBuiltins,PyCompatibility
class OrderedDict(dict, DictMixin):
# Copyright (c) 2009 Raymond Hettinger
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
# noinspection PyMissingConstructor
def __init__(self, *args, **kwds):
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d'
% len(args))
try:
self.__end
except AttributeError:
self.clear()
self.update(*args, **kwds)
def clear(self):
self.__end = end = []
# noinspection PyUnusedLocal
end += [None, end, end] # sentinel node for doubly linked list
self.__map = {} # key --> [key, prev, next]
dict.clear(self)
def __setitem__(self, key, value):
if key not in self:
end = self.__end
curr = end[1]
curr[2] = end[1] = self.__map[key] = [key, curr, end]
dict.__setitem__(self, key, value)
def __delitem__(self, key):
dict.__delitem__(self, key)
key, prev, next = self.__map.pop(key)
prev[2] = next
next[1] = prev
def __iter__(self):
end = self.__end
curr = end[2]
while curr is not end:
yield curr[0]
curr = curr[2]
def __reversed__(self):
end = self.__end
curr = end[1]
while curr is not end:
yield curr[0]
curr = curr[1]
def popitem(self, last=True):
if not self:
raise KeyError('dictionary is empty')
if last:
# noinspection PyUnresolvedReferences
key = reversed(self).next()
else:
key = iter(self).next()
value = self.pop(key)
return key, value
def __reduce__(self):
items = [[k, self[k]] for k in self]
tmp = self.__map, self.__end
del self.__map, self.__end
inst_dict = vars(self).copy()
self.__map, self.__end = tmp
if inst_dict:
return self.__class__, (items,), inst_dict
return self.__class__, (items,)
def keys(self):
return list(self)
setdefault = DictMixin.setdefault
update = DictMixin.update
pop = DictMixin.pop
values = DictMixin.values
items = DictMixin.items
iterkeys = DictMixin.iterkeys
itervalues = DictMixin.itervalues
iteritems = DictMixin.iteritems
def __repr__(self):
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, self.items())
def copy(self):
return self.__class__(self)
# noinspection PyMethodOverriding
@classmethod
def fromkeys(cls, iterable, value=None):
d = cls()
for key in iterable:
d[key] = value
return d
def __eq__(self, other):
if isinstance(other, OrderedDict):
if len(self) != len(other):
return False
for p, q in zip(self.items(), other.items()):
if p != q:
return False
return True
return dict.__eq__(self, other)
def __ne__(self, other):
return not self == other

View File

@ -80,7 +80,7 @@ class BaseConfigDict(dict):
class Config(BaseConfigDict): class Config(BaseConfigDict):
name = 'config' name = 'config'
helpurl = 'https://httpie.org/docs#config' helpurl = 'https://httpie.org/doc#config'
about = 'HTTPie configuration file' about = 'HTTPie configuration file'
DEFAULTS = { DEFAULTS = {
@ -104,7 +104,7 @@ class Config(BaseConfigDict):
try: try:
implicit_content_type = self.pop('implicit_content_type') implicit_content_type = self.pop('implicit_content_type')
except KeyError: except KeyError:
pass self.save()
else: else:
if implicit_content_type == 'form': if implicit_content_type == 'form':
self['default_options'].insert(0, '--form') self['default_options'].insert(0, '--form')

View File

@ -43,7 +43,7 @@ def get_exit_status(http_status, follow=False):
# Server Error # Server Error
return ExitStatus.ERROR_HTTP_5XX return ExitStatus.ERROR_HTTP_5XX
else: else:
return ExitStatus.OK return ExitStatus.SUCCESS
def print_debug_info(env): def print_debug_info(env):
@ -61,7 +61,7 @@ def print_debug_info(env):
def decode_args(args, stdin_encoding): def decode_args(args, stdin_encoding):
""" """
Convert all bytes ags to str Convert all bytes args to str
by decoding them using stdin encoding. by decoding them using stdin encoding.
""" """
@ -82,7 +82,7 @@ def program(args, env, log_error):
:return: status code :return: status code
""" """
exit_status = ExitStatus.OK exit_status = ExitStatus.SUCCESS
downloader = None downloader = None
show_traceback = args.debug or args.traceback show_traceback = args.debug or args.traceback
@ -109,7 +109,7 @@ def program(args, env, log_error):
http_status=response.status_code, http_status=response.status_code,
follow=args.follow follow=args.follow
) )
if not env.stdout_isatty and exit_status != ExitStatus.OK: if not env.stdout_isatty and exit_status != ExitStatus.SUCCESS:
log_error( log_error(
'HTTP %s %s', response.raw.status, response.raw.reason, 'HTTP %s %s', response.raw.status, response.raw.reason,
level='warning' level='warning'
@ -143,7 +143,7 @@ def program(args, env, log_error):
else: else:
raise raise
if downloader and exit_status == ExitStatus.OK: if downloader and exit_status == ExitStatus.SUCCESS:
# Last response body download. # Last response body download.
download_stream, download_to = downloader.start(final_response) download_stream, download_to = downloader.start(final_response)
write_stream( write_stream(
@ -164,8 +164,8 @@ def program(args, env, log_error):
if downloader and not downloader.finished: if downloader and not downloader.finished:
downloader.failed() downloader.failed()
if (not isinstance(args, list) and args.output_file and if (not isinstance(args, list) and args.output_file
args.output_file_specified): and args.output_file_specified):
args.output_file.close() args.output_file.close()
@ -202,9 +202,9 @@ def main(args=sys.argv[1:], env=Environment(), custom_log_error=None):
if include_debug_info: if include_debug_info:
print_debug_info(env) print_debug_info(env)
if args == ['--debug']: if args == ['--debug']:
return ExitStatus.OK return ExitStatus.SUCCESS
exit_status = ExitStatus.OK exit_status = ExitStatus.SUCCESS
try: try:
parsed_args = parser.parse_args(args=args, env=env) parsed_args = parser.parse_args(args=args, env=env)
@ -214,7 +214,7 @@ def main(args=sys.argv[1:], env=Environment(), custom_log_error=None):
raise raise
exit_status = ExitStatus.ERROR_CTRL_C exit_status = ExitStatus.ERROR_CTRL_C
except SystemExit as e: except SystemExit as e:
if e.code != ExitStatus.OK: if e.code != ExitStatus.SUCCESS:
env.stderr.write('\n') env.stderr.write('\n')
if include_traceback: if include_traceback:
raise raise
@ -232,7 +232,7 @@ def main(args=sys.argv[1:], env=Environment(), custom_log_error=None):
raise raise
exit_status = ExitStatus.ERROR_CTRL_C exit_status = ExitStatus.ERROR_CTRL_C
except SystemExit as e: except SystemExit as e:
if e.code != ExitStatus.OK: if e.code != ExitStatus.SUCCESS:
env.stderr.write('\n') env.stderr.write('\n')
if include_traceback: if include_traceback:
raise raise

View File

@ -54,8 +54,8 @@ def parse_content_range(content_range, resumed_from):
raise ContentRangeError('Missing Content-Range') raise ContentRangeError('Missing Content-Range')
pattern = ( pattern = (
'^bytes (?P<first_byte_pos>\d+)-(?P<last_byte_pos>\d+)' r'^bytes (?P<first_byte_pos>\d+)-(?P<last_byte_pos>\d+)'
'/(\*|(?P<instance_length>\d+))$' r'/(\*|(?P<instance_length>\d+))$'
) )
match = re.match(pattern, content_range) match = re.match(pattern, content_range)
@ -78,15 +78,15 @@ def parse_content_range(content_range, resumed_from):
# last-byte-pos value, is invalid. The recipient of an invalid # last-byte-pos value, is invalid. The recipient of an invalid
# byte-content-range- spec MUST ignore it and any content # byte-content-range- spec MUST ignore it and any content
# transferred along with it." # transferred along with it."
if (first_byte_pos >= last_byte_pos or if (first_byte_pos >= last_byte_pos
(instance_length is not None and or (instance_length is not None
instance_length <= last_byte_pos)): and instance_length <= last_byte_pos)):
raise ContentRangeError( raise ContentRangeError(
'Invalid Content-Range returned: %r' % content_range) 'Invalid Content-Range returned: %r' % content_range)
if (first_byte_pos != resumed_from or if (first_byte_pos != resumed_from
(instance_length is not None and or (instance_length is not None
last_byte_pos + 1 != instance_length)): and last_byte_pos + 1 != instance_length)):
# Not what we asked for. # Not what we asked for.
raise ContentRangeError( raise ContentRangeError(
'Unexpected Content-Range returned (%r)' 'Unexpected Content-Range returned (%r)'
@ -105,7 +105,7 @@ def filename_from_content_disposition(content_disposition):
:return: the filename if present and valid, otherwise `None` :return: the filename if present and valid, otherwise `None`
""" """
# attachment; filename=jkbrzt-httpie-0.4.1-20-g40bd8f6.tar.gz # attachment; filename=jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz
msg = Message('Content-Disposition: %s' % content_disposition) msg = Message('Content-Disposition: %s' % content_disposition)
filename = msg.get_filename() filename = msg.get_filename()
@ -238,7 +238,7 @@ class Downloader(object):
assert not self.status.time_started assert not self.status.time_started
# FIXME: some servers still might sent Content-Encoding: gzip # FIXME: some servers still might sent Content-Encoding: gzip
# <https://github.com/jkbrzt/httpie/issues/423> # <https://github.com/jakubroztocil/httpie/issues/423>
try: try:
total_size = int(response.headers['Content-Length']) total_size = int(response.headers['Content-Length'])
except (KeyError, ValueError, TypeError): except (KeyError, ValueError, TypeError):
@ -308,9 +308,9 @@ class Downloader(object):
@property @property
def interrupted(self): def interrupted(self):
return ( return (
self.finished and self.finished
self.status.total_size and and self.status.total_size
self.status.total_size != self.status.downloaded and self.status.total_size != self.status.downloaded
) )
def chunk_downloaded(self, chunk): def chunk_downloaded(self, chunk):
@ -399,8 +399,8 @@ class ProgressReporterThread(threading.Thread):
if now - self._prev_time >= self._update_interval: if now - self._prev_time >= self._update_interval:
downloaded = self.status.downloaded downloaded = self.status.downloaded
try: try:
speed = ((downloaded - self._prev_bytes) / speed = ((downloaded - self._prev_bytes)
(now - self._prev_time)) / (now - self._prev_time))
except ZeroDivisionError: except ZeroDivisionError:
speed = 0 speed = 0
@ -434,11 +434,11 @@ class ProgressReporterThread(threading.Thread):
self._prev_bytes = downloaded self._prev_bytes = downloaded
self.output.write( self.output.write(
CLEAR_LINE + CLEAR_LINE
' ' + + ' '
SPINNER[self._spinner_pos] + + SPINNER[self._spinner_pos]
' ' + + ' '
self._status_line + self._status_line
) )
self.output.flush() self.output.flush()
@ -463,8 +463,8 @@ class ProgressReporterThread(threading.Thread):
self.output.write(SUMMARY.format( self.output.write(SUMMARY.format(
downloaded=humanize_bytes(actually_downloaded), downloaded=humanize_bytes(actually_downloaded),
total=(self.status.total_size and total=(self.status.total_size
humanize_bytes(self.status.total_size)), and humanize_bytes(self.status.total_size)),
speed=humanize_bytes(speed), speed=humanize_bytes(speed),
time=time_taken, time=time_taken,
)) ))

View File

@ -9,16 +9,16 @@ import errno
import mimetypes import mimetypes
import getpass import getpass
from io import BytesIO from io import BytesIO
from collections import namedtuple, Iterable from collections import namedtuple, Iterable, OrderedDict
# noinspection PyCompatibility # noinspection PyCompatibility
from argparse import ArgumentParser, ArgumentTypeError, ArgumentError from argparse import ArgumentParser, ArgumentTypeError, ArgumentError
# TODO: Use MultiDict for headers once added to `requests`. # TODO: Use MultiDict for headers once added to `requests`.
# https://github.com/jkbrzt/httpie/issues/130 # https://github.com/jakubroztocil/httpie/issues/130
from httpie.plugins import plugin_manager from httpie.plugins import plugin_manager
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from httpie.compat import OrderedDict, urlsplit, str, is_pypy, is_py27 from httpie.compat import urlsplit, str, is_pypy, is_py27
from httpie.sessions import VALID_SESSION_NAME_PATTERN from httpie.sessions import VALID_SESSION_NAME_PATTERN
from httpie.utils import load_json_preserve_order from httpie.utils import load_json_preserve_order
@ -111,12 +111,13 @@ SSL_VERSION_ARG_MAPPING = {
'tls1': 'PROTOCOL_TLSv1', 'tls1': 'PROTOCOL_TLSv1',
'tls1.1': 'PROTOCOL_TLSv1_1', 'tls1.1': 'PROTOCOL_TLSv1_1',
'tls1.2': 'PROTOCOL_TLSv1_2', 'tls1.2': 'PROTOCOL_TLSv1_2',
'tls1.3': 'PROTOCOL_TLSv1_3',
} }
SSL_VERSION_ARG_MAPPING = dict( SSL_VERSION_ARG_MAPPING = {
(cli_arg, getattr(ssl, ssl_constant)) cli_arg: getattr(ssl, ssl_constant)
for cli_arg, ssl_constant in SSL_VERSION_ARG_MAPPING.items() for cli_arg, ssl_constant in SSL_VERSION_ARG_MAPPING.items()
if hasattr(ssl, ssl_constant) if hasattr(ssl, ssl_constant)
) }
class HTTPieArgumentParser(ArgumentParser): class HTTPieArgumentParser(ArgumentParser):
@ -254,8 +255,8 @@ class HTTPieArgumentParser(ArgumentParser):
else: else:
credentials = parse_auth(self.args.auth) credentials = parse_auth(self.args.auth)
if (not credentials.has_password() and if (not credentials.has_password()
plugin.prompt_password): and plugin.prompt_password):
if self.args.ignore_stdin: if self.args.ignore_stdin:
# Non-tty stdin read by now # Non-tty stdin read by now
self.error( self.error(
@ -302,7 +303,8 @@ class HTTPieArgumentParser(ArgumentParser):
""" """
if self.args.data: if self.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. Pass '
'--ignore-stdin to let key/value take priority.')
self.args.data = getattr(fd, 'buffer', fd).read() self.args.data = getattr(fd, 'buffer', fd).read()
def _guess_method(self): def _guess_method(self):
@ -337,10 +339,11 @@ class HTTPieArgumentParser(ArgumentParser):
self.args.url = self.args.method self.args.url = self.args.method
# Infer the method # Infer the method
has_data = ( has_data = (
(not self.args.ignore_stdin and (not self.args.ignore_stdin and not self.env.stdin_isatty)
not self.env.stdin_isatty) or or any(
any(item.sep in SEP_GROUP_DATA_ITEMS item.sep in SEP_GROUP_DATA_ITEMS
for item in self.args.items) for item in self.args.items
)
) )
self.args.method = HTTP_POST if has_data else HTTP_GET self.args.method = HTTP_POST if has_data else HTTP_GET
@ -425,8 +428,8 @@ class HTTPieArgumentParser(ArgumentParser):
if self.args.prettify == PRETTY_STDOUT_TTY_ONLY: if self.args.prettify == PRETTY_STDOUT_TTY_ONLY:
self.args.prettify = PRETTY_MAP[ self.args.prettify = PRETTY_MAP[
'all' if self.env.stdout_isatty else 'none'] 'all' if self.env.stdout_isatty else 'none']
elif (self.args.prettify and self.env.is_windows and elif (self.args.prettify and self.env.is_windows
self.args.output_file): and self.args.output_file):
self.error('Only terminal output can be colorized on Windows.') self.error('Only terminal output can be colorized on Windows.')
else: else:
# noinspection PyTypeChecker # noinspection PyTypeChecker
@ -468,8 +471,8 @@ class SessionNameValidator(object):
def __call__(self, value): def __call__(self, value):
# Session name can be a path or just a name. # Session name can be a path or just a name.
if (os.path.sep not in value and if (os.path.sep not in value
not VALID_SESSION_NAME_PATTERN.search(value)): and not VALID_SESSION_NAME_PATTERN.search(value)):
raise ArgumentError(None, self.error_message) raise ArgumentError(None, self.error_message)
return value return value
@ -504,7 +507,7 @@ class KeyValueArgType(object):
"""Represents an escaped character.""" """Represents an escaped character."""
def tokenize(string): def tokenize(string):
"""Tokenize `string`. There are only two token types - strings r"""Tokenize `string`. There are only two token types - strings
and escaped characters: and escaped characters:
tokenize(r'foo\=bar\\baz') tokenize(r'foo\=bar\\baz')

View File

@ -9,21 +9,25 @@ import pygments.style
from pygments.formatters.terminal import TerminalFormatter from pygments.formatters.terminal import TerminalFormatter
from pygments.formatters.terminal256 import Terminal256Formatter from pygments.formatters.terminal256 import Terminal256Formatter
from pygments.lexers.special import TextLexer from pygments.lexers.special import TextLexer
from pygments.lexers.text import HttpLexer as PygmentsHttpLexer
from pygments.util import ClassNotFound from pygments.util import ClassNotFound
from httpie.compat import is_windows from httpie.compat import is_windows
from httpie.plugins import FormatterPlugin from httpie.plugins import FormatterPlugin
AVAILABLE_STYLES = set(pygments.styles.STYLE_MAP.keys()) AUTO_STYLE = 'auto' # Follows terminal ANSI color styles
AVAILABLE_STYLES.add('solarized') DEFAULT_STYLE = AUTO_STYLE
SOLARIZED_STYLE = 'solarized' # Bundled here
if is_windows: if is_windows:
# Colors on Windows via colorama don't look that # Colors on Windows via colorama don't look that
# great and fruity seems to give the best result there # great and fruity seems to give the best result there.
DEFAULT_STYLE = 'fruity' DEFAULT_STYLE = 'fruity'
else:
DEFAULT_STYLE = 'solarized'
AVAILABLE_STYLES = set(pygments.styles.get_all_styles())
AVAILABLE_STYLES.add(SOLARIZED_STYLE)
AVAILABLE_STYLES.add(AUTO_STYLE)
class ColorFormatter(FormatterPlugin): class ColorFormatter(FormatterPlugin):
@ -39,40 +43,56 @@ class ColorFormatter(FormatterPlugin):
def __init__(self, env, explicit_json=False, def __init__(self, env, explicit_json=False,
color_scheme=DEFAULT_STYLE, **kwargs): color_scheme=DEFAULT_STYLE, **kwargs):
super(ColorFormatter, self).__init__(**kwargs) super(ColorFormatter, self).__init__(**kwargs)
if not env.colors: if not env.colors:
self.enabled = False self.enabled = False
return return
# --json, -j use_auto_style = color_scheme == AUTO_STYLE
self.explicit_json = explicit_json has_256_colors = env.colors == 256
if use_auto_style or not has_256_colors:
try: http_lexer = PygmentsHttpLexer()
style_class = pygments.styles.get_style_by_name(color_scheme) formatter = TerminalFormatter()
except ClassNotFound:
style_class = Solarized256Style
if env.colors == 256:
fmt_class = Terminal256Formatter
else: else:
fmt_class = TerminalFormatter http_lexer = SimplifiedHTTPLexer()
self.formatter = fmt_class(style=style_class) formatter = Terminal256Formatter(
style=self.get_style_class(color_scheme)
)
self.explicit_json = explicit_json # --json
self.formatter = formatter
self.http_lexer = http_lexer
def format_headers(self, headers): def format_headers(self, headers):
return pygments.highlight(headers, HTTPLexer(), self.formatter).strip() return pygments.highlight(
code=headers,
lexer=self.http_lexer,
formatter=self.formatter,
).strip()
def format_body(self, body, mime): def format_body(self, body, mime):
lexer = self.get_lexer(mime, body) lexer = self.get_lexer_for_body(mime, body)
if lexer: if lexer:
body = pygments.highlight(body, lexer, self.formatter) body = pygments.highlight(
code=body,
lexer=lexer,
formatter=self.formatter,
)
return body.strip() return body.strip()
def get_lexer(self, mime, body): def get_lexer_for_body(self, mime, body):
return get_lexer( return get_lexer(
mime=mime, mime=mime,
explicit_json=self.explicit_json, explicit_json=self.explicit_json,
body=body, body=body,
) )
def get_style_class(self, color_scheme):
try:
return pygments.styles.get_style_by_name(color_scheme)
except ClassNotFound:
return Solarized256Style
def get_lexer(mime, explicit_json=False, body=''): def get_lexer(mime, explicit_json=False, body=''):
@ -121,7 +141,7 @@ def get_lexer(mime, explicit_json=False, body=''):
return lexer return lexer
class HTTPLexer(pygments.lexer.RegexLexer): class SimplifiedHTTPLexer(pygments.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

View File

@ -15,8 +15,8 @@ class JSONFormatter(FormatterPlugin):
'javascript', 'javascript',
'text', 'text',
] ]
if (self.kwargs['explicit_json'] or if (self.kwargs['explicit_json']
any(token in mime for token in maybe_json)): or any(token in mime for token in maybe_json)):
try: try:
obj = json.loads(body) obj = json.loads(body)
except ValueError: except ValueError:

View File

@ -15,7 +15,7 @@ class AuthPlugin(BasePlugin):
""" """
Base auth plugin class. Base auth plugin class.
See <https://github.com/jkbrzt/httpie-ntlm> for an example auth plugin. See <https://github.com/httpie/httpie-ntlm> for an example auth plugin.
See also `test_auth_plugins.py` See also `test_auth_plugins.py`

View File

@ -17,7 +17,7 @@ class HTTPBasicAuth(requests.auth.HTTPBasicAuth):
""" """
Override username/password serialization to allow unicode. Override username/password serialization to allow unicode.
See https://github.com/jkbrzt/httpie/issues/212 See https://github.com/jakubroztocil/httpie/issues/212
""" """
r.headers['Authorization'] = type(self).make_header( r.headers['Authorization'] = type(self).make_header(

View File

@ -39,8 +39,7 @@ class PluginManager(object):
return [plugin for plugin in self if issubclass(plugin, AuthPlugin)] return [plugin for plugin in self if issubclass(plugin, AuthPlugin)]
def get_auth_plugin_mapping(self): def get_auth_plugin_mapping(self):
return dict((plugin.auth_type, plugin) return {plugin.auth_type: plugin for plugin in self.get_auth_plugins()}
for plugin in self.get_auth_plugins())
def get_auth_plugin(self, auth_type): def get_auth_plugin(self, auth_type):
return self.get_auth_plugin_mapping()[auth_type] return self.get_auth_plugin_mapping()[auth_type]

View File

@ -30,8 +30,8 @@ def get_response(requests_session, session_name,
if os.path.sep in session_name: if os.path.sep in session_name:
path = os.path.expanduser(session_name) path = os.path.expanduser(session_name)
else: else:
hostname = (args.headers.get('Host', None) or hostname = (args.headers.get('Host', None)
urlsplit(args.url).netloc.split('@')[-1]) or urlsplit(args.url).netloc.split('@')[-1])
if not hostname: if not hostname:
# HACK/FIXME: httpie-unixsocket's URLs have no hostname. # HACK/FIXME: httpie-unixsocket's URLs have no hostname.
hostname = 'localhost' hostname = 'localhost'
@ -74,7 +74,7 @@ def get_response(requests_session, session_name,
class Session(BaseConfigDict): class Session(BaseConfigDict):
helpurl = 'https://httpie.org/docs#sessions' helpurl = 'https://httpie.org/doc#sessions'
about = 'HTTPie session file' about = 'HTTPie session file'
def __init__(self, path, *args, **kwargs): def __init__(self, path, *args, **kwargs):
@ -136,10 +136,10 @@ class Session(BaseConfigDict):
stored_attrs = ['value', 'path', 'secure', 'expires'] stored_attrs = ['value', 'path', 'secure', 'expires']
self['cookies'] = {} self['cookies'] = {}
for cookie in jar: for cookie in jar:
self['cookies'][cookie.name] = dict( self['cookies'][cookie.name] = {
(attname, getattr(cookie, attname)) attname: getattr(cookie, attname)
for attname in stored_attrs for attname in stored_attrs
) }
@property @property
def auth(self): def auth(self):

View File

@ -1,12 +1,9 @@
from __future__ import division from __future__ import division
import json import json
from collections import OrderedDict
from httpie.compat import is_py26, OrderedDict
def load_json_preserve_order(s): def load_json_preserve_order(s):
if is_py26:
return json.loads(s)
return json.loads(s, object_pairs_hook=OrderedDict) return json.loads(s, object_pairs_hook=OrderedDict)

View File

@ -1,2 +0,0 @@
[pytest]
norecursedirs = tests/fixtures

View File

@ -5,3 +5,4 @@ pytest-cov
pytest-httpbin>=0.0.6 pytest-httpbin>=0.0.6
docutils docutils
wheel wheel
pycodestyle

View File

@ -1,2 +1,19 @@
[wheel] [wheel]
universal = 1 universal = 1
[tool:pytest]
# <https://docs.pytest.org/en/latest/customize.html>
norecursedirs = tests/fixtures
[pycodestyle]
# <http://pycodestyle.pycqa.org/en/latest/intro.html#configuration>
exclude = .git,.idea,__pycache__,build,dist,.tox,.pytest_cache,*.egg-info
# <http://pycodestyle.pycqa.org/en/latest/intro.html#error-codes>
# E241 - multiple spaces after ,
# E501 - line too long
# W503 - line break before binary operator
ignore = E241,E501,W503

View File

@ -35,7 +35,7 @@ tests_require = [
install_requires = [ install_requires = [
'requests>=2.11.0', 'requests>=2.18.4',
'Pygments>=2.1.3' 'Pygments>=2.1.3'
] ]
@ -58,9 +58,7 @@ if 'bdist_wheel' not in sys.argv:
# bdist_wheel # bdist_wheel
extras_require = { extras_require = {
# http://wheel.readthedocs.io/en/latest/#defining-conditional-dependencies # http://wheel.readthedocs.io/en/latest/#defining-conditional-dependencies
':python_version == "2.6"' 'python_version == "3.0" or python_version == "3.1"': ['argparse>=1.2.1'],
' or python_version == "3.0"'
' or python_version == "3.1" ': ['argparse>=1.2.1'],
':sys_platform == "win32"': ['colorama>=0.2.4'], ':sys_platform == "win32"': ['colorama>=0.2.4'],
} }
@ -76,7 +74,7 @@ setup(
description=httpie.__doc__.strip(), description=httpie.__doc__.strip(),
long_description=long_description(), long_description=long_description(),
url='http://httpie.org/', url='http://httpie.org/',
download_url='https://github.com/jkbrzt/httpie', download_url='https://github.com/jakubroztocil/httpie',
author=httpie.__author__, author=httpie.__author__,
author_email='jakub@roztocil.co', author_email='jakub@roztocil.co',
license=httpie.__licence__, license=httpie.__licence__,
@ -93,14 +91,14 @@ setup(
classifiers=[ classifiers=[
'Development Status :: 5 - Production/Stable', 'Development Status :: 5 - Production/Stable',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.1', 'Programming Language :: Python :: 3.1',
'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Environment :: Console', 'Environment :: Console',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'Intended Audience :: System Administrators', 'Intended Audience :: System Administrators',

View File

@ -5,4 +5,4 @@ HTTPie Test Suite
Please see `CONTRIBUTING`_. Please see `CONTRIBUTING`_.
.. _CONTRIBUTING: https://github.com/jkbrzt/httpie/blob/master/CONTRIBUTING.rst .. _CONTRIBUTING: https://github.com/jakubroztocil/httpie/blob/master/CONTRIBUTING.rst

View File

@ -1,14 +1,24 @@
import pytest import pytest
from pytest_httpbin.plugin import httpbin_ca_bundle from pytest_httpbin import certs
# Make httpbin's CA trusted by default @pytest.fixture(scope='function', autouse=True)
pytest.fixture(autouse=True)(httpbin_ca_bundle) def httpbin_add_ca_bundle(monkeypatch):
"""
Make pytest-httpbin's CA trusted by default.
(Same as `httpbin_ca_bundle`, just auto-used.).
"""
monkeypatch.setenv('REQUESTS_CA_BUNDLE', certs.where())
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def httpbin_secure_untrusted(monkeypatch, httpbin_secure): def httpbin_secure_untrusted(monkeypatch, httpbin_secure):
"""Like the `httpbin_secure` fixture, but without the """
make-CA-trusted-by-default""" Like the `httpbin_secure` fixture, but without the
make-CA-trusted-by-default.
"""
monkeypatch.delenv('REQUESTS_CA_BUNDLE') monkeypatch.delenv('REQUESTS_CA_BUNDLE')
return httpbin_secure return httpbin_secure

View File

@ -2,7 +2,7 @@
import mock import mock
import pytest import pytest
from utils import http, add_auth, HTTP_OK, TestEnvironment from utils import http, add_auth, HTTP_OK, MockEnvironment
import httpie.input import httpie.input
import httpie.cli import httpie.cli
@ -55,10 +55,10 @@ def test_credentials_in_url_auth_flag_has_priority(httpbin_both):
]) ])
def test_only_username_in_url(url): def test_only_username_in_url(url):
""" """
https://github.com/jkbrzt/httpie/issues/242 https://github.com/jakubroztocil/httpie/issues/242
""" """
args = httpie.cli.parser.parse_args(args=[url], env=TestEnvironment()) args = httpie.cli.parser.parse_args(args=[url], env=MockEnvironment())
assert args.auth assert args.auth
assert args.auth.username == 'username' assert args.auth.username == 'username'
assert args.auth.password == '' assert args.auth.password == ''

View File

@ -2,14 +2,14 @@
from fixtures import BIN_FILE_PATH, BIN_FILE_CONTENT, BIN_FILE_PATH_ARG from fixtures import BIN_FILE_PATH, BIN_FILE_CONTENT, BIN_FILE_PATH_ARG
from httpie.compat import urlopen from httpie.compat import urlopen
from httpie.output.streams import BINARY_SUPPRESSED_NOTICE from httpie.output.streams import BINARY_SUPPRESSED_NOTICE
from utils import TestEnvironment, http from utils import MockEnvironment, http
class TestBinaryRequestData: class TestBinaryRequestData:
def test_binary_stdin(self, httpbin): def test_binary_stdin(self, httpbin):
with open(BIN_FILE_PATH, 'rb') as stdin: with open(BIN_FILE_PATH, 'rb') as stdin:
env = TestEnvironment( env = MockEnvironment(
stdin=stdin, stdin=stdin,
stdin_isatty=False, stdin_isatty=False,
stdout_isatty=False stdout_isatty=False
@ -18,13 +18,13 @@ class TestBinaryRequestData:
assert r == BIN_FILE_CONTENT assert r == BIN_FILE_CONTENT
def test_binary_file_path(self, httpbin): def test_binary_file_path(self, httpbin):
env = TestEnvironment(stdin_isatty=True, stdout_isatty=False) env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)
r = http('--print=B', 'POST', httpbin.url + '/post', r = http('--print=B', 'POST', httpbin.url + '/post',
'@' + BIN_FILE_PATH_ARG, env=env, ) '@' + BIN_FILE_PATH_ARG, env=env, )
assert r == BIN_FILE_CONTENT assert r == BIN_FILE_CONTENT
def test_binary_file_form(self, httpbin): def test_binary_file_form(self, httpbin):
env = TestEnvironment(stdin_isatty=True, stdout_isatty=False) env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)
r = http('--print=B', '--form', 'POST', httpbin.url + '/post', r = http('--print=B', '--form', 'POST', httpbin.url + '/post',
'test@' + BIN_FILE_PATH_ARG, env=env) 'test@' + BIN_FILE_PATH_ARG, env=env)
assert bytes(BIN_FILE_CONTENT) in bytes(r) assert bytes(BIN_FILE_CONTENT) in bytes(r)
@ -44,12 +44,12 @@ class TestBinaryResponseData:
assert BINARY_SUPPRESSED_NOTICE.decode() in r assert BINARY_SUPPRESSED_NOTICE.decode() in r
def test_binary_suppresses_when_not_terminal_but_pretty(self): def test_binary_suppresses_when_not_terminal_but_pretty(self):
env = TestEnvironment(stdin_isatty=True, stdout_isatty=False) env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)
r = http('--pretty=all', 'GET', self.url, r = http('--pretty=all', 'GET', self.url,
env=env) env=env)
assert BINARY_SUPPRESSED_NOTICE.decode() in r assert BINARY_SUPPRESSED_NOTICE.decode() in r
def test_binary_included_and_correct_when_suitable(self): def test_binary_included_and_correct_when_suitable(self):
env = TestEnvironment(stdin_isatty=True, stdout_isatty=False) env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)
r = http('GET', self.url, env=env) r = http('GET', self.url, env=env)
assert r == self.bindata assert r == self.bindata

View File

@ -10,7 +10,7 @@ from httpie import input
from httpie.input import KeyValue, KeyValueArgType, DataDict from httpie.input import KeyValue, KeyValueArgType, DataDict
from httpie import ExitStatus from httpie import ExitStatus
from httpie.cli import parser from httpie.cli import parser
from utils import TestEnvironment, http, HTTP_OK from utils import MockEnvironment, http, HTTP_OK
from fixtures import ( from fixtures import (
FILE_PATH_ARG, JSON_FILE_PATH_ARG, FILE_PATH_ARG, JSON_FILE_PATH_ARG,
JSON_FILE_CONTENT, FILE_CONTENT, FILE_PATH JSON_FILE_CONTENT, FILE_CONTENT, FILE_PATH
@ -49,9 +49,9 @@ class TestItemParsing:
assert 'bar@baz' in items.files assert 'bar@baz' in items.files
@pytest.mark.parametrize(('string', 'key', 'sep', 'value'), [ @pytest.mark.parametrize(('string', 'key', 'sep', 'value'), [
('path=c:\windows', 'path', '=', 'c:\windows'), ('path=c:\\windows', 'path', '=', 'c:\\windows'),
('path=c:\windows\\', 'path', '=', 'c:\windows\\'), ('path=c:\\windows\\', 'path', '=', 'c:\\windows\\'),
('path\==c:\windows', 'path=', '=', 'c:\windows'), ('path\\==c:\\windows', 'path=', '=', 'c:\\windows'),
]) ])
def test_backslash_before_non_special_character_does_not_escape( def test_backslash_before_non_special_character_does_not_escape(
self, string, key, sep, value): self, string, key, sep, value):
@ -161,44 +161,44 @@ class TestQuerystring:
class TestLocalhostShorthand: class TestLocalhostShorthand:
def test_expand_localhost_shorthand(self): def test_expand_localhost_shorthand(self):
args = parser.parse_args(args=[':'], env=TestEnvironment()) args = parser.parse_args(args=[':'], env=MockEnvironment())
assert args.url == 'http://localhost' assert args.url == 'http://localhost'
def test_expand_localhost_shorthand_with_slash(self): def test_expand_localhost_shorthand_with_slash(self):
args = parser.parse_args(args=[':/'], env=TestEnvironment()) args = parser.parse_args(args=[':/'], env=MockEnvironment())
assert args.url == 'http://localhost/' assert args.url == 'http://localhost/'
def test_expand_localhost_shorthand_with_port(self): def test_expand_localhost_shorthand_with_port(self):
args = parser.parse_args(args=[':3000'], env=TestEnvironment()) args = parser.parse_args(args=[':3000'], env=MockEnvironment())
assert args.url == 'http://localhost:3000' assert args.url == 'http://localhost:3000'
def test_expand_localhost_shorthand_with_path(self): def test_expand_localhost_shorthand_with_path(self):
args = parser.parse_args(args=[':/path'], env=TestEnvironment()) args = parser.parse_args(args=[':/path'], env=MockEnvironment())
assert args.url == 'http://localhost/path' assert args.url == 'http://localhost/path'
def test_expand_localhost_shorthand_with_port_and_slash(self): def test_expand_localhost_shorthand_with_port_and_slash(self):
args = parser.parse_args(args=[':3000/'], env=TestEnvironment()) args = parser.parse_args(args=[':3000/'], env=MockEnvironment())
assert args.url == 'http://localhost:3000/' assert args.url == 'http://localhost:3000/'
def test_expand_localhost_shorthand_with_port_and_path(self): def test_expand_localhost_shorthand_with_port_and_path(self):
args = parser.parse_args(args=[':3000/path'], env=TestEnvironment()) args = parser.parse_args(args=[':3000/path'], env=MockEnvironment())
assert args.url == 'http://localhost:3000/path' assert args.url == 'http://localhost:3000/path'
def test_dont_expand_shorthand_ipv6_as_shorthand(self): def test_dont_expand_shorthand_ipv6_as_shorthand(self):
args = parser.parse_args(args=['::1'], env=TestEnvironment()) args = parser.parse_args(args=['::1'], env=MockEnvironment())
assert args.url == 'http://::1' assert args.url == 'http://::1'
def test_dont_expand_longer_ipv6_as_shorthand(self): def test_dont_expand_longer_ipv6_as_shorthand(self):
args = parser.parse_args( args = parser.parse_args(
args=['::ffff:c000:0280'], args=['::ffff:c000:0280'],
env=TestEnvironment() env=MockEnvironment()
) )
assert args.url == 'http://::ffff:c000:0280' assert args.url == 'http://::ffff:c000:0280'
def test_dont_expand_full_ipv6_as_shorthand(self): def test_dont_expand_full_ipv6_as_shorthand(self):
args = parser.parse_args( args = parser.parse_args(
args=['0000:0000:0000:0000:0000:0000:0000:0001'], args=['0000:0000:0000:0000:0000:0000:0000:0001'],
env=TestEnvironment() env=MockEnvironment()
) )
assert args.url == 'http://0000:0000:0000:0000:0000:0000:0000:0001' assert args.url == 'http://0000:0000:0000:0000:0000:0000:0000:0001'
@ -215,7 +215,7 @@ class TestArgumentParser:
self.parser.args.items = [] self.parser.args.items = []
self.parser.args.ignore_stdin = False self.parser.args.ignore_stdin = False
self.parser.env = TestEnvironment() self.parser.env = MockEnvironment()
self.parser._guess_method() self.parser._guess_method()
@ -229,7 +229,7 @@ class TestArgumentParser:
self.parser.args.url = 'http://example.com/' self.parser.args.url = 'http://example.com/'
self.parser.args.items = [] self.parser.args.items = []
self.parser.args.ignore_stdin = False self.parser.args.ignore_stdin = False
self.parser.env = TestEnvironment() self.parser.env = MockEnvironment()
self.parser._guess_method() self.parser._guess_method()
@ -243,7 +243,7 @@ class TestArgumentParser:
self.parser.args.url = 'data=field' self.parser.args.url = 'data=field'
self.parser.args.items = [] self.parser.args.items = []
self.parser.args.ignore_stdin = False self.parser.args.ignore_stdin = False
self.parser.env = TestEnvironment() self.parser.env = MockEnvironment()
self.parser._guess_method() self.parser._guess_method()
assert self.parser.args.method == 'POST' assert self.parser.args.method == 'POST'
@ -262,7 +262,7 @@ class TestArgumentParser:
self.parser.args.items = [] self.parser.args.items = []
self.parser.args.ignore_stdin = False self.parser.args.ignore_stdin = False
self.parser.env = TestEnvironment() self.parser.env = MockEnvironment()
self.parser._guess_method() self.parser._guess_method()
@ -285,7 +285,7 @@ class TestArgumentParser:
] ]
self.parser.args.ignore_stdin = False self.parser.args.ignore_stdin = False
self.parser.env = TestEnvironment() self.parser.env = MockEnvironment()
self.parser._guess_method() self.parser._guess_method()
@ -314,7 +314,7 @@ class TestIgnoreStdin:
def test_ignore_stdin(self, httpbin): def test_ignore_stdin(self, httpbin):
with open(FILE_PATH) as f: with open(FILE_PATH) as f:
env = TestEnvironment(stdin=f, stdin_isatty=False) env = MockEnvironment(stdin=f, stdin_isatty=False)
r = http('--ignore-stdin', '--verbose', httpbin.url + '/get', r = http('--ignore-stdin', '--verbose', httpbin.url + '/get',
env=env) env=env)
assert HTTP_OK in r assert HTTP_OK in r

View File

@ -1,8 +1,10 @@
from utils import TestEnvironment, http from httpie import __version__
from utils import MockEnvironment, http
from httpie.context import Environment
def test_default_options(httpbin): def test_default_options(httpbin):
env = TestEnvironment() env = MockEnvironment()
env.config['default_options'] = ['--form'] env.config['default_options'] = ['--form']
env.config.save() env.config.save()
r = http(httpbin.url + '/post', 'foo=bar', env=env) r = http(httpbin.url + '/post', 'foo=bar', env=env)
@ -10,7 +12,7 @@ def test_default_options(httpbin):
def test_default_options_overwrite(httpbin): def test_default_options_overwrite(httpbin):
env = TestEnvironment() env = MockEnvironment()
env.config['default_options'] = ['--form'] env.config['default_options'] = ['--form']
env.config.save() env.config.save()
r = http('--json', httpbin.url + '/post', 'foo=bar', env=env) r = http('--json', httpbin.url + '/post', 'foo=bar', env=env)
@ -18,7 +20,7 @@ def test_default_options_overwrite(httpbin):
def test_migrate_implicit_content_type(): def test_migrate_implicit_content_type():
config = TestEnvironment().config config = MockEnvironment().config
config['implicit_content_type'] = 'json' config['implicit_content_type'] = 'json'
config.save() config.save()
@ -31,3 +33,8 @@ def test_migrate_implicit_content_type():
config.load() config.load()
assert 'implicit_content_type' not in config assert 'implicit_content_type' not in config
assert config['default_options'] == ['--form'] assert config['default_options'] == ['--form']
def test_current_version():
version = Environment().config['__meta__']['httpie']
assert version == __version__

View File

@ -3,10 +3,25 @@ Tests for the provided defaults regarding HTTP method, and --json vs. --form.
""" """
from httpie.client import JSON_ACCEPT from httpie.client import JSON_ACCEPT
from utils import TestEnvironment, http, HTTP_OK from utils import MockEnvironment, http, HTTP_OK
from fixtures import FILE_PATH from fixtures import FILE_PATH
def test_default_headers_case_insensitive(httpbin):
"""
<https://github.com/jakubroztocil/httpie/issues/644>
"""
r = http(
'--debug',
'--print=H',
httpbin.url + '/post',
'CONTENT-TYPE:application/json-patch+json',
'a=b',
)
assert 'CONTENT-TYPE: application/json-patch+json' in r
assert 'Content-Type' not in r
class TestImplicitHTTPMethod: class TestImplicitHTTPMethod:
def test_implicit_GET(self, httpbin): def test_implicit_GET(self, httpbin):
r = http(httpbin.url + '/get') r = http(httpbin.url + '/get')
@ -29,7 +44,7 @@ class TestImplicitHTTPMethod:
def test_implicit_POST_stdin(self, httpbin): def test_implicit_POST_stdin(self, httpbin):
with open(FILE_PATH) as f: with open(FILE_PATH) as f:
env = TestEnvironment(stdin_isatty=False, stdin=f) env = MockEnvironment(stdin_isatty=False, stdin=f)
r = http('--form', httpbin.url + '/post', env=env) r = http('--form', httpbin.url + '/post', env=env)
assert HTTP_OK in r assert HTTP_OK in r
@ -43,7 +58,7 @@ class TestAutoContentTypeAndAcceptHeaders:
""" """
def test_GET_no_data_no_auto_headers(self, httpbin): def test_GET_no_data_no_auto_headers(self, httpbin):
# https://github.com/jkbrzt/httpie/issues/62 # https://github.com/jakubroztocil/httpie/issues/62
r = http('GET', httpbin.url + '/headers') r = http('GET', httpbin.url + '/headers')
assert HTTP_OK in r assert HTTP_OK in r
assert r.json['headers']['Accept'] == '*/*' assert r.json['headers']['Accept'] == '*/*'
@ -74,7 +89,7 @@ class TestAutoContentTypeAndAcceptHeaders:
assert HTTP_OK in r assert HTTP_OK in r
assert r.json['headers']['Accept'] == JSON_ACCEPT assert r.json['headers']['Accept'] == JSON_ACCEPT
# Make sure Content-Type gets set even with no data. # Make sure Content-Type gets set even with no data.
# https://github.com/jkbrzt/httpie/issues/137 # https://github.com/jakubroztocil/httpie/issues/137
assert 'application/json' in r.json['headers']['Content-Type'] assert 'application/json' in r.json['headers']['Content-Type']
def test_GET_explicit_JSON_explicit_headers(self, httpbin): def test_GET_explicit_JSON_explicit_headers(self, httpbin):
@ -97,11 +112,11 @@ class TestAutoContentTypeAndAcceptHeaders:
assert '"Content-Type": "application/xml"' in r assert '"Content-Type": "application/xml"' in r
def test_print_only_body_when_stdout_redirected_by_default(self, httpbin): def test_print_only_body_when_stdout_redirected_by_default(self, httpbin):
env = TestEnvironment(stdin_isatty=True, stdout_isatty=False) env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)
r = http('GET', httpbin.url + '/get', env=env) r = http('GET', httpbin.url + '/get', env=env)
assert 'HTTP/' not in r assert 'HTTP/' not in r
def test_print_overridable_when_stdout_redirected(self, httpbin): def test_print_overridable_when_stdout_redirected(self, httpbin):
env = TestEnvironment(stdin_isatty=True, stdout_isatty=False) env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)
r = http('--print=h', 'GET', httpbin.url + '/get', env=env) r = http('--print=h', 'GET', httpbin.url + '/get', env=env)
assert HTTP_OK in r assert HTTP_OK in r

View File

@ -10,7 +10,7 @@ from httpie.downloads import (
parse_content_range, filename_from_content_disposition, filename_from_url, parse_content_range, filename_from_content_disposition, filename_from_url,
get_unique_filename, ContentRangeError, Downloader, get_unique_filename, ContentRangeError, Downloader,
) )
from utils import http, TestEnvironment from utils import http, MockEnvironment
class Response(object): class Response(object):
@ -123,7 +123,7 @@ class TestDownloads:
def test_actual_download(self, httpbin_both, httpbin): def test_actual_download(self, httpbin_both, httpbin):
robots_txt = '/robots.txt' robots_txt = '/robots.txt'
body = urlopen(httpbin + robots_txt).read().decode() body = urlopen(httpbin + robots_txt).read().decode()
env = TestEnvironment(stdin_isatty=True, stdout_isatty=False) env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)
r = http('--download', httpbin_both.url + robots_txt, env=env) r = http('--download', httpbin_both.url + robots_txt, env=env)
assert 'Downloading' in r.stderr assert 'Downloading' in r.stderr
assert '[K' in r.stderr assert '[K' in r.stderr

View File

@ -1,7 +1,7 @@
import mock import mock
from httpie import ExitStatus from httpie import ExitStatus
from utils import TestEnvironment, http, HTTP_OK from utils import MockEnvironment, http, HTTP_OK
def test_keyboard_interrupt_during_arg_parsing_exit_status(httpbin): def test_keyboard_interrupt_during_arg_parsing_exit_status(httpbin):
@ -21,26 +21,26 @@ def test_keyboard_interrupt_in_program_exit_status(httpbin):
def test_ok_response_exits_0(httpbin): def test_ok_response_exits_0(httpbin):
r = http('GET', httpbin.url + '/get') r = http('GET', httpbin.url + '/get')
assert HTTP_OK in r assert HTTP_OK in r
assert r.exit_status == ExitStatus.OK assert r.exit_status == ExitStatus.SUCCESS
def test_error_response_exits_0_without_check_status(httpbin): def test_error_response_exits_0_without_check_status(httpbin):
r = http('GET', httpbin.url + '/status/500') r = http('GET', httpbin.url + '/status/500')
assert '500 INTERNAL SERVER ERRO' in r assert '500 INTERNAL SERVER ERROR' in r
assert r.exit_status == ExitStatus.OK assert r.exit_status == ExitStatus.SUCCESS
assert not r.stderr assert not r.stderr
def test_timeout_exit_status(httpbin): def test_timeout_exit_status(httpbin):
r = http('--timeout=0.01', 'GET', httpbin.url + '/delay/0.02', r = http('--timeout=0.01', 'GET', httpbin.url + '/delay/0.5',
error_exit_ok=True) error_exit_ok=True)
assert r.exit_status == ExitStatus.ERROR_TIMEOUT assert r.exit_status == ExitStatus.ERROR_TIMEOUT
def test_3xx_check_status_exits_3_and_stderr_when_stdout_redirected( def test_3xx_check_status_exits_3_and_stderr_when_stdout_redirected(
httpbin): httpbin):
env = TestEnvironment(stdout_isatty=False) env = MockEnvironment(stdout_isatty=False)
r = http('--check-status', '--headers', r = http('--check-status', '--headers',
'GET', httpbin.url + '/status/301', 'GET', httpbin.url + '/status/301',
env=env, error_exit_ok=True) env=env, error_exit_ok=True)
@ -55,7 +55,7 @@ def test_3xx_check_status_redirects_allowed_exits_0(httpbin):
error_exit_ok=True) error_exit_ok=True)
# The redirect will be followed so 200 is expected. # The redirect will be followed so 200 is expected.
assert HTTP_OK in r assert HTTP_OK in r
assert r.exit_status == ExitStatus.OK assert r.exit_status == ExitStatus.SUCCESS
def test_4xx_check_status_exits_4(httpbin): def test_4xx_check_status_exits_4(httpbin):

View File

@ -2,28 +2,27 @@
import pytest import pytest
from httpie.input import ParseError from httpie.input import ParseError
from utils import TestEnvironment, http, HTTP_OK from utils import MockEnvironment, http, HTTP_OK
from fixtures import FILE_PATH, FILE_CONTENT from fixtures import FILE_PATH, FILE_CONTENT
import httpie import httpie
from httpie.compat import is_py26
def test_debug(): def test_debug():
r = http('--debug') r = http('--debug')
assert r.exit_status == httpie.ExitStatus.OK assert r.exit_status == httpie.ExitStatus.SUCCESS
assert 'HTTPie %s' % httpie.__version__ in r.stderr assert 'HTTPie %s' % httpie.__version__ in r.stderr
def test_help(): def test_help():
r = http('--help', error_exit_ok=True) r = http('--help', error_exit_ok=True)
assert r.exit_status == httpie.ExitStatus.OK assert r.exit_status == httpie.ExitStatus.SUCCESS
assert 'https://github.com/jkbrzt/httpie/issues' in r assert 'https://github.com/jakubroztocil/httpie/issues' in r
def test_version(): def test_version():
r = http('--version', error_exit_ok=True) r = http('--version', error_exit_ok=True)
assert r.exit_status == httpie.ExitStatus.OK assert r.exit_status == httpie.ExitStatus.SUCCESS
# FIXME: py3 has version in stdout, py2 in stderr # FIXME: py3 has version in stdout, py2 in stderr
assert httpie.__version__ == r.stderr.strip() + r.strip() assert httpie.__version__ == r.stderr.strip() + r.strip()
@ -64,7 +63,7 @@ def test_POST_form_multiple_values(httpbin_both):
def test_POST_stdin(httpbin_both): def test_POST_stdin(httpbin_both):
with open(FILE_PATH) as f: with open(FILE_PATH) as f:
env = TestEnvironment(stdin=f, stdin_isatty=False) env = MockEnvironment(stdin=f, stdin_isatty=False)
r = http('--form', 'POST', httpbin_both + '/post', env=env) r = http('--form', 'POST', httpbin_both + '/post', env=env)
assert HTTP_OK in r assert HTTP_OK in r
assert FILE_CONTENT in r assert FILE_CONTENT in r
@ -107,10 +106,6 @@ def test_headers_empty_value_with_value_gives_error(httpbin):
http('GET', httpbin + '/headers', 'Accept;SYNTAX_ERROR') http('GET', httpbin + '/headers', 'Accept;SYNTAX_ERROR')
@pytest.mark.skipif(
is_py26,
reason='the `object_pairs_hook` arg for `json.loads()` is Py>2.6 only'
)
def test_json_input_preserve_order(httpbin_both): def test_json_input_preserve_order(httpbin_both):
r = http('PATCH', httpbin_both + '/patch', r = http('PATCH', httpbin_both + '/patch',
'order:={"map":{"1":"first","2":"second"}}') 'order:={"map":{"1":"first","2":"second"}}')

View File

@ -3,7 +3,7 @@ from tempfile import gettempdir
import pytest import pytest
from utils import TestEnvironment, http, HTTP_OK, COLOR, CRLF from utils import MockEnvironment, http, HTTP_OK, COLOR, CRLF
from httpie import ExitStatus from httpie import ExitStatus
from httpie.compat import urlopen from httpie.compat import urlopen
from httpie.output.formatters.colors import get_lexer from httpie.output.formatters.colors import get_lexer
@ -15,7 +15,7 @@ def test_output_option(httpbin, stdout_isatty):
url = httpbin + '/robots.txt' url = httpbin + '/robots.txt'
r = http('--output', output_filename, url, r = http('--output', output_filename, url,
env=TestEnvironment(stdout_isatty=stdout_isatty)) env=MockEnvironment(stdout_isatty=stdout_isatty))
assert r == '' assert r == ''
expected_body = urlopen(url).read().decode() expected_body = urlopen(url).read().decode()
@ -33,7 +33,7 @@ class TestVerboseFlag:
assert r.count('__test__') == 2 assert r.count('__test__') == 2
def test_verbose_form(self, httpbin): def test_verbose_form(self, httpbin):
# https://github.com/jkbrzt/httpie/issues/53 # https://github.com/jakubroztocil/httpie/issues/53
r = http('--verbose', '--form', 'POST', httpbin.url + '/post', r = http('--verbose', '--form', 'POST', httpbin.url + '/post',
'A=B', 'C=D') 'A=B', 'C=D')
assert HTTP_OK in r assert HTTP_OK in r
@ -86,7 +86,7 @@ class TestPrettyOptions:
"""Test the --pretty flag handling.""" """Test the --pretty flag handling."""
def test_pretty_enabled_by_default(self, httpbin): def test_pretty_enabled_by_default(self, httpbin):
env = TestEnvironment(colors=256) env = MockEnvironment(colors=256)
r = http('GET', httpbin.url + '/get', env=env) r = http('GET', httpbin.url + '/get', env=env)
assert COLOR in r assert COLOR in r
@ -95,7 +95,7 @@ class TestPrettyOptions:
assert COLOR not in r assert COLOR not in r
def test_force_pretty(self, httpbin): def test_force_pretty(self, httpbin):
env = TestEnvironment(stdout_isatty=False, colors=256) env = MockEnvironment(stdout_isatty=False, colors=256)
r = http('--pretty=all', 'GET', httpbin.url + '/get', env=env, ) r = http('--pretty=all', 'GET', httpbin.url + '/get', env=env, )
assert COLOR in r assert COLOR in r
@ -108,13 +108,13 @@ class TestPrettyOptions:
match any lexer. match any lexer.
""" """
env = TestEnvironment(colors=256) env = MockEnvironment(colors=256)
r = http('--print=B', '--pretty=all', httpbin.url + '/post', r = http('--print=B', '--pretty=all', httpbin.url + '/post',
'Content-Type:text/foo+json', 'a=b', env=env) 'Content-Type:text/foo+json', 'a=b', env=env)
assert COLOR in r assert COLOR in r
def test_colors_option(self, httpbin): def test_colors_option(self, httpbin):
env = TestEnvironment(colors=256) env = MockEnvironment(colors=256)
r = http('--print=B', '--pretty=colors', r = http('--print=B', '--pretty=colors',
'GET', httpbin.url + '/get', 'a=b', 'GET', httpbin.url + '/get', 'a=b',
env=env) env=env)
@ -123,7 +123,7 @@ class TestPrettyOptions:
assert COLOR in r assert COLOR in r
def test_format_option(self, httpbin): def test_format_option(self, httpbin):
env = TestEnvironment(colors=256) env = MockEnvironment(colors=256)
r = http('--print=B', '--pretty=format', r = http('--print=B', '--pretty=format',
'GET', httpbin.url + '/get', 'a=b', 'GET', httpbin.url + '/get', 'a=b',
env=env) env=env)
@ -161,7 +161,7 @@ class TestLineEndings:
def test_CRLF_formatted_response(self, httpbin): def test_CRLF_formatted_response(self, httpbin):
r = http('--pretty=format', 'GET', httpbin.url + '/get') r = http('--pretty=format', 'GET', httpbin.url + '/get')
assert r.exit_status == ExitStatus.OK assert r.exit_status == ExitStatus.SUCCESS
self._validate_crlf(r) self._validate_crlf(r)
def test_CRLF_ugly_request(self, httpbin): def test_CRLF_ugly_request(self, httpbin):

View File

@ -7,7 +7,7 @@ from httpie.compat import is_windows
def test_Host_header_overwrite(httpbin): def test_Host_header_overwrite(httpbin):
""" """
https://github.com/jkbrzt/httpie/issues/235 https://github.com/jakubroztocil/httpie/issues/235
""" """
host = 'httpbin.org' host = 'httpbin.org'
@ -21,7 +21,7 @@ def test_Host_header_overwrite(httpbin):
@pytest.mark.skipif(is_windows, reason='Unix-only') @pytest.mark.skipif(is_windows, reason='Unix-only')
def test_output_devnull(httpbin): def test_output_devnull(httpbin):
""" """
https://github.com/jkbrzt/httpie/issues/252 https://github.com/jakubroztocil/httpie/issues/252
""" """
http('--output=/dev/null', httpbin + '/get') http('--output=/dev/null', httpbin + '/get')

View File

@ -7,7 +7,7 @@ from tempfile import gettempdir
import pytest import pytest
from httpie.plugins.builtin import HTTPBasicAuth from httpie.plugins.builtin import HTTPBasicAuth
from utils import TestEnvironment, mk_config_dir, http, HTTP_OK from utils import MockEnvironment, mk_config_dir, http, HTTP_OK
from fixtures import UNICODE from fixtures import UNICODE
@ -29,7 +29,7 @@ class SessionTestBase(object):
for session files being reused. for session files being reused.
""" """
return TestEnvironment(config_dir=self.config_dir) return MockEnvironment(config_dir=self.config_dir)
class TestSessionFlow(SessionTestBase): class TestSessionFlow(SessionTestBase):
@ -81,8 +81,8 @@ class TestSessionFlow(SessionTestBase):
assert HTTP_OK in r4 assert HTTP_OK in r4
assert r4.json['headers']['Hello'] == 'World2' assert r4.json['headers']['Hello'] == 'World2'
assert r4.json['headers']['Cookie'] == 'hello=world2' assert r4.json['headers']['Cookie'] == 'hello=world2'
assert (r2.json['headers']['Authorization'] != assert (r2.json['headers']['Authorization']
r4.json['headers']['Authorization']) != r4.json['headers']['Authorization'])
def test_session_read_only(self, httpbin): def test_session_read_only(self, httpbin):
self.start_session(httpbin) self.start_session(httpbin)
@ -143,7 +143,7 @@ class TestSession(SessionTestBase):
@pytest.mark.skipif( @pytest.mark.skipif(
sys.version_info >= (3,), sys.version_info >= (3,),
reason="This test fails intermittently on Python 3 - " reason="This test fails intermittently on Python 3 - "
"see https://github.com/jkbrzt/httpie/issues/282") "see https://github.com/jakubroztocil/httpie/issues/282")
def test_session_unicode(self, httpbin): def test_session_unicode(self, httpbin):
self.start_session(httpbin) self.start_session(httpbin)
@ -157,14 +157,14 @@ class TestSession(SessionTestBase):
assert HTTP_OK in r2 assert HTTP_OK in r2
# FIXME: Authorization *sometimes* is not present on Python3 # FIXME: Authorization *sometimes* is not present on Python3
assert (r2.json['headers']['Authorization'] == assert (r2.json['headers']['Authorization']
HTTPBasicAuth.make_header(u'test', UNICODE)) == HTTPBasicAuth.make_header(u'test', UNICODE))
# httpbin doesn't interpret utf8 headers # httpbin doesn't interpret utf8 headers
assert UNICODE in r2 assert UNICODE in r2
def test_session_default_header_value_overwritten(self, httpbin): def test_session_default_header_value_overwritten(self, httpbin):
self.start_session(httpbin) self.start_session(httpbin)
# https://github.com/jkbrzt/httpie/issues/180 # https://github.com/jakubroztocil/httpie/issues/180
r1 = http('--session=test', r1 = http('--session=test',
httpbin.url + '/headers', 'User-Agent:custom', httpbin.url + '/headers', 'User-Agent:custom',
env=self.env()) env=self.env())
@ -176,7 +176,7 @@ class TestSession(SessionTestBase):
assert r2.json['headers']['User-Agent'] == 'custom' assert r2.json['headers']['User-Agent'] == 'custom'
def test_download_in_session(self, httpbin): def test_download_in_session(self, httpbin):
# https://github.com/jkbrzt/httpie/issues/412 # https://github.com/jakubroztocil/httpie/issues/412
self.start_session(httpbin) self.start_session(httpbin)
cwd = os.getcwd() cwd = os.getcwd()
os.chdir(gettempdir()) os.chdir(gettempdir())

View File

@ -73,6 +73,11 @@ class TestServerCert:
r = http(httpbin_secure.url + '/get', '--verify=no') r = http(httpbin_secure.url + '/get', '--verify=no')
assert HTTP_OK in r assert HTTP_OK in r
@pytest.mark.parametrize('verify_value', ['false', 'fALse'])
def test_verify_false_OK(self, httpbin_secure, verify_value):
r = http(httpbin_secure.url + '/get', '--verify', verify_value)
assert HTTP_OK in r
def test_verify_custom_ca_bundle_path( def test_verify_custom_ca_bundle_path(
self, httpbin_secure_untrusted): self, httpbin_secure_untrusted):
r = http(httpbin_secure_untrusted + '/get', '--verify', CA_BUNDLE) r = http(httpbin_secure_untrusted + '/get', '--verify', CA_BUNDLE)
@ -85,7 +90,8 @@ class TestServerCert:
http(httpbin_secure_untrusted.url + '/get') http(httpbin_secure_untrusted.url + '/get')
def test_verify_custom_ca_bundle_invalid_path(self, httpbin_secure): def test_verify_custom_ca_bundle_invalid_path(self, httpbin_secure):
with pytest.raises(SSLError): # since 2.14.0 requests raises IOError
with pytest.raises((SSLError, IOError)):
http(httpbin_secure.url + '/get', '--verify', '/__not_found__') http(httpbin_secure.url + '/get', '--verify', '/__not_found__')
def test_verify_custom_ca_bundle_invalid_bundle(self, httpbin_secure): def test_verify_custom_ca_bundle_invalid_bundle(self, httpbin_secure):

View File

@ -2,7 +2,7 @@ import pytest
from httpie.compat import is_windows from httpie.compat import is_windows
from httpie.output.streams import BINARY_SUPPRESSED_NOTICE from httpie.output.streams import BINARY_SUPPRESSED_NOTICE
from utils import http, TestEnvironment from utils import http, MockEnvironment
from fixtures import BIN_FILE_CONTENT, BIN_FILE_PATH from fixtures import BIN_FILE_CONTENT, BIN_FILE_PATH
@ -14,7 +14,7 @@ from fixtures import BIN_FILE_CONTENT, BIN_FILE_PATH
def test_pretty_redirected_stream(httpbin): def test_pretty_redirected_stream(httpbin):
"""Test that --stream works with prettified redirected output.""" """Test that --stream works with prettified redirected output."""
with open(BIN_FILE_PATH, 'rb') as f: with open(BIN_FILE_PATH, 'rb') as f:
env = TestEnvironment(colors=256, stdin=f, env = MockEnvironment(colors=256, stdin=f,
stdin_isatty=False, stdin_isatty=False,
stdout_isatty=False) stdout_isatty=False)
r = http('--verbose', '--pretty=all', '--stream', 'GET', r = http('--verbose', '--pretty=all', '--stream', 'GET',
@ -26,7 +26,7 @@ def test_encoded_stream(httpbin):
"""Test that --stream works with non-prettified """Test that --stream works with non-prettified
redirected terminal output.""" redirected terminal output."""
with open(BIN_FILE_PATH, 'rb') as f: with open(BIN_FILE_PATH, 'rb') as f:
env = TestEnvironment(stdin=f, stdin_isatty=False) env = MockEnvironment(stdin=f, stdin_isatty=False)
r = http('--pretty=none', '--stream', '--verbose', 'GET', r = http('--pretty=none', '--stream', '--verbose', 'GET',
httpbin.url + '/get', env=env) httpbin.url + '/get', env=env)
assert BINARY_SUPPRESSED_NOTICE.decode() in r assert BINARY_SUPPRESSED_NOTICE.decode() in r
@ -36,7 +36,7 @@ def test_redirected_stream(httpbin):
"""Test that --stream works with non-prettified """Test that --stream works with non-prettified
redirected terminal output.""" redirected terminal output."""
with open(BIN_FILE_PATH, 'rb') as f: with open(BIN_FILE_PATH, 'rb') as f:
env = TestEnvironment(stdout_isatty=False, env = MockEnvironment(stdout_isatty=False,
stdin_isatty=False, stdin_isatty=False,
stdin=f) stdin=f)
r = http('--pretty=none', '--stream', '--verbose', 'GET', r = http('--pretty=none', '--stream', '--verbose', 'GET',

View File

@ -3,7 +3,7 @@ import os
import pytest import pytest
from httpie.input import ParseError from httpie.input import ParseError
from utils import TestEnvironment, http, HTTP_OK from utils import MockEnvironment, http, HTTP_OK
from fixtures import FILE_PATH_ARG, FILE_PATH, FILE_CONTENT from fixtures import FILE_PATH_ARG, FILE_PATH, FILE_CONTENT
@ -62,14 +62,14 @@ class TestRequestBodyFromFilePath:
def test_request_body_from_file_by_path_no_field_name_allowed( def test_request_body_from_file_by_path_no_field_name_allowed(
self, httpbin): self, httpbin):
env = TestEnvironment(stdin_isatty=True) env = MockEnvironment(stdin_isatty=True)
r = http('POST', httpbin.url + '/post', 'field-name@' + FILE_PATH_ARG, r = http('POST', httpbin.url + '/post', 'field-name@' + FILE_PATH_ARG,
env=env, error_exit_ok=True) env=env, error_exit_ok=True)
assert 'perhaps you meant --form?' in r.stderr assert 'perhaps you meant --form?' in r.stderr
def test_request_body_from_file_by_path_no_data_items_allowed( def test_request_body_from_file_by_path_no_data_items_allowed(
self, httpbin): self, httpbin):
env = TestEnvironment(stdin_isatty=False) env = MockEnvironment(stdin_isatty=False)
r = http('POST', httpbin.url + '/post', '@' + FILE_PATH_ARG, 'foo=bar', r = http('POST', httpbin.url + '/post', '@' + FILE_PATH_ARG, 'foo=bar',
env=env, error_exit_ok=True) env=env, error_exit_ok=True)
assert 'cannot be mixed' in r.stderr assert 'cannot be mixed' in r.stderr

View File

@ -4,7 +4,7 @@ import tempfile
import pytest import pytest
from httpie.context import Environment from httpie.context import Environment
from utils import TestEnvironment, http from utils import MockEnvironment, http
from httpie.compat import is_windows from httpie.compat import is_windows
@ -20,7 +20,7 @@ class TestWindowsOnly:
class TestFakeWindows: class TestFakeWindows:
def test_output_file_pretty_not_allowed_on_windows(self, httpbin): def test_output_file_pretty_not_allowed_on_windows(self, httpbin):
env = TestEnvironment(is_windows=True) env = MockEnvironment(is_windows=True)
output_file = os.path.join( output_file = os.path.join(
tempfile.gettempdir(), tempfile.gettempdir(),
self.test_output_file_pretty_not_allowed_on_windows.__name__ self.test_output_file_pretty_not_allowed_on_windows.__name__

View File

@ -33,7 +33,7 @@ def add_auth(url, auth):
return proto + '://' + auth + '@' + rest return proto + '://' + auth + '@' + rest
class TestEnvironment(Environment): class MockEnvironment(Environment):
"""Environment subclass with reasonable defaults for testing.""" """Environment subclass with reasonable defaults for testing."""
colors = 0 colors = 0
stdin_isatty = True, stdin_isatty = True,
@ -51,7 +51,7 @@ class TestEnvironment(Environment):
mode='w+t', mode='w+t',
prefix='httpie_stderr' prefix='httpie_stderr'
) )
super(TestEnvironment, self).__init__(**kwargs) super(MockEnvironment, self).__init__(**kwargs)
self._delete_config_dir = False self._delete_config_dir = False
@property @property
@ -59,7 +59,7 @@ class TestEnvironment(Environment):
if not self.config_dir.startswith(tempfile.gettempdir()): if not self.config_dir.startswith(tempfile.gettempdir()):
self.config_dir = mk_config_dir() self.config_dir = mk_config_dir()
self._delete_config_dir = True self._delete_config_dir = True
return super(TestEnvironment, self).config return super(MockEnvironment, self).config
def cleanup(self): def cleanup(self):
if self._delete_config_dir: if self._delete_config_dir:
@ -119,8 +119,8 @@ class StrCLIResponse(str, BaseCLIResponse):
elif self.strip().startswith('{'): elif self.strip().startswith('{'):
# Looks like JSON body. # Looks like JSON body.
self._json = json.loads(self) self._json = json.loads(self)
elif (self.count('Content-Type:') == 1 and elif (self.count('Content-Type:') == 1
'application/json' in self): and 'application/json' in self):
# Looks like a whole JSON HTTP message, # Looks like a whole JSON HTTP message,
# try to extract its body. # try to extract its body.
try: try:
@ -183,7 +183,7 @@ def http(*args, **kwargs):
error_exit_ok = kwargs.pop('error_exit_ok', False) error_exit_ok = kwargs.pop('error_exit_ok', False)
env = kwargs.get('env') env = kwargs.get('env')
if not env: if not env:
env = kwargs['env'] = TestEnvironment() env = kwargs['env'] = MockEnvironment()
stdout = env.stdout stdout = env.stdout
stderr = env.stderr stderr = env.stderr
@ -219,7 +219,7 @@ def http(*args, **kwargs):
sys.stderr.write(stderr.read()) sys.stderr.write(stderr.read())
raise raise
else: else:
if not error_exit_ok and exit_status != ExitStatus.OK: if not error_exit_ok and exit_status != ExitStatus.SUCCESS:
dump_stderr() dump_stderr()
raise ExitStatusError( raise ExitStatusError(
'httpie.core.main() unexpectedly returned' 'httpie.core.main() unexpectedly returned'
@ -243,7 +243,7 @@ def http(*args, **kwargs):
r.stderr = stderr.read() r.stderr = stderr.read()
r.exit_status = exit_status r.exit_status = exit_status
if r.exit_status != ExitStatus.OK: if r.exit_status != ExitStatus.SUCCESS:
sys.stderr.write(r.stderr) sys.stderr.write(r.stderr)
return r return r

12
tox.ini
View File

@ -3,7 +3,8 @@
[tox] [tox]
envlist = py26, py27, py35, pypy, codestyle # pypy3 currently fails because of a Flask issue
envlist = py27, py37, pypy
[testenv] [testenv]
@ -21,10 +22,5 @@ commands =
--doctest-modules \ --doctest-modules \
{posargs:./httpie ./tests} {posargs:./httpie ./tests}
[testenv:codestyle] [testenv:py27-osx-builtin]
deps = pycodestyle basepython = /usr/bin/python2.7
commands =
pycodestyle \
--ignore=E241,E501
# 241 - multiple spaces after ,
# 501 - line too long