Skip to content

Commit

Permalink
Merge branch 'master' of github.com:agrausem/britney
Browse files Browse the repository at this point in the history
  • Loading branch information
agrausem committed Jul 17, 2015
2 parents b3bd14f + b16148f commit 05a8c70
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .coveragerc
@@ -1,5 +1,5 @@
[run]
source =
source =
britney
britney/middleware

Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -7,7 +7,7 @@ python:
install:
- pip install -r requirements.txt --use-mirrors
- pip install -e .
- pip install coverage nose --use-mirrors
- pip install coverage nose responses --use-mirrors
script: nosetests --config=nose.cfg
after_success:
- pip install coveralls
Expand Down
10 changes: 6 additions & 4 deletions README.rst
Expand Up @@ -16,13 +16,15 @@ This module requires a SPORE description of an API. Some descriptions for known
.. image:: https://coveralls.io/repos/agrausem/britney/badge.png?branch=master
:target: https://coveralls.io/r/agrausem/britney?branch=master

.. image:: https://pypip.in/v/britney/badge.png
.. image:: https://img.shields.io/pypi/v/britney.svg
:target: https://crate.io/packages/britney/
:alt: Latest PyPI version

.. image:: https://pypip.in/d/britney/badge.png
.. image:: https://img.shields.io/pypi/dm/britney.svg
:target: https://crate.io/packages/britney/
:alt: Number of PyPI downloads

.. image:: https://landscape.io/github/agrausem/britney/master/landscape.svg?style=flat
:target: https://landscape.io/github/agrausem/britney/master
:alt: Code Health



Expand Down
11 changes: 6 additions & 5 deletions britney/core.py
Expand Up @@ -12,6 +12,7 @@

import requests
import requests.models
from functools import reduce
from requests.compat import urlparse
import six

Expand Down Expand Up @@ -162,7 +163,7 @@ class SporeMethod(object):
:param formats: a list of formats accepted by the REST Web Service for this
method. This parameter replaces the global formats set whole client
(defaults to None)
:param base_url: a specific base url for this method. This parameter
:param base_url: a specific base url for this method. This parameter
replaces the base api url set for the whole client (defaults to '')
:param documentation: documentation for this method
:param global_authentication: a boolean that enables authentication for the
Expand Down Expand Up @@ -281,6 +282,7 @@ def server_port(parsed_url):
'spore.authentication': self.authentication,
'spore.params': '',
'spore.payload': '',
'spore.payload_format': '',
'spore.errors': '',
'spore.headers': {},
'spore.format': self.formats,
Expand Down Expand Up @@ -390,9 +392,8 @@ def __call__(self, **kwargs):

self.check_status(response)

for hook in hooks:
res = hook(response)
if res and isinstance(res, requests.models.Response):
response = res
res = reduce(lambda r, hook: hook(r), reversed(hooks), response)
if res and isinstance(res, requests.models.Response):
response = res

return response
48 changes: 19 additions & 29 deletions britney/middleware/format.py
Expand Up @@ -23,6 +23,8 @@ class Format(base.Middleware):

__metaclass__ = abc.ABCMeta

content_type = ''
accept = ''

@abc.abstractmethod
def dump(self, data):
Expand All @@ -35,53 +37,41 @@ def load(self, data):
"""
"""
pass

@abc.abstractproperty
def accept(self):
"""
"""
pass

@abc.abstractproperty
def content_type(self):
"""
"""
pass

def content_length(self, content):
"""
:param content:
:return: content length header information
"""
return 'Content-Length', len(content)

return len(content)

def process_request(self, environ):
base.add_header(environ, *self.accept)
if environ['spore.payload']:
payload = self.dump(environ['spore.payload'])
environ['spore.payload'] = payload
base.add_header(environ, *self.content_length(payload))
base.add_header(environ, *self.content_type)
base.add_header(environ, 'Accept', self.accept)
if environ['spore.payload'] and not environ['spore.payload_format']:
self.process_payload(environ)

def process_payload(self, environ):
"""
"""
payload = self.dump(environ['spore.payload'])
environ['spore.payload'] = payload
base.add_header(environ, 'Content-Length', self.content_length(payload))
base.add_header(environ, 'Content-Type', self.content_type)

def process_response(self, response):
response.data = self.load(response.text)
response.data = self.load(response.text) if response.text else {}
return response


class Json(Format):
"""
"""

content_type = 'application/json'
accept = 'application/json'

def dump(self, data):
return json.dumps(data)

def load(self, data):
return json.loads(data)

@property
def content_type(self):
return 'Content-Type', 'application/json'

@property
def accept(self):
return 'Accept', 'application/json'
2 changes: 1 addition & 1 deletion britney/utils.py
Expand Up @@ -9,7 +9,7 @@
import wsgiref.handlers


VERSION = '0.4.0'
VERSION = '0.5.0'


def get_user_agent():
Expand Down
26 changes: 11 additions & 15 deletions tests/test_middleware.py
Expand Up @@ -100,31 +100,26 @@ def test_auth_callable(self):
key_value=self.callable_format, key='fbfryfrbfyrbfr',
user='test')
self.middleware_callable(self.environ)
self.assertEqual(self.environ['spore.headers']['X-API-Key'],
self.assertEqual(self.environ['spore.headers']['X-API-Key'],
'ApiKey fbfryfrbfyrbfr:test')


class TestBaseFormatMiddleware(unittest.TestCase):

class Quoted(format_.Format):

content_type = 'quoted'
accept = 'quoted'

def dump(self, data):
return "'%s'" % data

def load(self, data):
return data.strip("'")

@property
def content_type(self):
return ('Content-Type', 'quoted')

@property
def accept(self):
return ('Accept', 'quoted')

def setUp(self):
self.middleware = self.Quoted()
self.environ = {'spore.payload': None, 'spore.headers': {}}
self.environ = {'spore.payload': None, 'spore.headers': {}, 'spore.payload_format': ''}

def test_process_request_without_payload(self):
self.assertIsNone(self.middleware.process_request(self.environ))
Expand All @@ -135,26 +130,27 @@ def test_process_request_with_payload(self):
self.environ['spore.payload'] = 'my_payload'
self.assertIsNone(self.middleware.process_request(self.environ))
self.assertDictEqual(self.environ['spore.headers'], {
'Accept': 'quoted',
'Accept': 'quoted',
'Content-Length': len("'my_payload'"),
'Content-Type': 'quoted'
})
self.assertEqual(self.environ['spore.payload'], "'my_payload'")

def test_process_response(self):
Response = type('Response', (object, ), {'text': "'my_content'",
Response = type('Response', (object, ), {'text': "'my_content'",
'data': ""})
response = Response()
self.assertIsNone(self.middleware.process_response(response))
self.assertIsNotNone(self.middleware.process_response(response))
self.assertEqual(response.data, "my_content")


class TestJsonFormatMiddleware(unittest.TestCase):

def setUp(self):
self.middleware = format_.Json()
self.environ = {'spore.payload': {'data': 'my_data'},
'spore.headers': {}}
self.environ = {'spore.payload': {'data': 'my_data'},
'spore.headers': {},
'spore.payload_format': ''}

def test_calling_middleware(self):
callback = self.middleware(self.environ)
Expand Down
89 changes: 89 additions & 0 deletions tests/test_response.py
@@ -0,0 +1,89 @@
import unittest
import britney
from britney.middleware import Json
from britney.middleware.base import Middleware
from os.path import abspath, dirname, join
import responses
from datetime import datetime


class OrderMiddleware(Middleware):

def process_order(self, response):
if hasattr(response, 'order'):
response.order.append(self.__class__.__name__)
else:
response.order = [self.__class__.__name__]


class JsonOrder(OrderMiddleware, Json):

def process_response(self, response):
self.process_order(response)
return super(JsonOrder, self).process_response(response)


class Timer(OrderMiddleware):

def process_request(self, environ):
self.timer = datetime.now()

def process_response(self, response):
self.process_order(response)
response.time = datetime.now() - self.timer
return response


class TestSporeResponse(unittest.TestCase):

description_path = join(dirname(abspath(__file__)), 'descriptions')

def setUp(self):
self.client = britney.new(join(self.description_path, 'api.json'))

@responses.activate
def test_simple_method(self):
responses.add(responses.GET, 'http://test.api.org/test',
body='{"test": "good"}', status=200,
content_type='application/json')

response = self.client.test()

self.assertEqual(response.text, '{"test": "good"}')
self.assertEqual(response.status_code, 200)

@responses.activate
def test_chaining_middlewares(self):
self.client.enable(Timer)
self.client.enable(JsonOrder)

responses.add(responses.GET,
'http://test.api.org/test-requires/1.json',
body='{"test": "good"}', status=200,
content_type='application/json')

response = self.client.test_requires(format='json', id='1')

self.assertEqual(response.order, ['JsonOrder', 'Timer'])

@responses.activate
def test_format_middleware(self):
self.client.enable('Json')

responses.add(responses.GET,
'http://test.api.org/test-requires/1.json',
body='{"test": "good"}', status=200,
content_type='application/json')

response = self.client.test_requires(format='json', id='1')
self.assertEqual(response.data, {'test': 'good'})

@responses.activate
def test_bad_status_code(self):
responses.add(responses.GET,
'http://test.api.org/test-requires/2.json',
body='{"detail": "not found"}', status=404,
content_type='application/json')

with self.assertRaises(britney.HTTPError):
self.client.test_requires(format='json', id='2')
1 change: 1 addition & 0 deletions tox.ini
Expand Up @@ -11,3 +11,4 @@ commands = nosetests --config=nose.cfg
deps =
coverage
nose
responses

0 comments on commit 05a8c70

Please sign in to comment.