Merge branch 'develop'

This commit is contained in:
Kenneth Reitz
2011-10-19 01:31:32 -04:00
9 changed files with 186 additions and 85 deletions
+1
View File
@@ -7,3 +7,4 @@ pylint.txt
docs/_build
toy.py
.gitignore
junit-report.xml
+7
View File
@@ -1,6 +1,13 @@
History
-------
0.6.5 (2011-10-19)
++++++++++++++++++
* Offline (fast) test suite.
* Session dictionary argument merging.
0.6.4 (2011-10-13)
++++++++++++++++++
+4 -2
View File
@@ -2,10 +2,12 @@ init:
pip install -r reqs.txt
test:
nosetests test_requests.py --processes=30
nosetests test_requests.py --with-color
ci: init
nosetests --search-test --processes=30 --with-nosexunit test_requests.py
nosetests test_requests.py --with-xunit --xunit-file=junit-report.xml
stats:
pyflakes requests | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' > violations.pyflakes.txt
site:
+63
View File
@@ -0,0 +1,63 @@
Modules
=======
- `requests-oauth-hook <https://github.com/maraujop/requests-oauth-hook>`_, adds OAuth support to Requests.
- `FacePy <https://github.com/jgorset/facepy>`_, a Python wrapper to the Facebook API.
- `robotframework-requests <https://github.com/bulkan/robotframework-requests>`_, a Robot Framework API wrapper.
- `fullerene <https://github.com/bitprophet/fullerene>`_, a Graphite Dashboard.
- `urbanairship-python <https://github.com/benjaminws/urbanairship-python>`_, a fork of the Urban Airship API wrapper.
Articles & Talks
================
- `Python for the Web <http://gun.io/blog/python-for-the-web/>`_ teaches how to use Python to interact with the web, using Requests.
- `Daniel Greenfield's Review of Requests <http://pydanny.blogspot.com/2011/05/python-http-requests-for-humans.html>`_
- `My 'Python for Humans' talk <http://python-for-humans.heroku.com>`_ ( `audio <http://codeconf.s3.amazonaws.com/2011/pycodeconf/talks/PyCodeConf2011%20-%20Kenneth%20Reitz.m4a>`_ )
- `Issac Kelly's 'Consuming Web APIs' talk <http://issackelly.github.com/Consuming-Web-APIs-with-Python-Talk/slides/slides.html>`_
- `Blog post about Requests via Yum <http://arunsag.wordpress.com/2011/08/17/new-package-python-requests-http-for-humans/>`_
- `Russian blog post introducing Requests <http://habrahabr.ru/blogs/python/126262/>`_
Integrations
============
ScraperWiki
------------
`ScraperWiki <https://scraperwiki.com/>`_ is an excellent service that allows
you to run Python, Ruby, and PHP scraper scripts on the web. Now, Requests
v0.6.1 is available to use in your scrapers!
To give it a try, simply::
import requests
Managed Packages
================
Requests is available in a number of popular package formats. Of course,
the ideal way to install Requests is via The Cheeseshop.
Ubuntu & Debian
---------------
Requests is available installed as a Debian package! Debian Etch Ubuntu, since Oneiric::
$ apt-get install python-requests
Unfortunately, the most recent version available is v0.5.0. If you're on the
Debian Python Package team, I'd love an update of that :)
Fedora and RedHat
-----------------
You can easily install Requests v0.6.1 with yum on rpm-based systems::
$ yum install python-requests
+1
View File
@@ -87,6 +87,7 @@ Requests ecosystem and community.
:maxdepth: 2
community/faq
community/out-there.rst
community/support
community/updates
+7
View File
@@ -0,0 +1,7 @@
envoy==0.0.2
httpbin==0.0.4
gunicorn
nose
pyflakes
omnijson
rudolf
+2 -2
View File
@@ -12,8 +12,8 @@ This module implements the main Requests system.
"""
__title__ = 'requests'
__version__ = '0.6.4'
__build__ = 0x000604
__version__ = '0.6.5'
__build__ = 0x000605
__author__ = 'Kenneth Reitz'
__license__ = 'ISC'
__copyright__ = 'Copyright 2011 Kenneth Reitz'
+65 -16
View File
@@ -16,21 +16,60 @@ from .utils import add_dict_to_cookiejar
def merge_kwargs(local_kwarg, default_kwarg):
"""Merges kwarg dictionaries.
If a local key in the dictionary is set to None, it will be removed.
"""
if default_kwarg is None:
return local_kwarg
if local_kwarg is None:
return default_kwarg
# Bypass if not a dictionary (e.g. timeout)
if not hasattr(default_kwarg, 'items'):
return local_kwarg
# Update new values.
kwargs = default_kwarg.copy()
kwargs.update(local_kwarg)
# Remove keys that are set to None.
for (k,v) in local_kwarg.items():
if v is None:
del kwargs[k]
return kwargs
class Session(object):
"""A Requests session."""
__attrs__ = ['headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks']
def __init__(self, **kwargs):
def __init__(self,
headers=None,
cookies=None,
auth=None,
timeout=None,
proxies=None,
hooks=None):
self.headers = headers or {}
self.cookies = cookies or {}
self.auth = auth
self.timeout = timeout
self.proxies = proxies or {}
self.hooks = hooks or {}
# Set up a CookieJar to be used by default
self.cookies = cookielib.FileCookieJar()
# Map args from kwargs to instance-local variables
map(lambda k, v: (k in self.__attrs__) and setattr(self, k, v),
kwargs.iterkeys(), kwargs.itervalues())
# Map and wrap requests.api methods
self._map_api_methods()
@@ -42,10 +81,8 @@ class Session(object):
return self
def __exit__(self, *args):
# print args
pass
def _map_api_methods(self):
"""Reads each available method from requests.api and decorates
them with a wrapper, which inserts any instance-local attributes
@@ -54,23 +91,35 @@ class Session(object):
def pass_args(func):
def wrapper_func(*args, **kwargs):
inst_attrs = dict((k, v) for k, v in self.__dict__.iteritems()
if k in self.__attrs__)
# Combine instance-local values with kwargs values, with
# priority to values in kwargs
kwargs = dict(inst_attrs.items() + kwargs.items())
# Argument collector.
_kwargs = {}
# If a session request has a cookie_dict, inject the
# values into the existing CookieJar instead.
if isinstance(kwargs.get('cookies', None), dict):
kwargs['cookies'] = add_dict_to_cookiejar(
inst_attrs['cookies'], kwargs['cookies']
self.cookies, kwargs['cookies']
)
if kwargs.get('headers', None) and inst_attrs.get('headers', None):
kwargs['headers'].update(inst_attrs['headers'])
for attr in self.__attrs__:
# for attr in ['headers',]:
s_val = self.__dict__.get(attr)
r_val = kwargs.get(attr)
new_attr = merge_kwargs(r_val, s_val)
# Skip attributes that were set to None.
if new_attr is not None:
_kwargs[attr] = new_attr
# Make sure we didn't miss anything.
for (k, v) in kwargs.items():
if k not in _kwargs:
_kwargs[k] = v
return func(*args, **_kwargs)
return func(*args, **kwargs)
return wrapper_func
# Map and decorate each function available in requests.api
+36 -65
View File
@@ -3,8 +3,12 @@
from __future__ import with_statement
import unittest
import time
import cookielib
import os
import unittest
import envoy
try:
import omnijson as json
@@ -15,12 +19,10 @@ import requests
from requests.sessions import Session
PORT = os.environ.get('HTTPBIN_PORT', '7045')
HTTPBIN_URL = 'http://httpbin.ep.io/'
HTTPSBIN_URL = 'https://httpbin.ep.io/'
# HTTPBIN_URL = 'http://staging.httpbin.org/'
# HTTPSBIN_URL = 'https://httpbin-staging.ep.io/'
HTTPBIN_URL = 'http://0.0.0.0:%s/' % (PORT)
# HTTPBIN_URL = 'http://127.0.0.1:8000/'
def httpbin(*suffix):
@@ -29,15 +31,9 @@ def httpbin(*suffix):
return HTTPBIN_URL + '/'.join(suffix)
def httpsbin(*suffix):
"""Returns url for HTTPSBIN resource."""
return HTTPSBIN_URL + '/'.join(suffix)
SERVICES = (httpbin, httpsbin)
SERVICES = (httpbin, )
_httpbin = False
class RequestsTestSuite(unittest.TestCase):
"""Requests test cases."""
@@ -46,12 +42,21 @@ class RequestsTestSuite(unittest.TestCase):
_multiprocess_can_split_ = True
def setUp(self):
pass
global _httpbin
if not _httpbin:
self.httpbin = envoy.connect('gunicorn httpbin:app --bind=0.0.0.0:%s' % (PORT))
_httpbin = True
time.sleep(1)
def tearDown(self):
"""Teardown."""
pass
# self.httpbin.kill()
def test_invalid_url(self):
@@ -59,7 +64,7 @@ class RequestsTestSuite(unittest.TestCase):
def test_HTTP_200_OK_GET(self):
r = requests.get(httpbin('/'))
r = requests.get(httpbin('/get'))
self.assertEqual(r.status_code, 200)
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
@@ -70,10 +75,6 @@ class RequestsTestSuite(unittest.TestCase):
r = requests.get(httpbin('redirect', '1'), allow_redirects=False)
self.assertEqual(r.status_code, 302)
def test_HTTPS_200_OK_GET(self):
r = requests.get(httpsbin('/'))
self.assertEqual(r.status_code, 200)
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
heads = {'User-agent': 'Mozilla/5.0'}
@@ -112,12 +113,7 @@ class RequestsTestSuite(unittest.TestCase):
def test_HTTP_200_OK_HEAD(self):
r = requests.head(httpbin('/'))
self.assertEqual(r.status_code, 200)
def test_HTTPS_200_OK_HEAD(self):
r = requests.head(httpsbin('/'))
r = requests.head(httpbin('/get'))
self.assertEqual(r.status_code, 200)
@@ -126,21 +122,11 @@ class RequestsTestSuite(unittest.TestCase):
self.assertEqual(r.status_code, 200)
def test_HTTPS_200_OK_PUT(self):
r = requests.put(httpsbin('put'))
self.assertEqual(r.status_code, 200)
def test_HTTP_200_OK_PATCH(self):
r = requests.patch(httpbin('patch'))
self.assertEqual(r.status_code, 200)
def test_HTTPS_200_OK_PATCH(self):
r = requests.patch(httpsbin('patch'))
self.assertEqual(r.status_code, 200)
def test_AUTH_HTTP_200_OK_GET(self):
for service in SERVICES:
@@ -206,7 +192,7 @@ class RequestsTestSuite(unittest.TestCase):
r = requests.get(service('status', '500'))
self.assertEqual(bool(r), False)
r = requests.get(service('/'))
r = requests.get(service('/get'))
self.assertEqual(bool(r), True)
@@ -257,7 +243,7 @@ class RequestsTestSuite(unittest.TestCase):
for service in SERVICES:
url = service('/')
url = service('/get')
requests.get(url, params={'foo': u'føø'})
requests.get(url, params={u'føø': u'føø'})
@@ -275,19 +261,6 @@ class RequestsTestSuite(unittest.TestCase):
self.assertEquals(r.status_code, 401)
def test_settings(self):
def test():
r = requests.get(httpbin(''))
r.raise_for_status()
with requests.settings(timeout=0.0000001):
self.assertRaises(requests.Timeout, test)
with requests.settings(timeout=100):
requests.get(httpbin(''))
def test_urlencoded_post_data(self):
for service in SERVICES:
@@ -386,9 +359,9 @@ class RequestsTestSuite(unittest.TestCase):
self.assertEquals(rbody.get('data'), 'foobar')
def test_idna(self):
r = requests.get(u'http://➡.ws/httpbin')
assert 'httpbin' in r.url
# def test_idna(self):
# r = requests.get(u'http://➡.ws/httpbin')
# assert 'httpbin' in r.url
def test_urlencoded_get_query_multivalued_param(self):
@@ -470,14 +443,7 @@ class RequestsTestSuite(unittest.TestCase):
def test_session_HTTP_200_OK_GET(self):
s = Session()
r = s.get(httpbin('/'))
self.assertEqual(r.status_code, 200)
def test_session_HTTPS_200_OK_GET(self):
s = Session()
r = s.get(httpsbin('/'))
r = s.get(httpbin('/get'))
self.assertEqual(r.status_code, 200)
@@ -487,13 +453,18 @@ class RequestsTestSuite(unittest.TestCase):
s = Session()
s.headers = heads
# Make 2 requests from Session object, should send header both times
r1 = s.get(httpbin('user-agent'))
assert heads['User-agent'] in r1.content
r2 = s.get(httpbin('user-agent'))
r2 = s.get(httpbin('user-agent'))
assert heads['User-agent'] in r2.content
new_heads = {'User-agent': 'blah'}
r3 = s.get(httpbin('user-agent'), headers=new_heads)
assert new_heads['User-agent'] in r3.content
self.assertEqual(r2.status_code, 200)