Skip to content

Commit

Permalink
Allow POST and PUT requests to take both querystring params and reque…
Browse files Browse the repository at this point in the history
…st body

data.
  • Loading branch information
rboulton committed Jun 16, 2011
1 parent f31ade3 commit 80d860d
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 27 deletions.
24 changes: 14 additions & 10 deletions requests/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,11 @@ def request(method, url, params=None, data=None, headers=None, cookies=None, fil
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
"""

if params and data:
raise StandardError('You may provide either params or data to a request, but not both.')

r = Request(
method = method,
url = url,
data = params or data,
data = data,
params = params,
headers = headers,
cookiejar = cookies,
files = files,
Expand Down Expand Up @@ -81,7 +79,8 @@ def head(url, params=None, headers=None, cookies=None, auth=None, timeout=None):
return request('HEAD', url, params=params, headers=headers, cookies=cookies, auth=auth, timeout=timeout)


def post(url, data='', headers=None, files=None, cookies=None, auth=None, timeout=None, allow_redirects=False):
def post(url, data='', headers=None, files=None, cookies=None, auth=None,
timeout=None, allow_redirects=False, params=None):
"""Sends a POST request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
Expand All @@ -92,13 +91,16 @@ def post(url, data='', headers=None, files=None, cookies=None, auth=None, timeou
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
:param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed.
:param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`.
"""

return request('POST', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth,
timeout=timeout, allow_redirects=allow_redirects)
return request('POST', url, params=params, data=data, headers=headers,
files=files, cookies=cookies, auth=auth, timeout=timeout,
allow_redirects=allow_redirects)


def put(url, data='', headers=None, files=None, cookies=None, auth=None, timeout=None, allow_redirects=False):
def put(url, data='', headers=None, files=None, cookies=None, auth=None,
timeout=None, allow_redirects=False, params=None):
"""Sends a PUT request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
Expand All @@ -109,10 +111,12 @@ def put(url, data='', headers=None, files=None, cookies=None, auth=None, timeout
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
:param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed.
:param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`.
"""

return request('PUT', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth,
timeout=timeout, allow_redirects=allow_redirects)
return request('PUT', url, params=params, data=data, headers=headers,
files=files, cookies=cookies, auth=auth, timeout=timeout,
allow_redirects=allow_redirects)


def delete(url, params=None, headers=None, cookies=None, auth=None, timeout=None, allow_redirects=False):
Expand Down
41 changes: 24 additions & 17 deletions requests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class Request(object):
_METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE')

def __init__(self, url=None, headers=dict(), files=None, method=None,
data=dict(), auth=None, cookiejar=None, timeout=None,
redirect=False, allow_redirects=False):
data=dict(), params=dict(), auth=None, cookiejar=None,
timeout=None, redirect=False, allow_redirects=False):

socket.setdefaulttimeout(timeout)

Expand All @@ -44,15 +44,21 @@ def __init__(self, url=None, headers=dict(), files=None, method=None,
self.files = files
#: HTTP Method to use. Available: GET, HEAD, PUT, POST, DELETE.
self.method = method
#: Form or Byte data to attach to the :class:`Request <models.Request>`.
self.data = dict()
#: Dictionary or byte of request body data to attach to the
#: :class:`Request <models.Request>`.
self.data = None
#: Dictionary or byte of querystring data to attach to the
#: :class:`Request <models.Request>`.
self.params = None
#: True if :class:`Request <models.Request>` is part of a redirect chain (disables history
#: and HTTPError storage).
self.redirect = redirect
#: Set to True if full redirects are allowed (e.g. re-POST-ing of data at new ``Location``)
self.allow_redirects = allow_redirects

self.data, self._enc_data = self._encode_params(data)
self.params, self._enc_params = self._encode_params(params)

#: :class:`Response <models.Response>` instance, containing
#: content and metadata of HTTP Response, once :attr:`sent <send>`.
self.response = Response()
Expand Down Expand Up @@ -185,7 +191,8 @@ def build(resp):

request = Request(
url, self.headers, self.files, method,
self.data, self.auth, self.cookiejar, redirect=False
self.data, self.params, self.auth, self.cookiejar,
redirect=False
)
request.send()
r = request.response
Expand Down Expand Up @@ -217,17 +224,16 @@ def _encode_params(data):
return data, data


@staticmethod
def _build_url(url, data=None):
"""Build URLs."""
def _build_url(self):
"""Build the actual URL to use"""

if urlparse(url).query:
return '%s&%s' % (url, data)
else:
if data:
return '%s?%s' % (url, data)
if self._enc_params:
if urlparse(self.url).query:
return '%s&%s' % (self.url, self._enc_params)
else:
return url
return '%s?%s' % (self.url, self._enc_params)
else:
return self.url


def send(self, anyway=False):
Expand All @@ -243,8 +249,9 @@ def send(self, anyway=False):
self._checks()
success = False

url = self._build_url()
if self.method in ('GET', 'HEAD', 'DELETE'):
req = _Request(self._build_url(self.url, self._enc_data), method=self.method)
req = _Request(url, method=self.method)
else:

if self.files:
Expand All @@ -254,10 +261,10 @@ def send(self, anyway=False):
self.files.update(self.data)

datagen, headers = multipart_encode(self.files)
req = _Request(self.url, data=datagen, headers=headers, method=self.method)
req = _Request(url, data=datagen, headers=headers, method=self.method)

else:
req = _Request(self.url, data=self._enc_data, method=self.method)
req = _Request(url, data=self._enc_data, method=self.method)

if self.headers:
req.headers.update(self.headers)
Expand Down
64 changes: 64 additions & 0 deletions test_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

import unittest
import cookielib
try:
import simplejson as json
except ImportError:
import json

import requests

Expand Down Expand Up @@ -229,8 +233,68 @@ def test_settings(self):
requests.get(httpbin(''))


def test_urlencoded_post_data(self):
r = requests.post(httpbin('post'), data=dict(test='fooaowpeuf'))
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
self.assertEquals(r.url, httpbin('post'))
rbody = json.loads(r.content)
self.assertEquals(rbody.get('form'), dict(test='fooaowpeuf'))
self.assertEquals(rbody.get('data'), '')


def test_nonurlencoded_post_data(self):
r = requests.post(httpbin('post'), data='fooaowpeuf')
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
self.assertEquals(r.url, httpbin('post'))
rbody = json.loads(r.content)
# Body wasn't valid url encoded data, so the server returns None as
# "form" and the raw body as "data".
self.assertEquals(rbody.get('form'), None)
self.assertEquals(rbody.get('data'), 'fooaowpeuf')


def test_urlencoded_post_querystring(self):
r = requests.post(httpbin('post'), params=dict(test='fooaowpeuf'))
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
self.assertEquals(r.url, httpbin('post?test=fooaowpeuf'))
rbody = json.loads(r.content)
self.assertEquals(rbody.get('form'), {}) # No form supplied
self.assertEquals(rbody.get('data'), '')


def test_nonurlencoded_post_querystring(self):
r = requests.post(httpbin('post'), params='fooaowpeuf')
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
self.assertEquals(r.url, httpbin('post?fooaowpeuf'))
rbody = json.loads(r.content)
self.assertEquals(rbody.get('form'), {}) # No form supplied
self.assertEquals(rbody.get('data'), '')


def test_urlencoded_post_query_and_data(self):
r = requests.post(httpbin('post'), params=dict(test='fooaowpeuf'),
data=dict(test2="foobar"))
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
self.assertEquals(r.url, httpbin('post?test=fooaowpeuf'))
rbody = json.loads(r.content)
self.assertEquals(rbody.get('form'), dict(test2='foobar'))
self.assertEquals(rbody.get('data'), '')


def test_nonurlencoded_post_query_and_data(self):
r = requests.post(httpbin('post'), params='fooaowpeuf',
data="foobar")
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
self.assertEquals(r.url, httpbin('post?fooaowpeuf'))
rbody = json.loads(r.content)
self.assertEquals(rbody.get('form'), None)
self.assertEquals(rbody.get('data'), 'foobar')



Expand Down

0 comments on commit 80d860d

Please sign in to comment.