├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGES.txt ├── LICENSE ├── MANIFEST.in ├── README.md ├── setup.py └── unirest ├── __init__.py ├── test ├── __init__.py └── test_unirest.py └── utils.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.yml] 13 | indent_size = 2 14 | 15 | [*.{md,yml}] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .pydevproject 3 | *.pyc 4 | ********.swp 5 | .DS_Store 6 | dist 7 | MANIFEST 8 | *.pyc 9 | *.egg-info 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | install: "pip install unirest" 3 | script: 4 | - python unirest/test/test_unirest.py 5 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v3.0, 2013-02-15 -- Initial release. 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013-2015 Mashape (https://www.mashape.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | recursive-include docs *.txt 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unirest for Python [![Build Status][travis-image]][travis-url] [![version][pypi-version]][pypi-url] 2 | 3 | [![License][pypi-license]][license-url] 4 | [![Downloads][pypi-downloads]][pypi-url] 5 | [![Dependencies][versioneye-image]][versioneye-url] 6 | [![Gitter][gitter-image]][gitter-url] 7 | [![Not Maintained](https://img.shields.io/badge/Maintenance%20Level-Not%20Maintained-yellow.svg)](https://gist.github.com/cheerfulstoic/d107229326a01ff0f333a1d3476e068d) 8 | 9 | 10 | ![][unirest-logo] 11 | 12 | 13 | [Unirest](http://unirest.io) is a set of lightweight HTTP libraries available in multiple languages. 14 | 15 | 16 | ## Features 17 | 18 | * Make `GET`, `POST`, `PUT`, `PATCH`, `DELETE` requests 19 | * Both syncronous and asynchronous (non-blocking) requests 20 | * Supports form parameters, file uploads and custom body entities 21 | * Supports gzip 22 | * Supports Basic Authentication natively 23 | * Customizable timeout 24 | * Customizable default headers for every request (DRY) 25 | * Automatic JSON parsing into a native object for JSON responses 26 | 27 | ## Installing 28 | To utilize Unirest, install it using pip: 29 | 30 | ```bash 31 | $ pip install unirest 32 | ``` 33 | 34 | After installing the pip package, you can now begin simplifying requests by importing unirest: 35 | 36 | ```python 37 | import unirest 38 | ``` 39 | 40 | ### Creating Requests 41 | 42 | So you're probably wondering how using Unirest makes creating requests in Python easier, let's start with a working example: 43 | 44 | ```python 45 | response = unirest.post("http://httpbin.org/post", headers={ "Accept": "application/json" }, params={ "parameter": 23, "foo": "bar" }) 46 | 47 | response.code # The HTTP status code 48 | response.headers # The HTTP headers 49 | response.body # The parsed response 50 | response.raw_body # The unparsed response 51 | ``` 52 | 53 | ## Asynchronous Requests 54 | 55 | Python also supports asynchronous requests in which you can define a `callback` function to be passed along and invoked when Unirest receives the response: 56 | 57 | ```python 58 | def callback_function(response): 59 | response.code # The HTTP status code 60 | response.headers # The HTTP headers 61 | response.body # The parsed response 62 | response.raw_body # The unparsed response 63 | 64 | thread = unirest.post("http://httpbin.org/post", headers={ "Accept": "application/json" }, params={ "parameter": 23, "foo": "bar" }, callback=callback_function) 65 | ``` 66 | 67 | ## File Uploads 68 | 69 | Transferring file data requires that you `open` the file in a readable `r` mode: 70 | 71 | ```python 72 | response = unirest.post("http://httpbin.org/post", headers={"Accept": "application/json"}, 73 | params={ 74 | "parameter": "value", 75 | "file": open("/tmp/file", mode="r") 76 | } 77 | ) 78 | ``` 79 | 80 | ## Custom Entity Body 81 | 82 | ```python 83 | import json 84 | 85 | response = unirest.post("http://httpbin.org/post", headers={ "Accept": "application/json" }, 86 | params=json.dumps({ 87 | "parameter": "value", 88 | "foo": "bar" 89 | }) 90 | ) 91 | ``` 92 | 93 | **Note**: For the sake of semplicity, even with custom entities in the body, the keyword argument is still `params` (instead of `data` for example). I'm looking for feedback on this. 94 | 95 | ### Basic Authentication 96 | 97 | Authenticating the request with basic authentication can be done by providing an `auth` array like: 98 | 99 | ```python 100 | response = unirest.get("http://httpbin.org/get", auth=('username', 'password')) 101 | ``` 102 | 103 | # Request 104 | 105 | ```python 106 | unirest.get(url, headers = {}, params = {}, auth = (), callback = None) 107 | unirest.post(url, headers = {}, params = {}, auth = (), callback = None) 108 | unirest.put(url, headers = {}, params = {}, auth = (), callback = None) 109 | unirest.patch(url, headers = {}, params = {}, auth = (), callback = None) 110 | unirest.delete(url, headers = {}, params = {}, auth = (), callback = None) 111 | ``` 112 | 113 | - `url` - Endpoint, address, or URI to be acted upon and requested information from in a string format. 114 | - `headers` - Request Headers as an associative array 115 | - `params` - Request Body as an associative array or object 116 | - `auth` - The Basic Authentication credentials as an array 117 | - `callback` - Asychronous callback method to be invoked upon result. 118 | 119 | # Response 120 | Upon receiving a response, Unirest returns the result in the form of an Object. This object should always have the same keys for each language regarding to the response details. 121 | 122 | - `code` - HTTP Response Status Code (Example 200) 123 | - `headers`- HTTP Response Headers 124 | - `body`- Parsed response body where applicable, for example JSON responses are parsed to Objects / Associative Arrays. 125 | - `raw_body`- Un-parsed response body 126 | 127 | # Advanced Configuration 128 | 129 | You can set some advanced configuration to tune Unirest-Python: 130 | 131 | ### Timeout 132 | 133 | You can set a custom timeout value (in **seconds**): 134 | 135 | ```python 136 | unirest.timeout(5) # 5s timeout 137 | ``` 138 | 139 | ### Default Request Headers 140 | 141 | You can set default headers that will be sent on every request: 142 | 143 | ```python 144 | unirest.default_header('Header1','Value1') 145 | unirest.default_header('Header2','Value2') 146 | ``` 147 | 148 | You can clear the default headers anytime with: 149 | 150 | ```python 151 | unirest.clear_default_headers() 152 | ``` 153 | 154 | ---- 155 | 156 | Made with ♥ from the [Mashape](https://www.mashape.com/) team 157 | 158 | [unirest-logo]: http://cl.ly/image/2P373Y090s2O/Image%202015-10-12%20at%209.48.06%20PM.png 159 | 160 | 161 | [license-url]: https://github.com/Mashape/unirest-python/blob/master/LICENSE 162 | 163 | [gitter-url]: https://gitter.im/Mashape/unirest-python 164 | [gitter-image]: https://img.shields.io/badge/Gitter-Join%20Chat-blue.svg?style=flat 165 | 166 | [travis-url]: https://travis-ci.org/Mashape/unirest-python 167 | [travis-image]: https://img.shields.io/travis/Mashape/unirest-python.svg?style=flat 168 | 169 | [pypi-url]: https://pypi.python.org/pypi/Unirest/ 170 | [pypi-license]: https://img.shields.io/pypi/l/Unirest.svg?style=flat 171 | [pypi-version]: https://img.shields.io/pypi/v/Unirest.svg?style=flat 172 | [pypi-downloads]: https://img.shields.io/pypi/dm/Unirest.svg?style=flat 173 | 174 | [codeclimate-url]: https://codeclimate.com/github/Mashape/unirest-python 175 | [codeclimate-quality]: https://img.shields.io/codeclimate/github/Mashape/unirest-python.svg?style=flat 176 | [codeclimate-coverage]: https://img.shields.io/codeclimate/coverage/github/Mashape/unirest-python.svg?style=flat 177 | 178 | [versioneye-url]: https://www.versioneye.com/user/projects/54b82a8905064657eb00024e 179 | [versioneye-image]: https://img.shields.io/versioneye/d/user/projects/54b82a8905064657eb00024e.svg?style=flat 180 | 181 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | except ImportError: 4 | from distutils.core import setup 5 | 6 | setup( 7 | name='Unirest', 8 | version='1.1.7', 9 | author='Mashape', 10 | author_email='opensource@mashape.com', 11 | packages=['unirest'], 12 | url='https://github.com/Mashape/unirest-python', 13 | license='LICENSE', 14 | description='Simplified, lightweight HTTP client library', 15 | install_requires=[ 16 | "poster >= 0.8.1" 17 | ] 18 | ) 19 | -------------------------------------------------------------------------------- /unirest/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | The MIT License 3 | 4 | Copyright (c) 2013 Mashape (https://www.mashape.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | ''' 25 | 26 | import urllib 27 | import base64 28 | import threading 29 | import gzip 30 | from . import utils 31 | 32 | from StringIO import StringIO 33 | from poster.streaminghttp import register_openers 34 | 35 | try: 36 | import json 37 | except ImportError: 38 | import simplejson as json 39 | 40 | USER_AGENT = "unirest-python/1.1.6" 41 | 42 | _defaultheaders = {} 43 | _timeout = 10 44 | 45 | _httplib = None 46 | try: 47 | from google.appengine.api import urlfetch 48 | _httplib = 'urlfetch' 49 | except ImportError: 50 | pass 51 | 52 | if not _httplib: 53 | import urllib2 54 | _httplib = "urllib2" 55 | 56 | # Register the streaming http handlers 57 | register_openers() 58 | 59 | 60 | def __request(method, url, params={}, headers={}, auth=None, callback=None): 61 | 62 | # Encode URL 63 | url_parts = url.split("\\?") 64 | url = url_parts[0].replace(" ", "%20") 65 | if len(url_parts) == 2: 66 | url += "?" + url_parts[1] 67 | 68 | # Lowercase header keys 69 | headers = dict((k.lower(), v) for k, v in headers.iteritems()) 70 | headers["user-agent"] = USER_AGENT 71 | 72 | data, post_headers = utils.urlencode(params) 73 | if post_headers is not None: 74 | headers = dict(headers.items() + post_headers.items()) 75 | 76 | headers['Accept-encoding'] = 'gzip' 77 | 78 | if auth is not None: 79 | if len(auth) == 2: 80 | user = auth[0] 81 | password = auth[1] 82 | encoded_string = base64.b64encode(user + ':' + password) 83 | headers['Authorization'] = "Basic " + encoded_string 84 | 85 | headers = dict(headers.items() + _defaultheaders.items()) 86 | 87 | _unirestResponse = None 88 | if _httplib == "urlfetch": 89 | res = urlfetch.fetch(url, payload=data, headers=headers, method=method, deadline=_timeout) 90 | _unirestResponse = UnirestResponse(res.status_code, 91 | res.headers, 92 | res.content) 93 | else: 94 | req = urllib2.Request(url, data, headers) 95 | req.get_method = lambda: method 96 | 97 | try: 98 | response = urllib2.urlopen(req, timeout=_timeout) 99 | _unirestResponse = UnirestResponse(response.code, response.headers, response.read()) 100 | except urllib2.HTTPError, e: 101 | response = e 102 | _unirestResponse = UnirestResponse(response.code, response.headers, response.read()) 103 | except urllib2.URLError, e: 104 | _unirestResponse = UnirestResponse(0, {}, str(e.reason)) 105 | 106 | if callback is None or callback == {}: 107 | return _unirestResponse 108 | else: 109 | callback(_unirestResponse) 110 | 111 | # The following methods in the Mashape class are based on 112 | # Stripe's python bindings which are under the MIT license. 113 | # See https://github.com/stripe/stripe-python 114 | 115 | 116 | 117 | # End of Stripe methods. 118 | 119 | HEADERS_KEY = 'headers' 120 | CALLBACK_KEY = 'callback' 121 | PARAMS_KEY = 'params' 122 | AUTH_KEY = 'auth' 123 | 124 | 125 | def get_parameters(kwargs): 126 | params = kwargs.get(PARAMS_KEY, {}) 127 | if params is not None and type(params) is dict: 128 | return dict((k, v) for k, v in params.iteritems() if v is not None) 129 | return params 130 | 131 | 132 | def get(url, **kwargs): 133 | params = get_parameters(kwargs) 134 | if len(params) > 0: 135 | if url.find("?") == -1: 136 | url += "?" 137 | else: 138 | url += "&" 139 | url += utils.dict2query(dict((k, v) for k, v in params.iteritems() if v is not None)) # Removing None values/encode unicode objects 140 | 141 | return __dorequest("GET", url, {}, kwargs.get(HEADERS_KEY, {}), kwargs.get(AUTH_KEY, None), kwargs.get(CALLBACK_KEY, None)) 142 | 143 | 144 | def post(url, **kwargs): 145 | return __dorequest("POST", url, get_parameters(kwargs), kwargs.get(HEADERS_KEY, {}), kwargs.get(AUTH_KEY, None), kwargs.get(CALLBACK_KEY, None)) 146 | 147 | 148 | def put(url, **kwargs): 149 | return __dorequest("PUT", url, get_parameters(kwargs), kwargs.get(HEADERS_KEY, {}), kwargs.get(AUTH_KEY, None), kwargs.get(CALLBACK_KEY, None)) 150 | 151 | 152 | def delete(url, **kwargs): 153 | return __dorequest("DELETE", url, get_parameters(kwargs), kwargs.get(HEADERS_KEY, {}), kwargs.get(AUTH_KEY, None), kwargs.get(CALLBACK_KEY, None)) 154 | 155 | 156 | def patch(url, **kwargs): 157 | return __dorequest("PATCH", url, get_parameters(kwargs), kwargs.get(HEADERS_KEY, {}), kwargs.get(AUTH_KEY, None), kwargs.get(CALLBACK_KEY, None)) 158 | 159 | 160 | def default_header(name, value): 161 | _defaultheaders[name] = value 162 | 163 | 164 | def clear_default_headers(): 165 | _defaultheaders.clear() 166 | 167 | 168 | def timeout(seconds): 169 | global _timeout 170 | _timeout = seconds 171 | 172 | 173 | def __dorequest(method, url, params, headers, auth, callback=None): 174 | if callback is None: 175 | return __request(method, url, params, headers, auth) 176 | else: 177 | thread = threading.Thread(target=__request, 178 | args=(method, 179 | url, 180 | params, 181 | headers, 182 | auth, 183 | callback)) 184 | thread.start() 185 | return thread 186 | 187 | 188 | class UnirestResponse(object): 189 | def __init__(self, code, headers, body): 190 | self._code = code 191 | self._headers = headers 192 | 193 | if headers.get("Content-Encoding") == 'gzip': 194 | buf = StringIO(body) 195 | f = gzip.GzipFile(fileobj=buf) 196 | body = f.read() 197 | 198 | self._raw_body = body 199 | self._body = self._raw_body 200 | 201 | try: 202 | self._body = json.loads(self._raw_body) 203 | except ValueError: 204 | # Do nothing 205 | pass 206 | 207 | @property 208 | def code(self): 209 | return self._code 210 | 211 | @property 212 | def body(self): 213 | return self._body 214 | 215 | @property 216 | def raw_body(self): 217 | return self._raw_body 218 | 219 | @property 220 | def headers(self): 221 | return self._headers 222 | -------------------------------------------------------------------------------- /unirest/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kong/unirest-python/6118d0eec3113cc0090e72e47df07c9661a40719/unirest/test/__init__.py -------------------------------------------------------------------------------- /unirest/test/test_unirest.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import sys 4 | import os 5 | import unittest 6 | 7 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) 8 | import unirest 9 | 10 | class UnirestTestCase(unittest.TestCase): 11 | def test_get(self): 12 | response = unirest.get('http://httpbin.org/get?name=Mark', params={"nick":"thefosk"}) 13 | self.assertEqual(response.code, 200) 14 | self.assertEqual(len(response.body['args']), 2) 15 | self.assertEqual(response.body['args']['name'], "Mark") 16 | self.assertEqual(response.body['args']['nick'], "thefosk") 17 | 18 | def test_get2(self): 19 | response = unirest.get('http://httpbin.org/get?name=Mark', params={"nick":"the fosk"}) 20 | self.assertEqual(response.code, 200) 21 | self.assertEqual(len(response.body['args']), 2) 22 | self.assertEqual(response.body['args']['name'], "Mark") 23 | self.assertEqual(response.body['args']['nick'], "the fosk") 24 | 25 | def test_get_unicode_param(self): 26 | response = unirest.get('http://httpbin.org/get?name=Shimada', params={"nick":u"しまりん"}) 27 | self.assertEqual(response.code, 200) 28 | self.assertEqual(len(response.body['args']), 2) 29 | self.assertEqual(response.body['args']['name'], "Shimada") 30 | self.assertEqual(response.body['args']['nick'], u"しまりん") 31 | 32 | def test_get_none_param(self): 33 | response = unirest.get('http://httpbin.org/get?name=Mark', params={"nick":"thefosk", "age": None, "third":""}) 34 | self.assertEqual(response.code, 200) 35 | self.assertEqual(len(response.body['args']), 3) 36 | self.assertEqual(response.body['args']['name'], "Mark") 37 | self.assertEqual(response.body['args']['nick'], "thefosk") 38 | self.assertEqual(response.body['args']['third'], "") 39 | 40 | def test_post(self): 41 | response = unirest.post('http://httpbin.org/post', params={"name":"Mark", "nick":"thefosk"}) 42 | self.assertEqual(response.code, 200) 43 | self.assertEqual(len(response.body['args']), 0) 44 | self.assertEqual(len(response.body['form']), 2) 45 | self.assertEqual(response.body['form']['name'], "Mark") 46 | self.assertEqual(response.body['form']['nick'], "thefosk") 47 | 48 | def test_post_none_param(self): 49 | response = unirest.post('http://httpbin.org/post', params={"name":"Mark", "nick":"thefosk", "age": None, "third":""}) 50 | self.assertEqual(response.code, 200) 51 | self.assertEqual(len(response.body['args']), 0) 52 | self.assertEqual(len(response.body['form']), 3) 53 | self.assertEqual(response.body['form']['name'], "Mark") 54 | self.assertEqual(response.body['form']['nick'], "thefosk") 55 | self.assertEqual(response.body['form']['third'], "") 56 | 57 | def test_delete(self): 58 | response = unirest.delete('http://httpbin.org/delete', params={"name":"Mark", "nick":"thefosk"}) 59 | self.assertEqual(response.code, 200) 60 | self.assertEqual(response.body['form']['name'], "Mark") 61 | self.assertEqual(response.body['form']['nick'], "thefosk") 62 | 63 | def test_put(self): 64 | response = unirest.put('http://httpbin.org/put', params={"name":"Mark", "nick":"thefosk"}) 65 | self.assertEqual(response.code, 200) 66 | self.assertEqual(len(response.body['args']), 0) 67 | self.assertEqual(len(response.body['form']), 2) 68 | self.assertEqual(response.body['form']['name'], "Mark") 69 | self.assertEqual(response.body['form']['nick'], "thefosk") 70 | 71 | def test_patch(self): 72 | response = unirest.patch('http://httpbin.org/patch', params={"name":"Mark", "nick":"thefosk"}) 73 | self.assertEqual(response.code, 200) 74 | self.assertEqual(len(response.body['args']), 0) 75 | self.assertEqual(len(response.body['form']), 2) 76 | self.assertEqual(response.body['form']['name'], "Mark") 77 | self.assertEqual(response.body['form']['nick'], "thefosk") 78 | 79 | def test_post_entity(self): 80 | response = unirest.post('http://httpbin.org/post', headers={'Content-Type':'text/plain'}, params="hello this is custom data") 81 | self.assertEqual(response.code, 200) 82 | self.assertEqual(response.body['data'], "hello this is custom data") 83 | 84 | def test_gzip(self): 85 | response = unirest.get('http://httpbin.org/gzip', params={"name":"Mark"}) 86 | self.assertEqual(response.code, 200) 87 | self.assertTrue(response.body['gzipped']) 88 | 89 | def test_basicauth(self): 90 | response = unirest.get('http://httpbin.org/get', auth=('marco', 'password')) 91 | self.assertEqual(response.code, 200) 92 | self.assertEqual(response.body['headers']['Authorization'], "Basic bWFyY286cGFzc3dvcmQ=") 93 | 94 | def test_defaultheaders(self): 95 | unirest.default_header('custom','custom header') 96 | response = unirest.get('http://httpbin.org/get') 97 | self.assertEqual(response.code, 200) 98 | self.assertTrue('Custom' in response.body['headers']); 99 | self.assertEqual(response.body['headers']['Custom'], "custom header") 100 | 101 | # Make another request 102 | response = unirest.get('http://httpbin.org/get') 103 | self.assertEqual(response.code, 200) 104 | self.assertTrue('Custom' in response.body['headers']); 105 | self.assertTrue(response.body['headers']['Custom'], "custom header") 106 | 107 | # Clear the default headers 108 | unirest.clear_default_headers() 109 | response = unirest.get('http://httpbin.org/get') 110 | self.assertEqual(response.code, 200) 111 | self.assertFalse('Custom' in response.body['headers']); 112 | 113 | def test_timeout(self): 114 | unirest.timeout(3) 115 | response = unirest.get('http://httpbin.org/delay/1') 116 | self.assertEqual(response.code, 200) 117 | 118 | unirest.timeout(1) 119 | try: 120 | response = unirest.get('http://httpbin.org/delay/3') 121 | self.fail("The timeout didn't work") 122 | except: 123 | pass 124 | 125 | if __name__ == '__main__': 126 | unittest.main() 127 | -------------------------------------------------------------------------------- /unirest/utils.py: -------------------------------------------------------------------------------- 1 | from poster.encode import multipart_encode 2 | import urllib 3 | 4 | def to_utf8(value): 5 | if isinstance(value, unicode): 6 | return urllib.quote_plus(value.encode('utf-8')) 7 | 8 | return value 9 | 10 | 11 | def _dictionary_encoder(key, dictionary): 12 | result = [] 13 | for k, v in dictionary.iteritems(): 14 | if type(v) is file: 15 | continue 16 | key = to_utf8(key) 17 | k = to_utf8(k) 18 | v = to_utf8(v) 19 | result.append('{}[{}]={}'.format(key, k, v)) 20 | 21 | return result 22 | 23 | 24 | def dict2query(dictionary): 25 | """ 26 | We want post vars of form: 27 | {'foo': 'bar', 'nested': {'a': 'b', 'c': 'd'}} 28 | to become: 29 | foo=bar&nested[a]=b&nested[c]=d 30 | """ 31 | query = [] 32 | encoders = {dict: _dictionary_encoder} 33 | for k, v in dictionary.iteritems(): 34 | if v.__class__ in encoders: 35 | nested_query = encoders[v.__class__](k, v) 36 | query += nested_query 37 | else: 38 | key = to_utf8(k) 39 | value = to_utf8(v) 40 | query.append('{}={}'.format(key, value)) 41 | 42 | return '&'.join(query) 43 | 44 | 45 | def urlencode(data): 46 | if isinstance(data, dict): 47 | for v in data.values(): 48 | if isinstance(v, file): 49 | return multipart_encode(data) 50 | return dict2query(data), None 51 | else: 52 | return data, None 53 | 54 | 55 | if __name__ == '__main__': 56 | print('...') 57 | print(dict2query({'foo': 'bar', 'nested': {'a': 'b', 'c': 'd'}})) 58 | --------------------------------------------------------------------------------