Skip to content

Commit

Permalink
Merge remote-tracking branch 'samgiles/master' into separate_serializers
Browse files Browse the repository at this point in the history
  • Loading branch information
onlynone committed Jun 23, 2017
2 parents a5bb1f3 + af0f9ef commit 5e4d892
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 50 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ language: python
python:
- 2.6
- 2.7
- 3.2
- 3.3
- 3.4
- 3.6
- pypy
install:
- pip install -r requirements.txt
Expand Down
9 changes: 0 additions & 9 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,6 @@ and processing requests.
options
howitworks

Getting Help
============

There are two primary ways of getting help. I have an IRC channel
(`#slumber on irc.freenode.net`_) to get help, want to bounce idea or
generally shoot the breeze.

.. _#slumber on irc.freenode.net: irc://irc.freenode.net/slumber

QuickStart
==========

Expand Down
17 changes: 17 additions & 0 deletions docs/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,20 @@ Slashes
Slumber assumes by default that all urls should end with a slash. If you do not
want this behavior you can control it via the append_slash option which can be
set by passing append_slash to the ``slumber.API`` kwargs.

Raw Responses
=================

By default Slumber will return a decoded representation of the response body,
if one existed. If the `API` is constructed with `raw=True`, then instead
of returning a decoded representation, a tuple will be returned, where the
first item is the actual `requests.Response` object, and the second is the
decoded representation::

api = slumber.API("https://example.com/path/to/api", raw=True)
(response, decoded) = api.subresource.get()

Alternatively, this can be done on a per resource basis using the `as_raw`
method::

(response, decoded) = api.subresource.as_raw().get()
22 changes: 22 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,28 @@
author_email = "[email protected]",
maintainer = "Samuel Giles",
maintainer_email = "[email protected]",
classifiers=[
# See: https://pypi.python.org/pypi?:action=list_classifiers
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
# List of python versions and their support status:
# https://en.wikipedia.org/wiki/CPython#Version_history
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Internet :: WWW/HTTP :: HTTP Servers',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Software Development :: Testing',
],
packages = ["slumber"],
zip_safe = False,
install_requires = install_requires,
Expand Down
55 changes: 32 additions & 23 deletions slumber/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,45 +141,42 @@ def _try_to_serialize_response(self, resp):
return resp.content

def _process_response(self, resp):
# TODO: something to expose headers and status

if 200 <= resp.status_code <= 299:
return self._try_to_serialize_response(resp)
decoded = self._try_to_serialize_response(resp)
else:
return # @@@ We should probably do some sort of error here? (Is this even possible?)
# @@@ We should probably do some sort of error here? (Is this even possible?)
decoded = None

def url(self):
url = self._store["base_url"]
if self._store["raw"]:
return (resp, decoded)

if self._store["append_slash"] and not url.endswith("/"):
url = url + "/"
return decoded

return url
def _do_verb_request(self, verb, data=None, files=None, params=None):
resp = self._request(verb, data=data, files=files, params=params)
return self._process_response(resp)

def as_raw(self):
self._store["raw"] = True
return self

# TODO: refactor these methods - lots of commonality
def get(self, **kwargs):
resp = self._request("GET", params=kwargs)
return self._process_response(resp)
return self._do_verb_request("GET", params=kwargs)

def options(self, **kwargs):
resp = self._request("OPTIONS", params=kwargs)
return self._process_response(resp)
return self._do_verb_request("OPTIONS", params=kwargs)

def head(self, **kwargs):
resp = self._request("HEAD", params=kwargs)
return self._process_response(resp)
return self._do_verb_request("HEAD", params=kwargs)

def post(self, data=None, files=None, **kwargs):
resp = self._request("POST", data=data, files=files, params=kwargs)
return self._process_response(resp)
return self._do_verb_request("POST", data=data, files=files, params=kwargs)

def patch(self, data=None, files=None, **kwargs):
resp = self._request("PATCH", data=data, files=files, params=kwargs)
return self._process_response(resp)
return self._do_verb_request("PATCH", data=data, files=files, params=kwargs)

def put(self, data=None, files=None, **kwargs):
resp = self._request("PUT", data=data, files=files, params=kwargs)
return self._process_response(resp)
return self._do_verb_request("PUT", data=data, files=files, params=kwargs)

def delete(self, **kwargs):
resp = self._request("DELETE", params=kwargs)
Expand All @@ -191,6 +188,15 @@ def delete(self, **kwargs):
else:
return False

def url(self):
url = self._store["base_url"]

if self._store["append_slash"] and not url.endswith("/"):
url = url + "/"

return url


def _get_resource(self, **kwargs):
return self.__class__(**kwargs)

Expand All @@ -199,7 +205,9 @@ class API(ResourceAttributesMixin, object):

resource_class = Resource

def __init__(self, base_url=None, auth=None, format=None, input_format=None, append_slash=True, session=None, serializer=None, input_serializer=None):
def __init__(self, base_url=None, auth=None,
format=None, input_format=None, append_slash=True,
session=None, serializer=None, input_serializer=None, raw=False):
if serializer is None:
serializer = Serializer(default=format)

Expand All @@ -220,6 +228,7 @@ def __init__(self, base_url=None, auth=None, format=None, input_format=None, app
"session": session,
"serializer": serializer,
"input_serializer": input_serializer,
"raw": raw,
}

# Do some Checks for Required Values
Expand Down
115 changes: 99 additions & 16 deletions tests/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
class ResourceTestCase(unittest.TestCase):

def setUp(self):
self.base_resource = slumber.Resource(base_url="http://example/api/v1/test", format="json", input_format="json", append_slash=False)
self.base_resource = slumber.Resource(
base_url="http://example/api/v1/test", format="json",
input_format="json", append_slash=False, raw=False)

def test_get_200_json(self):
r = mock.Mock(spec=requests.Response)
Expand All @@ -38,7 +40,7 @@ def test_get_200_json(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(), "accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.get()
Expand Down Expand Up @@ -68,7 +70,7 @@ def test_get_200_text(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(), "accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.get()
Expand Down Expand Up @@ -98,8 +100,7 @@ def test_options_200_json(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(),
"accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.options()
Expand Down Expand Up @@ -132,8 +133,7 @@ def test_head_200_json(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(),
"accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.head()
Expand Down Expand Up @@ -168,7 +168,7 @@ def test_post_201_redirect(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(), "accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.post(data={'foo': 'bar'})
Expand Down Expand Up @@ -198,7 +198,7 @@ def test_post_decodable_response(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(), "accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.post(data={'foo': 'bar'})
Expand Down Expand Up @@ -233,7 +233,7 @@ def test_patch_201_redirect(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(), "accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.patch(data={'foo': 'bar'})
Expand Down Expand Up @@ -263,7 +263,7 @@ def test_patch_decodable_response(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(), "accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.patch(data={'foo': 'bar'})
Expand Down Expand Up @@ -298,7 +298,7 @@ def test_put_201_redirect(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(), "accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.put(data={'foo': 'bar'})
Expand Down Expand Up @@ -328,7 +328,7 @@ def test_put_decodable_response(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(), "accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.put(data={'foo': 'bar'})
Expand Down Expand Up @@ -390,7 +390,7 @@ def test_get_200_subresource_json(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(), "accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.get()
Expand Down Expand Up @@ -505,12 +505,95 @@ def test_get_200_json_py3(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(), "accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.get()
self.assertEqual(resp['result'], ['a', 'b', 'c'])

def test_get_with_raw(self):
r = mock.Mock(spec=requests.Response)
r.status_code = 200
r.headers = {"content-type": "application/json"}
r.content = '{"result": "a"}'

self.base_resource._store.update({
"session": mock.Mock(spec=requests.Session),
"serializer": slumber.serialize.Serializer(),
"raw": True,
})
self.base_resource._store["session"].request.return_value = r

(response, decoded) = self.base_resource.get()

self.assertIsInstance(response, requests.Response)
self.assertEqual(decoded["result"], "a")

def test_as_raw_resource_get(self):
apiurl = "http://example/api/v1"
ses = mock.Mock(spec=requests.session())
r = mock.Mock(spec=requests.Response)
r.status_code = 200
r.headers = {}
ses.request.return_value = r

api = slumber.API(apiurl, session=ses)

(response, _) = api.myresource(1).subresource.as_raw().get()
self.assertIsInstance(response, requests.Response)

def test_all_resource_requests_are_raw_if_set_in_api(self):
apiurl = "http://example/api/v1"
ses = mock.Mock(spec=requests.session())
r = mock.Mock(spec=requests.Response)
r.status_code = 200
r.headers = {}
ses.request.return_value = r

api = slumber.API(apiurl, session=ses, raw=True)

(response, _) = api.myresource(1).subresource.get()
self.assertIsInstance(response, requests.Response)

(response, _) = api.myresource(1).get()
self.assertIsInstance(response, requests.Response)

def test_send_content_type_only_if_body_data_exists(self):
apiuri = "http://example/api/v1/"
newuri = "http://example/api/v1/myresource/"
ses = mock.Mock(spec=requests.session())
r = mock.Mock(spec=requests.Response)
r.status_code = 201
r.headers = {}
ses.request.return_value = r

api = slumber.API(apiuri, session=ses)

# Empty post request
api.myresource.post()
ses.return_value.status_code = 201
ses.return_value.headers = {}
self.assertEqual(ses.request.call_count, 1)

ses.request.assert_called_with('POST', newuri,
headers={
'accept': 'application/json'
},
data=None,
files=None,
params={})

api.myresource.post(data=dict(key='value'))
self.assertEqual(ses.request.call_count, 2)
ses.request.assert_called_with('POST', newuri,
headers={
'accept': 'application/json',
'content-type': 'application/json'
},
data='{"key": "value"}',
files=None,
params={})

@unittest.expectedFailure
def test_post_201_does_get(self):
getparams = dict(username="luser", api_key="1234")
Expand Down Expand Up @@ -555,7 +638,7 @@ def test_unicode_decodable_response(self):
data=None,
files=None,
params=None,
headers={"content-type": self.base_resource._store["serializer"].get_content_type(), "accept": self.base_resource._store["serializer"].get_content_type()}
headers={"accept": self.base_resource._store["serializer"].get_content_type()}
)

resp = self.base_resource.post(data={'foo': 'bar'})
Expand Down
Loading

0 comments on commit 5e4d892

Please sign in to comment.