Compare commits

..

113 Commits

Author SHA1 Message Date
Kenneth Reitz
7215452da5 Merge branch 'release/0.3.3' 2011-05-12 04:02:16 -04:00
Kenneth Reitz
01b22a7e12 version bump 2011-05-12 04:02:06 -04:00
Kenneth Reitz
d28804cc48 update history 2011-05-12 03:57:17 -04:00
Kenneth Reitz
a046a04aa2 Merge branch 'feature/timeout' into develop 2011-05-12 03:56:13 -04:00
Kenneth Reitz
59dd3ed459 unicode urls 2011-05-12 03:20:27 -04:00
Kenneth Reitz
b6f6048cff Encode incoming data.
Closes #27.
2011-05-12 03:14:09 -04:00
Kenneth Reitz
8a4a05aac0 test for unicode url issues 2011-05-12 03:12:30 -04:00
Kenneth Reitz
64d6c3074f sphinx extension fix 2011-05-12 00:29:31 -04:00
Kenneth Reitz
45ca6d8a78 Merge pull request #26 from jgorset/requests
---

Ive implemented a `settings` context manager as described in [pull request #25](https://github.com/kennethreitz/requests/pull/25#issuecomment-1039541).

    >>> import requests
    >>> with requests.settings(timeout=0.5):
    ...     requests.get(http://example.org) # Times out after 0.5 seconds
    ...     requests.get(http://example.org, timeout=10) # Times out after 10 seconds

Settings may also be manipulated separately:

    >>> import requests
    >>> requests.timeout = 0.5
    >>> requests.get(http://example.org) # Times out after 0.5 seconds
2011-04-21 17:24:43 -04:00
Johannes
77f4b8a75e Implement settings context manager 2011-04-21 18:38:33 +02:00
Kenneth Reitz
122c4f0514 updated history w/ latest changes 2011-04-21 10:37:22 -04:00
Kenneth Reitz
83494152fe Merge branch 'timeout' of https://github.com/jgorset/requests into develop 2011-04-21 10:28:26 -04:00
Kenneth Reitz
f6f636f74c Merge pull request #24 from jgorset/requests
---

I made some insignificant corrections to the README.
2011-04-21 10:16:54 -04:00
Johannes
39e9424f01 Update AUTHORS 2011-04-21 14:55:50 +02:00
Johannes
d93731213a Implement optional timeout for request functions 2011-04-21 14:54:30 +02:00
Johannes
f31dc8a9ef Substitute 'request' for 'requests' 2011-04-21 14:17:02 +02:00
Johannes
4687951a04 Substitute <request object> for the actual return value of the request functions 2011-04-21 14:01:43 +02:00
Kenneth Reitz
576cba81d9 Merge branch 'master' into develop 2011-04-15 18:00:37 -04:00
Kenneth Reitz
d3a46c822f preliminary multidicts 2011-04-15 18:00:10 -04:00
Kenneth Reitz
ea86602765 Merge branch 'release/0.3.2' into develop 2011-04-15 17:29:38 -04:00
Kenneth Reitz
fec0d1a9a0 history changes 2011-04-15 17:29:26 -04:00
Kenneth Reitz
642c667566 Merge branch 'release/0.3.2' 2011-04-15 17:27:17 -04:00
Kenneth Reitz
b8b87d416e install simplejson if python < 2.6 2011-04-15 17:27:02 -04:00
Kenneth Reitz
6e14d4704d version bump 2011-04-15 17:26:51 -04:00
Kenneth Reitz
f494cd9c72 add autoauth tuple http basic test 2011-04-15 17:24:03 -04:00
Kenneth Reitz
2814664e91 Fixes #20. 2011-04-15 17:21:38 -04:00
Kenneth Reitz
1359094cc8 test for gzip decompress 2011-04-14 19:46:35 -04:00
Kenneth Reitz
7a62b10ff2 Automatically decompress gzipped responses if content-type is set. fixes #19 2011-04-14 19:44:29 -04:00
Kenneth Reitz
23d5761bd4 accept any extra args to r.read() 2011-04-02 18:22:05 -04:00
Kenneth Reitz
cc16073c36 Merge branch 'develop' 2011-04-01 14:55:24 -04:00
Kenneth Reitz
ed8ff63048 Version bump to 0.3.1 2011-04-01 14:53:53 -04:00
Kenneth Reitz
267a852ba6 Update history 2011-04-01 14:52:33 -04:00
Kenneth Reitz
f2b04f94ca Response.read() [file like object] 2011-03-31 04:47:02 -04:00
Kenneth Reitz
e350bea167 added Request.read 2011-03-31 04:46:19 -04:00
robmadole
2401f14975 Fixing User-agent header problem introduced with Python 2.7.1 2011-03-30 00:07:24 -05:00
Kenneth Reitz
be228043a1 whitespace fix 2011-03-21 20:45:37 -04:00
Kenneth Reitz
48ffb627fe added Aram Dulyan to authors 2011-03-09 13:22:47 -05:00
Aram Dulyan
89192c64f0 An empty cookiejar evaluates to False, so it needs to be compared to None for the cookie functionality to work. 2011-03-09 22:31:13 +11:00
Kenneth Reitz
a67cc5c5a9 someone stole the cookie jar 2011-03-06 19:19:22 -05:00
Kenneth Reitz
fa2f1c5f60 Merge branch 'develop' of https://github.com/robmadole/requests into develop 2011-03-06 19:08:32 -05:00
Kenneth Reitz
1d021c5cc1 Added Rob Madole to AUTHORS 2011-03-06 19:07:29 -05:00
Kenneth Reitz
e17111a5dd testing requests with poster and adding headers 2011-03-06 18:50:28 -05:00
Kenneth Reitz
9fdee250de Better POSTER header compatibility (Fixes #13) 2011-03-06 18:48:44 -05:00
Kenneth Reitz
c6acfef3d0 Added Justin Murphy to AUTHORS 2011-03-06 18:48:14 -05:00
robmadole
e4c690e7bc Initial support for cookies 2011-03-01 09:21:00 -06:00
robmadole
ee43ad2497 Ignore those compiled files 2011-03-01 09:15:14 -06:00
Kenneth Reitz
044252ea7b Merge branch 'release/0.3.0' 2011-02-25 09:00:23 -05:00
Kenneth Reitz
f8032e08ae tests cleanups 2011-02-25 08:58:24 -05:00
Kenneth Reitz
f0d2ac3dd5 Readme update 2011-02-25 08:58:17 -05:00
Kenneth Reitz
270898e521 Readme Updates 2011-02-25 08:48:09 -05:00
Kenneth Reitz
897dd3aaa8 whitespace 2011-02-25 08:11:17 -05:00
Kenneth Reitz
dd01f8661e hacking whitespace fixes 2011-02-25 08:11:10 -05:00
Kenneth Reitz
99c3970fd3 history 2011-02-25 08:04:03 -05:00
Kenneth Reitz
839c8cf150 postbin tests were broken 2011-02-25 08:00:23 -05:00
Kenneth Reitz
b252cd8403 allow files to be upload along with form data 2011-02-25 08:00:11 -05:00
Kenneth Reitz
b3b2b19212 hmmm 2011-02-25 07:39:19 -05:00
Kenneth Reitz
a74e954788 test improvements 2011-02-25 07:21:11 -05:00
Kenneth Reitz
32f75585ad url builder, cleanups 2011-02-25 07:20:59 -05:00
Kenneth Reitz
9a8b25d5fa makin' history 2011-02-25 07:20:37 -05:00
Kenneth Reitz
14ba99f975 prettier imports 2011-02-25 05:03:12 -05:00
Kenneth Reitz
ccb7fdd8c2 Version bump 2011-02-25 04:48:10 -05:00
Kenneth Reitz
9a1d90d184 Proper async module docs 2011-02-25 04:43:18 -05:00
Kenneth Reitz
22798ca32b Prepping for v0.3.0 release. 2011-02-25 04:40:33 -05:00
Kenneth Reitz
3fe4753d5d Cleanups 2011-02-24 02:06:48 -05:00
Kenneth Reitz
d91157c567 Merge branch 'develop' into merge/xero 2011-02-24 01:53:55 -05:00
Kenneth Reitz
e7b8e9371a merge changes 2011-02-24 01:51:53 -05:00
Kenneth Reitz
1f60e1004c Merge https://github.com/Digitalxero/requests into merge/xero 2011-02-24 01:45:44 -05:00
Kenneth Reitz
9e45d201f3 Added Dj Gilcrease to AUTHORS. 2011-02-24 01:38:58 -05:00
Kenneth Reitz
47ecb3662d !+= 2011-02-24 01:30:00 -05:00
Kenneth Reitz
7369e47f18 Added Flavio to AUTHORS 2011-02-24 01:29:51 -05:00
Flavio Percoco Premoli
6f5b64bca6 small workaround for issue #7 2011-02-23 08:49:08 +01:00
Kenneth Reitz
e788ca7ee6 Safer error response handling. 2011-02-21 14:36:41 -05:00
Kenneth Reitz
68dea921e7 Added HACKING file. 2011-02-21 02:25:01 -05:00
digitalxero
f96157d259 Fix a syntax error on py2.5 and pypy 2011-02-19 15:33:24 -05:00
digitalxero
718f45698d Reset copyright on the async module 2011-02-19 15:21:47 -05:00
digitalxero
15761fc126 Made it so people can add their own auth handlers and not just the 4 provided by Python 2011-02-19 15:07:58 -05:00
digitalxero
edfdef2396 Add in a proper AuthManager instead of the list version that was being used.
Added support for all Auth types that python supports
2011-02-19 15:04:56 -05:00
digitalxero
a9e18e9106 Update the README 2011-02-19 11:18:29 -05:00
digitalxero
3b82b06adc Added an async module to make it explicit to the user that they are using async calls. 2011-02-19 11:13:10 -05:00
digitalxero
d1c89333d0 Remove the eventles code, this should live in requests.async or something so the user explicitly requests it
Clean up the code to get rid of the unnecessary AuthObject class
Cleanup the send method
Cleanup the convenience methods by adding a request method that they all use.
2011-02-19 10:51:54 -05:00
Kenneth Reitz
e26570d0d8 Merge branch 'hotfix/date' into develop 2011-02-19 01:30:22 -05:00
Kenneth Reitz
8b3c4da2c7 Merge branch 'hotfix/date' 2011-02-19 01:30:10 -05:00
Kenneth Reitz
5c4aa1f4b7 history date fix 2011-02-19 01:30:03 -05:00
Kenneth Reitz
d2cdefa7df Merge branch 'release/0.2.4' 2011-02-19 01:03:39 -05:00
Kenneth Reitz
720d0b2c38 Version needs to be available externally. 2011-02-19 01:02:13 -05:00
Kenneth Reitz
d2584ff853 Production Ready! Added Python 2.5 to Support list. 2011-02-19 01:02:01 -05:00
Kenneth Reitz
433452f9f1 Simpler setup.py versioning. 2011-02-19 01:01:51 -05:00
Kenneth Reitz
a34e0bea8b Version Bump (v0.2.4) 2011-02-19 01:00:03 -05:00
Kenneth Reitz
f66b35454f v0.2.4 Release information 2011-02-19 00:59:56 -05:00
Kenneth Reitz
b5c2806e8c Roadmap update 2011-02-19 00:43:27 -05:00
Kenneth Reitz
5072399f7a Import fix for python2.5 2011-02-19 00:41:28 -05:00
Kenneth Reitz
736cb70e9a Merge branch 'develop' of github.com:kennethreitz/requests into develop 2011-02-19 00:38:09 -05:00
Kenneth Reitz
8eafb8d3df Added autoauth tests 2011-02-19 00:37:33 -05:00
Kenneth Reitz
e8234a0137 import fix 2011-02-18 16:13:49 -05:00
Kenneth Reitz
fbe0393eb3 cstring io fix for py3 2011-02-18 16:08:26 -05:00
Kenneth Reitz
b82e72eb04 cStringIO fix. 2011-02-18 16:00:34 -05:00
Kenneth Reitz
a7c87edebe screw 2.4 2011-02-18 15:57:50 -05:00
Kenneth Reitz
7e20f32b02 pypy configuration 2011-02-18 04:41:42 -05:00
Kenneth Reitz
4419e7183f no more test suite 2011-02-18 04:41:34 -05:00
Kenneth Reitz
6bde955170 Python 2.5 Fixes. 2011-02-18 04:39:53 -05:00
Kenneth Reitz
952970f403 Merge branch 'release/0.9.4' 2011-02-18 03:35:18 -05:00
Kenneth Reitz
d3911da653 tox update (usable now?) 2011-02-18 03:32:10 -05:00
Kenneth Reitz
7b6ed48a86 tox setup for 2.4-2.7 2011-02-17 19:38:40 -05:00
Kenneth Reitz
7229dbe033 Making everyone else happy. 2011-02-17 16:22:03 -05:00
Kenneth Reitz
015d6b78d5 cleaning up line lengths 2011-02-17 16:20:53 -05:00
Kenneth Reitz
f4c4bf3d0b Much nicer helper functions and constructors 2011-02-17 16:17:46 -05:00
Kenneth Reitz
efedbd9775 added test_suite.sh for CI 2011-02-17 13:14:03 -05:00
Kenneth Reitz
f4e03a5d32 gitignore update for jenkins 2011-02-17 13:13:52 -05:00
Kenneth Reitz
34ca692c9c from __future__ import absolute_imports 2011-02-16 12:47:58 -05:00
Kenneth Reitz
a5517960ea history indent fix for rest/pypi 2011-02-15 10:02:39 -05:00
Kenneth Reitz
fa42c12e5b I need autodoc. 2011-02-15 09:58:02 -05:00
Kenneth Reitz
8ac29b40e9 new documentation on Request changes. 2011-02-15 09:51:46 -05:00
Kenneth Reitz
adb53c2b4f Merge branch 'release/0.2.3' into develop 2011-02-15 09:46:58 -05:00
16 changed files with 1163 additions and 480 deletions

6
.gitignore vendored
View File

@@ -1,2 +1,6 @@
.idea
MANIFEST
MANIFEST
coverage.xml
nosetests.xml
pylint.txt
*.pyc

View File

@@ -12,3 +12,9 @@ Patches and Suggestions
- Various Pocoo Members
- Chris Adams
- Flavio Percoco Premoli
- Dj Gilcrease
- Justin Murphy
- Rob Madole
- Aram Dulyan
- Johannes Gorset

15
HACKING Normal file
View File

@@ -0,0 +1,15 @@
Where possible, please follow PEP8 with regard to coding style. Sometimes the
line length restriction is too hard to follow, so don't bend over backwards
there.
Triple-quotes should always be """, single quotes are ' unless using " would
result in less escaping within the string.
All modules, functions, and methods should be well documented reStructuredText
for Sphinx AutoDoc.
All functionality should be available in pure Python. Optional C (via Cython)
implementations may be written for performance reasons, but should never
replace the Python implementation.
Lastly, don't take yourself too seriously :)

View File

@@ -1,14 +1,57 @@
History
-------
0.3.3 (2011-05-12)
++++++++++++++++++
* Request timeouts
* Unicode url-encoded data
* Settings context manager and module
0.3.2 (2011-04-15)
++++++++++++++++++
* Automatic Decompression of GZip Encoded Content
* AutoAuth Support for Tupled HTTP Auth
0.3.1 (2011-04-01)
++++++++++++++++++
* Cookie Changes
* Response.read()
* Poster fix
0.3.0 (2011-02-25)
++++++++++++++++++
* Automatic Authentication API Change
* Smarter Query URL Parameterization
* Allow file uploads and POST data together
* New Authentication Manager System
- Simpler Basic HTTP System
- Supports all build-in urllib2 Auths
- Allows for custom Auth Handlers
0.2.4 (2011-02-19)
++++++++++++++++++
* Python 2.5 Support
* PyPy-c v1.4 Support
* Auto-Authentication tests
* Improved Request object constructor
0.2.3 (2011-02-15)
++++++++++++++++++
* New HTTPHandling Methods
- Reponse.__nonzero__ (false if bad HTTP Status)
- Response.ok (True if expected HTTP Status)
- Response.error (Logged HTTPError if bad HTTP Status)
- Reponse.raise_for_status() (Raises stored HTTPError)
- Reponse.__nonzero__ (false if bad HTTP Status)
- Response.ok (True if expected HTTP Status)
- Response.error (Logged HTTPError if bad HTTP Status)
- Reponse.raise_for_status() (Raises stored HTTPError)
0.2.2 (2011-02-14)

View File

@@ -1,11 +1,11 @@
Requests: The Simple (e.g. usable) HTTP Module
==============================================
Most existing Python modules for dealing HTTP requests are insane. I have to look up *everything* that I want to do. Most of my worst Python experiences are a result of the various built-in HTTP libraries (yes, even worse than Logging).
Most existing Python modules for dealing HTTP requests are insane. I have to look up *everything* that I want to do. Most of my worst Python experiences are a result of the various built-in HTTP libraries (yes, even worse than Logging).
But this one's different. This one's going to be awesome. And simple.
Really simple.
Really simple.
Features
--------
@@ -15,8 +15,8 @@ Features
+ Simple Data/Params Request Attachment
+ Simple Multipart File Uploads
+ CookieJar Support
- Simple Basic HTTP Authentication
- Simple Authentication
+ Simple URL + HTTP Auth Registry
@@ -30,23 +30,23 @@ It couldn't be simpler. ::
HTTPS? Basic Authentication? ::
>>> r = requests.get('https://convore.com/api/account/verify.json')
>>> r.status_code
401
Uh oh, we're not authorized! Let's add authentication. ::
>>> conv_auth = requests.AuthObject('requeststest', 'requeststest')
>>> conv_auth = ('requeststest', 'requeststest')
>>> r = requests.get('https://convore.com/api/account/verify.json', auth=conv_auth)
>>> r.status_code
200
200
>>> r.headers['content-type']
'application/json'
>>> r.content
'{"username": "requeststest", "url": "/users/requeststest/", "id": "9408", "img": "censored-long-url"}'
@@ -54,55 +54,67 @@ Uh oh, we're not authorized! Let's add authentication. ::
API
---
**Requests:**
All request functions return a Response object (see below).
If a {filename: fileobject} dictionary is passed in (files=...), a multipart_encode upload will be performed.
If CookieJar object is is passed in (cookies=...), the cookies will be sent with the request.
GET Requests
>>> request.get(url, params={}, headers={}, cookies=None, auth=None)
<request object>
>>> requests.get(url, params={}, headers={}, cookies=None, auth=None)
<Response [200]>
HEAD Requests
>>> request.head(url, params={}, headers={}, cookies=None, auth=None)
<request object>
>>> requests.head(url, params={}, headers={}, cookies=None, auth=None)
<Response [200]>
PUT Requests
>>> request.put(url, data='', headers={}, files={}, cookies=None, auth=None)
<request object>
>>> requests.put(url, data='', headers={}, files={}, cookies=None, auth=None)
<Response [200]>
POST Requests
>>> request.post(url, data={}, headers={}, files={}, cookies=None, auth=None)
<request object>
>>> requests.post(url, data={}, headers={}, files={}, cookies=None, auth=None)
<Response [200]>
DELETE Requests
>>> request.delete(url, params={}, headers={}, cookies=None, auth=None)
<request object>
>>> requests.delete(url, params={}, headers={}, cookies=None, auth=None)
<Response [200]>
**Responses:**
Request.status_code:
Response.status_code
(Integer) Received HTTP Status Code Response
Request.headers:
Response.headers
(Dictionary) Received HTTP Response Headers
Request.content:
Response.content
(Bytes) Received Content
Request.url
(String) URL of response. Useful for detecting redirects.
Response.url
(String) URL of response. Useful for detecting redirects.
Response.ok
(Bool) True if no errors occurred during the request, and the status_code is kosher.
Response.cached
(Bool) True if Response.content is stored within the object.
Response.error
(HTTPError) If an HTTPError occurred (e.g. status of 404), Otherwise this is None.
Response.raise_for_status()
Raises HTTPError if a request is not kosher.
**HTTP Authentication Registry:**
You can register AuthObjects to automatically enable HTTP Authentication on requests that contain a registered base URL string.
>>> requests.add_autoauth(url, authobject)
>>> requests.auth_manager.add_auth(url, authobject)
@@ -112,13 +124,13 @@ Installation
To install requests, simply: ::
$ pip install requests
Or, if you absolutely must: ::
$ easy_install requests
But, you really shouldn't do that.
Contribute
@@ -131,8 +143,10 @@ If you'd like to contribute, simply fork `the repository`_, commit your changes
Roadmap
-------
- Sphinx Documentation
- Exhaustive Unittests
- Sphinx Documentation (http://code.kennethreitz.com/requests/)
- Exhaustive unit tests
- Get rid of Poster (gets really nasty in py3.x)
- Python 3.x Support
.. _`the repository`: http://github.com/kennethreitz/requests
.. _AUTHORS: http://github.com/kennethreitz/requests/blob/master/AUTHORS

View File

@@ -25,7 +25,7 @@ import sys, os
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.jsmath', 'sphinx.ext.viewcode']
extensions = ['sphinx.ext.autodoc']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -48,9 +48,9 @@ copyright = u'2011, Kenneth Reitz'
# built documents.
#
# The short X.Y version.
version = '0.2.0'
version = '0.3.3'
# The full version, including alpha/beta/rc tags.
release = '0.2.0'
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@@ -1,4 +1,31 @@
# -*- coding: utf-8 -*-
from . import packages
from .core import *
import inspect
import packages
from core import *
from core import __version__
timeout = None
class settings:
"""Context manager for settings."""
cache = {}
def __init__(self, timeout):
self.module = inspect.getmodule(self)
# Cache settings
self.cache['timeout'] = self.module.timeout
self.module.timeout = timeout
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
# Restore settings
for key in self.cache:
setattr(self.module, key, self.cache[key])

48
requests/async.py Normal file
View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
"""
requests.async
~~~~~~~~~~~~~~
This module implements the main Requests system, after monkey-patching
the urllib2 module with eventlet or gevent..
:copyright: (c) 2011 by Kenneth Reitz.
:license: ISC, see LICENSE for more details.
"""
from __future__ import absolute_import
import urllib
import urllib2
from urllib2 import HTTPError
try:
import eventlet
eventlet.monkey_patch()
except ImportError:
pass
if not 'eventlet' in locals():
try:
from gevent import monkey
monkey.patch_all()
except ImportError:
pass
if not 'eventlet' in locals():
raise ImportError('No Async adaptations of urllib2 found!')
from .core import *
__all__ = [
'Request', 'Response', 'request', 'get', 'head', 'post', 'put', 'delete',
'auth_manager', 'AuthObject','RequestException', 'AuthenticationError',
'URLRequired', 'InvalidMethod', 'HTTPError'
]

View File

@@ -10,135 +10,172 @@
:license: ISC, see LICENSE for more details.
"""
from __future__ import absolute_import
import requests
import urllib
import urllib2
import socket
import zlib
from urllib2 import HTTPError
try:
import eventlet
eventlet.monkey_patch()
except ImportError:
pass
if not 'eventlet' in locals():
try:
from gevent import monkey
monkey.patch_all()
except ImportError:
pass
from urlparse import urlparse
from .packages.poster.encode import multipart_encode
from .packages.poster.streaminghttp import register_openers
from .packages.poster.streaminghttp import register_openers, get_handlers
__title__ = 'requests'
__version__ = '0.2.3'
__build__ = 0x000203
__version__ = '0.3.3'
__build__ = 0x000303
__author__ = 'Kenneth Reitz'
__license__ = 'ISC'
__copyright__ = 'Copyright 2011 Kenneth Reitz'
AUTOAUTHS = []
__all__ = [
'Request', 'Response', 'request', 'get', 'head', 'post', 'put', 'delete',
'auth_manager', 'AuthObject','RequestException', 'AuthenticationError',
'URLRequired', 'InvalidMethod', 'HTTPError'
]
class _Request(urllib2.Request):
"""Hidden wrapper around the urllib2.Request object. Allows for manual
"""Hidden wrapper around the urllib2.Request object. Allows for manual
setting of HTTP methods.
"""
def __init__(self, url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None):
urllib2.Request.__init__( self, url, data, headers, origin_req_host, unverifiable)
self.method = method
def get_method(self):
if self.method:
return self.method
def __init__(self, url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None):
urllib2.Request.__init__(self, url, data, headers, origin_req_host, unverifiable)
self.method = method
return urllib2.Request.get_method(self)
def get_method(self):
if self.method:
return self.method
return urllib2.Request.get_method(self)
class Request(object):
"""The :class:`Request` object. It carries out all functionality of
"""The :class:`Request` object. It carries out all functionality of
Requests. Recommended interface is with the Requests functions.
"""
_METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE')
def __init__(self):
self.url = None
self.headers = dict()
self.files = None
self.method = None
self.params = {}
self.data = {}
self.response = Response()
self.auth = None
self.cookiejar = None
self.sent = False
def __repr__(self):
return '<Request [%s]>' % (self.method)
def __setattr__(self, name, value):
if (name == 'method') and (value):
if not value in self._METHODS:
raise InvalidMethod()
object.__setattr__(self, name, value)
def _checks(self):
"""Deterministic checks for consistency."""
if not self.url:
raise URLRequired
_METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE')
def _get_opener(self):
"""Creates appropriate opener object for urllib2."""
def __init__(self, url=None, headers=dict(), files=None, method=None,
data=dict(), auth=None, cookiejar=None, timeout=None):
_handlers = []
self.url = url
self.headers = headers
self.files = files
self.method = method
self.data = data
if self.auth or self.cookiejar:
socket.setdefaulttimeout(timeout)
if self.auth:
for (k, v) in self.data.iteritems():
self.data[k] = v.encode('utf-8')
authr = urllib2.HTTPPasswordMgrWithDefaultRealm()
# url encode data if it's a dict
if hasattr(data, 'items'):
self._enc_data = urllib.urlencode(self.data)
else:
self._enc_data = self.data
authr.add_password(None, self.url, self.auth.username, self.auth.password)
auth_handler = urllib2.HTTPBasicAuthHandler(authr)
self.response = Response()
_handlers.append(auth_handler)
if self.cookiejar:
cookie_handler = urllib2.HTTPCookieProcessor(cookiejar)
_handlers.append(cookie_handler)
opener = urllib2.build_opener(*_handlers)
return opener.open
else:
return urllib2.urlopen
if isinstance(auth, (list, tuple)):
auth = AuthObject(*auth)
if not auth:
auth = auth_manager.get_auth(self.url)
self.auth = auth
self.cookiejar = cookiejar
self.sent = False
def _build_response(self, resp):
"""Build internal Response object from given response."""
self.response.status_code = resp.code
self.response.headers = resp.info().dict
self.response.content = resp.read()
self.response.url = resp.url
def __repr__(self):
return '<Request [%s]>' % (self.method)
def send(self, anyway=False):
"""Sends the request. Returns True of successful, false if not.
def __setattr__(self, name, value):
if (name == 'method') and (value):
if not value in self._METHODS:
raise InvalidMethod()
object.__setattr__(self, name, value)
def _checks(self):
"""Deterministic checks for consistency."""
if not self.url:
raise URLRequired
def _get_opener(self):
"""Creates appropriate opener object for urllib2."""
_handlers = []
if self.cookiejar is not None:
_handlers.append(urllib2.HTTPCookieProcessor(self.cookiejar))
if self.auth:
if not isinstance(self.auth.handler, (urllib2.AbstractBasicAuthHandler, urllib2.AbstractDigestAuthHandler)):
auth_manager.add_password(self.auth.realm, self.url, self.auth.username, self.auth.password)
self.auth.handler = self.auth.handler(auth_manager)
auth_manager.add_auth(self.url, self.auth)
_handlers.append(self.auth.handler)
if not _handlers:
return urllib2.urlopen
_handlers.extend(get_handlers())
opener = urllib2.build_opener(*_handlers)
if self.headers:
# Allow default headers in the opener to be overloaded
normal_keys = [k.capitalize() for k in self.headers]
for key, val in opener.addheaders[:]:
if key not in normal_keys:
continue
# Remove it, we have a value to take its place
opener.addheaders.remove((key, val))
return opener.open
def _build_response(self, resp):
"""Build internal Response object from given response."""
self.response.status_code = getattr(resp, 'code', None)
self.response.headers = getattr(resp.info(), 'dict', None)
self.response.content = resp.read()
if self.response.headers.get('content-encoding', None) == 'gzip':
try:
self.response.content = zlib.decompress(self.response.content, 16+zlib.MAX_WBITS)
except zlib.error:
pass
self.response.url = getattr(resp, 'url', None)
@staticmethod
def _build_url(url, data=None):
"""Build URLs."""
if urlparse(url).query:
return '%s&%s' % (url, data)
else:
if data:
return '%s?%s' % (url, data)
else:
return url
def send(self, anyway=False):
"""Sends the request. Returns True of successful, false if not.
If there was an HTTPError during transmission,
self.response.status_code will contain the HTTPError code.
@@ -147,326 +184,374 @@ class Request(object):
:param anyway: If True, request will be sent, even if it has
already been sent.
"""
self._checks()
success = False
self._checks()
if self.method in ('GET', 'HEAD', 'DELETE'):
req = _Request(self._build_url(self.url, self._enc_data), method=self.method)
else:
success = False
if self.files:
register_openers()
if self.data:
self.files.update(self.data)
datagen, headers = multipart_encode(self.files)
req = _Request(self.url, data=datagen, headers=headers, method=self.method)
else:
req = _Request(self.url, data=self._enc_data, method=self.method)
if self.headers:
req.headers.update(self.headers)
if not self.sent or anyway:
if self.method in ('GET', 'HEAD', 'DELETE'):
if (not self.sent) or anyway:
# url encode GET params if it's a dict
if isinstance(self.params, dict):
params = urllib.urlencode(self.params)
else:
params = self.params
try:
opener = self._get_opener()
resp = opener(req)
req = _Request(("%s?%s" % (self.url, params)), method=self.method)
if self.cookiejar is not None:
self.cookiejar.extract_cookies(resp, req)
if self.headers:
req.headers = self.headers
except urllib2.HTTPError, why:
self._build_response(why)
self.response.error = why
else:
self._build_response(resp)
self.response.ok = True
opener = self._get_opener()
self.response.cached = False
else:
self.response.cached = True
try:
resp = opener(req)
self._build_response(resp)
self.response.ok = True
self.sent = self.response.ok
except urllib2.HTTPError as why:
self._build_response(why)
self.response.error = why
return self.sent
elif self.method == 'PUT':
if (not self.sent) or anyway:
if self.files:
register_openers()
datagen, headers = multipart_encode(self.files)
req = _Request(self.url, data=datagen, headers=headers, method='PUT')
if self.headers:
req.headers.update(self.headers)
else:
req = _Request(self.url, method='PUT')
if self.headers:
req.headers = self.headers
req.data = self.data
try:
opener = self._get_opener()
resp = opener(req)
self._build_response(resp)
self.response.ok = True
except urllib2.HTTPError as why:
self._build_response(why)
self.response.error = why
def read(self, *args):
return self.response.read()
elif self.method == 'POST':
if (not self.sent) or anyway:
if self.files:
register_openers()
datagen, headers = multipart_encode(self.files)
req = _Request(self.url, data=datagen, headers=headers, method='POST')
if self.headers:
req.headers.update(self.headers)
else:
req = _Request(self.url, method='POST')
req.headers = self.headers
# url encode form data if it's a dict
if isinstance(self.data, dict):
req.data = urllib.urlencode(self.data)
else:
req.data = self.data
try:
opener = self._get_opener()
resp = opener(req)
self._build_response(resp)
self.response.ok = True
except urllib2.HTTPError as why:
self._build_response(why)
self.response.error = why
self.sent = self.response.ok
return self.sent
class Response(object):
"""The :class:`Request` object. All :class:`Request` objects contain a
"""The :class:`Request` object. All :class:`Request` objects contain a
:class:`Request.response <response>` attribute, which is an instance of
this class.
"""
def __init__(self):
self.content = None
self.status_code = None
self.headers = dict()
self.url = None
self.ok = False
self.error = None
def __repr__(self):
return '<Response [%s]>' % (self.status_code)
def __nonzero__(self):
"""Returns true if status_code is 'OK'."""
return not self.error
def raise_for_status(self):
"""Raises stored HTTPError if one exists."""
if self.error:
raise self.error
def __init__(self):
self.content = None
self.status_code = None
self.headers = dict()
self.url = None
self.ok = False
self.error = None
self.cached = False
def __repr__(self):
return '<Response [%s]>' % (self.status_code)
def __nonzero__(self):
"""Returns true if status_code is 'OK'."""
return not self.error
def raise_for_status(self):
"""Raises stored HTTPError if one exists."""
if self.error:
raise self.error
def read(self, *args):
return self.content
class AuthManager(object):
"""Authentication Manager."""
def __new__(cls):
singleton = cls.__dict__.get('__singleton__')
if singleton is not None:
return singleton
cls.__singleton__ = singleton = object.__new__(cls)
return singleton
def __init__(self):
self.passwd = {}
self._auth = {}
def __repr__(self):
return '<AuthManager [%s]>' % (self.method)
def add_auth(self, uri, auth):
"""Registers AuthObject to AuthManager."""
uri = self.reduce_uri(uri, False)
# try to make it an AuthObject
if not isinstance(auth, AuthObject):
try:
auth = AuthObject(*auth)
except TypeError:
pass
self._auth[uri] = auth
def add_password(self, realm, uri, user, passwd):
"""Adds password to AuthManager."""
# uri could be a single URI or a sequence
if isinstance(uri, basestring):
uri = [uri]
reduced_uri = tuple([self.reduce_uri(u, False) for u in uri])
if reduced_uri not in self.passwd:
self.passwd[reduced_uri] = {}
self.passwd[reduced_uri] = (user, passwd)
def find_user_password(self, realm, authuri):
for uris, authinfo in self.passwd.iteritems():
reduced_authuri = self.reduce_uri(authuri, False)
for uri in uris:
if self.is_suburi(uri, reduced_authuri):
return authinfo
return (None, None)
def get_auth(self, uri):
(in_domain, in_path) = self.reduce_uri(uri, False)
for domain, path, authority in (
(i[0][0], i[0][1], i[1]) for i in self._auth.iteritems()
):
if in_domain == domain:
if path in in_path:
return authority
def reduce_uri(self, uri, default_port=True):
"""Accept authority or URI and extract only the authority and path."""
# note HTTP URLs do not have a userinfo component
parts = urllib2.urlparse.urlsplit(uri)
if parts[1]:
# URI
scheme = parts[0]
authority = parts[1]
path = parts[2] or '/'
else:
# host or host:port
scheme = None
authority = uri
path = '/'
host, port = urllib2.splitport(authority)
if default_port and port is None and scheme is not None:
dport = {"http": 80,
"https": 443,
}.get(scheme)
if dport is not None:
authority = "%s:%d" % (host, dport)
return authority, path
def is_suburi(self, base, test):
"""Check if test is below base in a URI tree
Both args must be URIs in reduced form.
"""
if base == test:
return True
if base[0] != test[0]:
return False
common = urllib2.posixpath.commonprefix((base[1], test[1]))
if len(common) == len(base[1]):
return True
return False
def empty(self):
self.passwd = {}
def remove(self, uri, realm=None):
# uri could be a single URI or a sequence
if isinstance(uri, basestring):
uri = [uri]
for default_port in True, False:
reduced_uri = tuple([self.reduce_uri(u, default_port) for u in uri])
del self.passwd[reduced_uri][realm]
def __contains__(self, uri):
# uri could be a single URI or a sequence
if isinstance(uri, basestring):
uri = [uri]
uri = tuple([self.reduce_uri(u, False) for u in uri])
if uri in self.passwd:
return True
return False
auth_manager = AuthManager()
class AuthObject(object):
"""The :class:`AuthObject` is a simple HTTP Authentication token. When
"""The :class:`AuthObject` is a simple HTTP Authentication token. When
given to a Requests function, it enables Basic HTTP Authentication for that
Request. You can also enable Authorization for domain realms with AutoAuth.
See AutoAuth for more details.
:param username: Username to authenticate with.
:param password: Password for given username.
:param realm: (optional) the realm this auth applies to
:param handler: (optional) basic || digest || proxy_basic || proxy_digest
"""
def __init__(self, username, password):
self.username = username
self.password = password
_handlers = {
'basic': urllib2.HTTPBasicAuthHandler,
'digest': urllib2.HTTPDigestAuthHandler,
'proxy_basic': urllib2.ProxyBasicAuthHandler,
'proxy_digest': urllib2.ProxyDigestAuthHandler
}
def __init__(self, username, password, handler='basic', realm=None):
self.username = username
self.password = password
self.realm = realm
if isinstance(handler, basestring):
self.handler = self._handlers.get(handler.lower(), urllib2.HTTPBasicAuthHandler)
else:
self.handler = handler
def get(url, params={}, headers={}, cookies=None, auth=None):
"""Sends a GET request. Returns :class:`Response` object.
def request(method, url, **kwargs):
"""Sends a `method` request. Returns :class:`Response` object.
:param method: method for the new :class:`Request` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary of GET/HEAD/DELETE Parameters to send with the :class:`Request`.
:param data: (optional) Bytes/Dictionary of PUT/POST Data to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
"""
data = kwargs.pop('data', dict()) or kwargs.pop('params', dict())
r = Request(method=method, url=url, data=data, headers=kwargs.pop('headers', {}),
cookiejar=kwargs.pop('cookies', None), files=kwargs.pop('files', None),
auth=kwargs.pop('auth', auth_manager.get_auth(url)),
timeout=kwargs.pop('timeout', requests.timeout))
r.send()
return r.response
def get(url, params={}, headers={}, cookies=None, auth=None, **kwargs):
"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
"""
r = Request()
r.method = 'GET'
r.url = url
r.params = params
r.headers = headers
r.cookiejar = cookies
r.auth = _detect_auth(url, auth)
r.send()
return r.response
return request('GET', url, params=params, headers=headers, cookies=cookies, auth=auth, **kwargs)
def head(url, params={}, headers={}, cookies=None, auth=None):
"""Sends a HEAD request. Returns :class:`Response` object.
def head(url, params={}, headers={}, cookies=None, auth=None, **kwargs):
"""Sends a HEAD request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
"""
r = Request()
r.method = 'HEAD'
r.url = url
# return response object
r.params = params
r.headers = headers
r.cookiejar = cookies
r.auth = _detect_auth(url, auth)
r.send()
return r.response
return request('HEAD', url, params=params, headers=headers, cookies=cookies, auth=auth, **kwargs)
def post(url, data={}, headers={}, files=None, cookies=None, auth=None):
"""Sends a POST request. Returns :class:`Response` object.
def post(url, data={}, headers={}, files=None, cookies=None, auth=None, **kwargs):
"""Sends a POST request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary of POST Data to send with the :class:`Request`.
:param data: (optional) Dictionary of POST data to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
"""
r = Request()
r.url = url
r.method = 'POST'
r.data = data
if files:
r.files = files
r.headers = headers
r.cookiejar = cookies
r.auth = _detect_auth(url, auth)
r.send()
return r.response
def put(url, data='', headers={}, files={}, cookies=None, auth=None):
"""Sends a PUT request. Returns :class:`Response` object.
return request('POST', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth, **kwargs)
def put(url, data='', headers={}, files={}, cookies=None, auth=None, **kwargs):
"""Sends a PUT request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Bytes of PUT Data to send with the :class:`Request`.
:param params: (optional) Bytes of PUT Data to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
"""
r = Request()
r.url = url
r.method = 'PUT'
r.data = data
r.files = files
r.headers = headers
r.cookiejar = cookies
r.auth = _detect_auth(url, auth)
r.send()
return r.response
return request('PUT', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth, **kwargs)
def delete(url, params={}, headers={}, cookies=None, auth=None):
"""Sends a DELETE request. Returns :class:`Response` object.
def delete(url, params={}, headers={}, cookies=None, auth=None, **kwargs):
"""Sends a DELETE request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`.
:param params: (optional) Dictionary of DELETE Parameters to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
"""
r = Request()
r.url = url
r.method = 'DELETE'
r.headers = headers
r.cookiejar = cookies
r.auth = _detect_auth(url, auth)
r.send()
return r.response
def add_autoauth(url, authobject):
"""Registers given AuthObject to given URL domain. for auto-activation.
Once a URL is registered with an AuthObject, the configured HTTP
Authentication will be used for all requests with URLS containing the given
URL string.
Example: ::
>>> c_auth = requests.AuthObject('kennethreitz', 'xxxxxxx')
>>> requests.add_autoauth('https://convore.com/api/', c_auth)
>>> r = requests.get('https://convore.com/api/account/verify.json')
# Automatically HTTP Authenticated! Wh00t!
:param url: Base URL for given AuthObject to auto-activate for.
:param authobject: AuthObject to auto-activate.
:param timeout: (optional) Float describing the timeout of the request.
"""
global AUTOAUTHS
AUTOAUTHS.append((url, authobject))
return request('DELETE', url, params=params, headers=headers, cookies=cookies, auth=auth, **kwargs)
def _detect_auth(url, auth):
"""Returns registered AuthObject for given url if available, defaulting to
given AuthObject.
"""
return _get_autoauth(url) if not auth else auth
def _get_autoauth(url):
"""Returns registered AuthObject for given url if available."""
for (autoauth_url, auth) in AUTOAUTHS:
if autoauth_url in url:
return auth
return None
class RequestException(Exception):
"""There was an ambiguous exception that occured while handling your request."""
"""There was an ambiguous exception that occured while handling your
request."""
class AuthenticationError(RequestException):
"""The authentication credentials provided were invalid."""
"""The authentication credentials provided were invalid."""
class URLRequired(RequestException):
"""A valid URL is required to make a request."""
"""A valid URL is required to make a request."""
class InvalidMethod(RequestException):
"""An inappropriate method was attempted."""
"""An inappropriate method was attempted."""

View File

@@ -1 +1,3 @@
from __future__ import absolute_import
from . import poster

View File

@@ -26,6 +26,8 @@ New releases of poster will always have a version number that compares greater
than an older version of poster.
New in version 0.6."""
from __future__ import absolute_import
from . import streaminghttp
from . import encode

View File

@@ -181,16 +181,18 @@ if hasattr(httplib, 'HTTPS'):
return urllib2.HTTPSHandler.do_request_(self, req)
def get_handlers():
handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler]
if hasattr(httplib, "HTTPS"):
handlers.append(StreamingHTTPSHandler)
return handlers
def register_openers():
"""Register the streaming http handlers in the global urllib2 default
opener object.
Returns the created OpenerDirector object."""
handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler]
if hasattr(httplib, "HTTPS"):
handlers.append(StreamingHTTPSHandler)
opener = urllib2.build_opener(*handlers)
opener = urllib2.build_opener(*get_handlers())
urllib2.install_opener(opener)

360
requests/structures.py Normal file
View File

@@ -0,0 +1,360 @@
# from: werkzeug
class TypeConversionDict(dict):
"""Works like a regular dict but the :meth:`get` method can perform
type conversions. :class:`MultiDict` and :class:`CombinedMultiDict`
are subclasses of this class and provide the same feature.
"""
def get(self, key, default=None, type=None):
"""Return the default value if the requested data doesn't exist.
If `type` is provided and is a callable it should convert the value,
return it or raise a :exc:`ValueError` if that is not possible. In
this case the function will return the default as if the value was not
found:
>>> d = TypeConversionDict(foo='42', bar='blub')
>>> d.get('foo', type=int)
42
>>> d.get('bar', -1, type=int)
-1
:param key: The key to be looked up.
:param default: The default value to be returned if the key can't
be looked up. If not further specified `None` is
returned.
:param type: A callable that is used to cast the value in the
:class:`MultiDict`. If a :exc:`ValueError` is raised
by this callable the default value is returned.
"""
try:
rv = self[key]
if type is not None:
rv = type(rv)
except (KeyError, ValueError):
rv = default
return rv
# from: werkzeug
class MultiDict(TypeConversionDict):
"""A :class:`MultiDict` is a dictionary subclass customized to deal with
multiple values for the same key which is for example used by the parsing
functions in the wrappers. This is necessary because some HTML form
elements pass multiple values for the same key.
:class:`MultiDict` implements all standard dictionary methods.
Internally, it saves all values for a key as a list, but the standard dict
access methods will only return the first value for a key. If you want to
gain access to the other values, too, you have to use the `list` methods as
explained below.
Basic Usage:
>>> d = MultiDict([('a', 'b'), ('a', 'c')])
>>> d
MultiDict([('a', 'b'), ('a', 'c')])
>>> d['a']
'b'
>>> d.getlist('a')
['b', 'c']
>>> 'a' in d
True
It behaves like a normal dict thus all dict functions will only return the
first value when multiple values for one key are found.
From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will
render a page for a ``400 BAD REQUEST`` if caught in a catch-all for HTTP
exceptions.
A :class:`MultiDict` can be constructed from an iterable of
``(key, value)`` tuples, a dict, a :class:`MultiDict` or from Werkzeug 0.2
onwards some keyword parameters.
:param mapping: the initial value for the :class:`MultiDict`. Either a
regular dict, an iterable of ``(key, value)`` tuples
or `None`.
"""
# the key error this class raises. Because of circular dependencies
# with the http exception module this class is created at the end of
# this module.
KeyError = None
def __init__(self, mapping=None):
if isinstance(mapping, MultiDict):
dict.__init__(self, ((k, l[:]) for k, l in mapping.iterlists()))
elif isinstance(mapping, dict):
tmp = {}
for key, value in mapping.iteritems():
if isinstance(value, (tuple, list)):
value = list(value)
else:
value = [value]
tmp[key] = value
dict.__init__(self, tmp)
else:
tmp = {}
for key, value in mapping or ():
tmp.setdefault(key, []).append(value)
dict.__init__(self, tmp)
def __getstate__(self):
return dict(self.lists())
def __setstate__(self, value):
dict.clear(self)
dict.update(self, value)
def __iter__(self):
return self.iterkeys()
def __getitem__(self, key):
"""Return the first data value for this key;
raises KeyError if not found.
:param key: The key to be looked up.
:raise KeyError: if the key does not exist.
"""
if key in self:
return dict.__getitem__(self, key)[0]
raise self.KeyError(key)
def __setitem__(self, key, value):
"""Like :meth:`add` but removes an existing key first.
:param key: the key for the value.
:param value: the value to set.
"""
dict.__setitem__(self, key, [value])
def add(self, key, value):
"""Adds a new value for the key.
.. versionadded:: 0.6
:param key: the key for the value.
:param value: the value to add.
"""
dict.setdefault(self, key, []).append(value)
def getlist(self, key, type=None):
"""Return the list of items for a given key. If that key is not in the
`MultiDict`, the return value will be an empty list. Just as `get`
`getlist` accepts a `type` parameter. All items will be converted
with the callable defined there.
:param key: The key to be looked up.
:param type: A callable that is used to cast the value in the
:class:`MultiDict`. If a :exc:`ValueError` is raised
by this callable the value will be removed from the list.
:return: a :class:`list` of all the values for the key.
"""
try:
rv = dict.__getitem__(self, key)
except KeyError:
return []
if type is None:
return list(rv)
result = []
for item in rv:
try:
result.append(type(item))
except ValueError:
pass
return result
def setlist(self, key, new_list):
"""Remove the old values for a key and add new ones. Note that the list
you pass the values in will be shallow-copied before it is inserted in
the dictionary.
>>> d = MultiDict()
>>> d.setlist('foo', ['1', '2'])
>>> d['foo']
'1'
>>> d.getlist('foo')
['1', '2']
:param key: The key for which the values are set.
:param new_list: An iterable with the new values for the key. Old values
are removed first.
"""
dict.__setitem__(self, key, list(new_list))
def setdefault(self, key, default=None):
"""Returns the value for the key if it is in the dict, otherwise it
returns `default` and sets that value for `key`.
:param key: The key to be looked up.
:param default: The default value to be returned if the key is not
in the dict. If not further specified it's `None`.
"""
if key not in self:
self[key] = default
else:
default = self[key]
return default
def setlistdefault(self, key, default_list=None):
"""Like `setdefault` but sets multiple values. The list returned
is not a copy, but the list that is actually used internally. This
means that you can put new values into the dict by appending items
to the list:
>>> d = MultiDict({"foo": 1})
>>> d.setlistdefault("foo").extend([2, 3])
>>> d.getlist("foo")
[1, 2, 3]
:param key: The key to be looked up.
:param default: An iterable of default values. It is either copied
(in case it was a list) or converted into a list
before returned.
:return: a :class:`list`
"""
if key not in self:
default_list = list(default_list or ())
dict.__setitem__(self, key, default_list)
else:
default_list = dict.__getitem__(self, key)
return default_list
def items(self, multi=False):
"""Return a list of ``(key, value)`` pairs.
:param multi: If set to `True` the list returned will have a
pair for each value of each key. Otherwise it
will only contain pairs for the first value of
each key.
:return: a :class:`list`
"""
return list(self.iteritems(multi))
def lists(self):
"""Return a list of ``(key, values)`` pairs, where values is the list of
all values associated with the key.
:return: a :class:`list`
"""
return list(self.iterlists())
def values(self):
"""Returns a list of the first value on every key's value list.
:return: a :class:`list`.
"""
return [self[key] for key in self.iterkeys()]
def listvalues(self):
"""Return a list of all values associated with a key. Zipping
:meth:`keys` and this is the same as calling :meth:`lists`:
>>> d = MultiDict({"foo": [1, 2, 3]})
>>> zip(d.keys(), d.listvalues()) == d.lists()
True
:return: a :class:`list`
"""
return list(self.iterlistvalues())
def iteritems(self, multi=False):
"""Like :meth:`items` but returns an iterator."""
for key, values in dict.iteritems(self):
if multi:
for value in values:
yield key, value
else:
yield key, values[0]
def iterlists(self):
"""Like :meth:`items` but returns an iterator."""
for key, values in dict.iteritems(self):
yield key, list(values)
def itervalues(self):
"""Like :meth:`values` but returns an iterator."""
for values in dict.itervalues(self):
yield values[0]
def iterlistvalues(self):
"""Like :meth:`listvalues` but returns an iterator."""
return dict.itervalues(self)
def copy(self):
"""Return a shallow copy of this object."""
return self.__class__(self)
def to_dict(self, flat=True):
"""Return the contents as regular dict. If `flat` is `True` the
returned dict will only have the first item present, if `flat` is
`False` all values will be returned as lists.
:param flat: If set to `False` the dict returned will have lists
with all the values in it. Otherwise it will only
contain the first value for each key.
:return: a :class:`dict`
"""
if flat:
return dict(self.iteritems())
return dict(self.lists())
def update(self, other_dict):
"""update() extends rather than replaces existing key lists."""
for key, value in iter_multi_items(other_dict):
MultiDict.add(self, key, value)
def pop(self, key, default=_missing):
"""Pop the first item for a list on the dict. Afterwards the
key is removed from the dict, so additional values are discarded:
>>> d = MultiDict({"foo": [1, 2, 3]})
>>> d.pop("foo")
1
>>> "foo" in d
False
:param key: the key to pop.
:param default: if provided the value to return if the key was
not in the dictionary.
"""
try:
return dict.pop(self, key)[0]
except KeyError, e:
if default is not _missing:
return default
raise self.KeyError(str(e))
def popitem(self):
"""Pop an item from the dict."""
try:
item = dict.popitem(self)
return (item[0], item[1][0])
except KeyError, e:
raise self.KeyError(str(e))
def poplist(self, key):
"""Pop the list for a key from the dict. If the key is not in the dict
an empty list is returned.
.. versionchanged:: 0.5
If the key does no longer exist a list is returned instead of
raising an error.
"""
return dict.pop(self, key, [])
def popitemlist(self):
"""Pop a ``(key, list)`` tuple from the dict."""
try:
return dict.popitem(self)
except KeyError, e:
raise self.KeyError(str(e))
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.items(multi=True))

View File

@@ -3,49 +3,51 @@
import os
import sys
import requests
from distutils.core import setup
if sys.argv[-1] == "publish":
os.system("python setup.py sdist upload")
sys.exit()
os.system("python setup.py sdist upload")
sys.exit()
if sys.argv[-1] == "test":
os.system("python test_requests.py")
sys.exit()
os.system("python test_requests.py")
sys.exit()
required = []
# if python > 2.6, require simplejson
if sys.version_info[:2] < (2,6):
required.append('simplejson')
setup(
name='requests',
version='0.2.3',
description='Awesome Python HTTP Library that\'s actually usable.',
long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(),
author='Kenneth Reitz',
author_email='me@kennethreitz.com',
url='https://github.com/kennethreitz/requests',
packages= [
'requests',
'requests.packages',
'requests.packages.poster'
],
install_requires=required,
license='ISC',
classifiers=(
# 'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Natural Language :: English',
'License :: OSI Approved :: ISC License (ISCL)',
'Programming Language :: Python',
# 'Programming Language :: Python :: 2.5',
name='requests',
version=requests.__version__,
description='Awesome Python HTTP Library that\'s actually usable.',
long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(),
author='Kenneth Reitz',
author_email='me@kennethreitz.com',
url='https://github.com/kennethreitz/requests',
packages= [
'requests',
'requests.packages',
'requests.packages.poster'
],
install_requires=required,
license='ISC',
classifiers=(
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Natural Language :: English',
'License :: OSI Approved :: ISC License (ISCL)',
'Programming Language :: Python',
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
# 'Programming Language :: Python :: 3.0',
# 'Programming Language :: Python :: 3.1',
),
'Programming Language :: Python :: 2.7',
# 'Programming Language :: Python :: 3.0',
# 'Programming Language :: Python :: 3.1',
),
)

176
test_requests.py Normal file → Executable file
View File

@@ -2,86 +2,148 @@
# -*- coding: utf-8 -*-
import unittest
from cStringIO import StringIO
import cookielib
import requests
class RequestsTestSuite(unittest.TestCase):
"""Requests test cases."""
def setUp(self):
pass
"""Requests test cases."""
def tearDown(self):
"""Teardown."""
pass
def test_invalid_url(self):
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
def setUp(self):
pass
def tearDown(self):
"""Teardown."""
pass
def test_invalid_url(self):
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
def test_HTTP_200_OK_GET(self):
r = requests.get('http://google.com')
self.assertEqual(r.status_code, 200)
def test_HTTPS_200_OK_GET(self):
r = requests.get('https://google.com')
self.assertEqual(r.status_code, 200)
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
heads = {'User-agent': 'Mozilla/5.0'}
r = requests.get('http://www.google.com/search', params={'q': 'test'}, headers=heads)
self.assertEqual(r.status_code, 200)
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
heads = {'User-agent': 'Mozilla/5.0'}
r = requests.get('http://google.com/search?test=true', params={'q': 'test'}, headers=heads)
self.assertEqual(r.status_code, 200)
def test_user_agent_transfers(self):
"""Issue XX"""
heads = {'User-agent':
'Mozilla/5.0 (github.com/kennethreitz/requests)'}
r = requests.get('http://whatsmyua.com', headers=heads);
self.assertTrue(heads['User-agent'] in r.content)
def test_HTTP_200_OK_HEAD(self):
r = requests.head('http://google.com')
self.assertEqual(r.status_code, 200)
def test_HTTPS_200_OK_HEAD(self):
r = requests.head('https://google.com')
self.assertEqual(r.status_code, 200)
def test_AUTH_HTTPS_200_OK_GET(self):
auth = ('requeststest', 'requeststest')
url = 'https://convore.com/api/account/verify.json'
r = requests.get(url, auth=auth)
self.assertEqual(r.status_code, 200)
r = requests.get(url)
self.assertEqual(r.status_code, 200)
# reset auto authentication
requests.auth_manager.empty()
def test_POSTBIN_GET_POST_FILES(self):
bin = requests.post('http://www.postbin.org/')
self.assertEqual(bin.status_code, 200)
post = requests.post(bin.url, data={'some': 'data'})
self.assertEqual(post.status_code, 201)
post2 = requests.post(bin.url, files={'some': open('test_requests.py')})
self.assertEqual(post2.status_code, 201)
def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
bin = requests.post('http://www.postbin.org/')
self.assertEqual(bin.status_code, 200)
post2 = requests.post(bin.url, files={'some': open('test_requests.py')}, data={'some': 'data'})
self.assertEqual(post2.status_code, 201)
def test_HTTP_200_OK_GET(self):
r = requests.get('http://google.com')
self.assertEqual(r.status_code, 200)
def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):
bin = requests.post('http://www.postbin.org/')
self.assertEqual(bin.status_code, 200)
post2 = requests.post(bin.url, files={'some': open('test_requests.py')},
headers={'User-Agent': 'requests-tests'})
def test_HTTPS_200_OK_GET(self):
r = requests.get('https://google.com')
self.assertEqual(r.status_code, 200)
self.assertEqual(post2.status_code, 201)
def test_nonzero_evaluation(self):
r = requests.get('http://google.com/some-404-url')
self.assertEqual(bool(r), False)
def test_HTTP_200_OK_HEAD(self):
r = requests.head('http://google.com')
self.assertEqual(r.status_code, 200)
r = requests.get('http://google.com/')
self.assertEqual(bool(r), True)
def test_request_ok_set(self):
r = requests.get('http://google.com/some-404-url')
self.assertEqual(r.ok, False)
def test_HTTPS_200_OK_HEAD(self):
r = requests.head('https://google.com')
self.assertEqual(r.status_code, 200)
def test_status_raising(self):
r = requests.get('http://google.com/some-404-url')
self.assertRaises(requests.HTTPError, r.raise_for_status)
r = requests.get('http://google.com/')
self.assertFalse(r.error)
r.raise_for_status()
def test_AUTH_HTTPS_200_OK_GET(self):
auth = requests.AuthObject('requeststest', 'requeststest')
url = 'https://convore.com/api/account/verify.json'
r = requests.get(url, auth=auth)
def test_cookie_jar(self):
"""
.. todo:: This really doesn't test to make sure the cookie is working
"""
jar = cookielib.CookieJar()
self.assertFalse(jar)
self.assertEqual(r.status_code, 200)
requests.get('http://google.com', cookies=jar)
self.assertTrue(jar)
def test_decompress_gzip(self):
def test_POSTBIN_GET_POST_FILES(self):
r = requests.get('http://api.stackoverflow.com/1.1/users/495995/top-answer-tags')
r.content.decode('ascii')
bin = requests.post('http://www.postbin.org/')
self.assertEqual(bin.status_code, 200)
def test_autoauth(self):
post = requests.post(bin.url, data={'some': 'data'})
self.assertEqual(post.status_code, 201)
conv_auth = ('requeststest', 'requeststest')
requests.auth_manager.add_auth('convore.com', conv_auth)
post2 = requests.post(bin.url, files={'some': StringIO('data')})
self.assertEqual(post2.status_code, 201)
r = requests.get('https://convore.com/api/account/verify.json')
self.assertEquals(r.status_code, 200)
def test_nonzero_evaluation(self):
r = requests.get('http://google.com/some-404-url')
self.assertEqual(bool(r), False)
r = requests.get('http://google.com/')
self.assertEqual(bool(r), True)
def test_request_ok_set(self):
r = requests.get('http://google.com/some-404-url')
self.assertEqual(r.ok, False)
def test_status_raising(self):
r = requests.get('http://google.com/some-404-url')
self.assertRaises(requests.HTTPError, r.raise_for_status)
r = requests.get('http://google.com/')
self.assertFalse(r.error)
r.raise_for_status()
def test_unicode_get(self):
requests.get('http://google.com', params={'foo': u'føø'})
requests.get('http://google.com', params={'foo': u'foo'})
requests.get('http://google.com/ø', params={'foo': u'foo'})
if __name__ == '__main__':
unittest.main()
unittest.main()

11
tox.ini Normal file
View File

@@ -0,0 +1,11 @@
[tox]
envlist = py25,py26,py27,pypy
[testenv]
commands=py.test --junitxml=junit-{envname}.xml
deps =
pytest
[testenv:pypy]
basepython=/usr/bin/pypy-c