mirror of
https://github.com/sprockets/sprockets.mixins.correlation.git
synced 2024-11-23 11:19:53 +00:00
Merge pull request #1 from sprockets/add-correlation-handler
Add correlation handler
This commit is contained in:
commit
e901344bad
8 changed files with 180 additions and 5 deletions
25
.travis.yml
Normal file
25
.travis.yml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
%YAML 1.1
|
||||||
|
---
|
||||||
|
language: python
|
||||||
|
python:
|
||||||
|
- '2.7'
|
||||||
|
- '3.3'
|
||||||
|
- '3.4'
|
||||||
|
- pypy
|
||||||
|
install:
|
||||||
|
- pip install -e .
|
||||||
|
- pip install -r test-requirements.txt
|
||||||
|
- pip install tornado
|
||||||
|
script: nosetests
|
||||||
|
after_success:
|
||||||
|
- coveralls
|
||||||
|
deploy:
|
||||||
|
provider: pypi
|
||||||
|
user: aweber
|
||||||
|
password:
|
||||||
|
secure: fdn7NjeIHeo51VHhp+6LnviRYJtfd7As0dCMcwGf1X/STvKbTqp1b8usq9QjSacakde+ZdFsuNM1sImw/yUlft0ZbxksBE98sO+q6qdSMVnQyNtXSyc0Rifw5yyx3/lBdqI0JDGDnMzNWSL1CF5csX1GeUAZguP3kgiV3D8yChA=
|
||||||
|
distributions: "sdist bdist_wheel"
|
||||||
|
on:
|
||||||
|
python: 2.7
|
||||||
|
tags: true
|
||||||
|
all_branches: true
|
|
@ -1,7 +1,6 @@
|
||||||
Version History
|
Version History
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
0.0.0
|
Next Release
|
||||||
~~~~~
|
~~~~~~~~~~~~
|
||||||
- Nothing to see here
|
- Adds ``sprockets.mixins.correlation.HandlerMixin``
|
||||||
|
|
||||||
|
|
|
@ -21,4 +21,6 @@ html_theme = 'sphinx_rtd_theme'
|
||||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
'python': ('https://docs.python.org/', None),
|
'python': ('https://docs.python.org/', None),
|
||||||
|
'sprockets': ('https://sprockets.readthedocs.org/en/latest/', None),
|
||||||
|
'tornado': ('http://www.tornadoweb.org/en/latest/', None),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
.. include:: ../README.rst
|
.. include:: ../README.rst
|
||||||
|
|
||||||
|
API Documentation
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
correlation.HandlerMixin
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
.. autoclass:: sprockets.mixins.correlation.HandlerMixin
|
||||||
|
:members:
|
||||||
|
|
||||||
Contributing to this Library
|
Contributing to this Library
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
|
from .mixins import HandlerMixin
|
||||||
|
|
||||||
|
|
||||||
version_info = (0, 0, 0)
|
version_info = (0, 0, 0)
|
||||||
__version__ = '.'.join(str(v) for v in version_info[:3])
|
__version__ = '.'.join(str(v) for v in version_info[:3])
|
||||||
|
|
83
sprockets/mixins/correlation/mixins.py
Normal file
83
sprockets/mixins/correlation/mixins.py
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class HandlerMixin(object):
|
||||||
|
"""
|
||||||
|
Mix this in over a ``RequestHandler`` for a correlating header.
|
||||||
|
|
||||||
|
:keyword str correlation_header: the name of the header to use
|
||||||
|
for correlation. If this keyword is omitted, then the header
|
||||||
|
is named ``Correlation-ID``.
|
||||||
|
|
||||||
|
This mix-in ensures that responses include a header that correlates
|
||||||
|
requests and responses. If there header is set on the incoming
|
||||||
|
request, then it will be copied to the outgoing response. Otherwise,
|
||||||
|
a new UUIDv4 will be generated and inserted. The value can be
|
||||||
|
examined or modified via the ``correlation_id`` property.
|
||||||
|
|
||||||
|
The MRO needs to contain something that resembles a standard
|
||||||
|
:class:`tornado.web.RequestHandler`. Specifically, we need the
|
||||||
|
following things to be available:
|
||||||
|
|
||||||
|
- :meth:`~tornado.web.RequestHandler.prepare` needs to be called
|
||||||
|
appropriately
|
||||||
|
- :meth:`~tornado.web.RequestHandler.set_header` needs to exist in
|
||||||
|
the MRO and it needs to overwrite the header value
|
||||||
|
- :meth:`~tornado.web.RequestHandler.set_default_headers` should be
|
||||||
|
called to establish the default header values
|
||||||
|
- ``self.request`` is a object that has a ``headers`` property that
|
||||||
|
contains the request headers as a ``dict``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# correlation_id is used from within set_default_headers
|
||||||
|
# which is called from within super().__init__() so we need
|
||||||
|
# to make sure that it is set *BEFORE* we call super.
|
||||||
|
self.__header_name = kwargs.pop(
|
||||||
|
'correlation_header', 'Correlation-ID')
|
||||||
|
self.__correlation_id = str(uuid.uuid4())
|
||||||
|
super(HandlerMixin, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
# Here we want to copy an incoming Correlation-ID header if
|
||||||
|
# one exists. We also want to set it in the outgoing response
|
||||||
|
# which the property setter does for us.
|
||||||
|
super(HandlerMixin, self).prepare()
|
||||||
|
correlation_id = self.get_request_header(self.__header_name, None)
|
||||||
|
if correlation_id is not None:
|
||||||
|
self.correlation_id = correlation_id
|
||||||
|
|
||||||
|
def set_default_headers(self):
|
||||||
|
# This is called during initialization as well as *AFTER*
|
||||||
|
# calling clear() when an error occurs so we need to make
|
||||||
|
# sure that our header is set again...
|
||||||
|
super(HandlerMixin, self).set_default_headers()
|
||||||
|
self.set_header(self.__header_name, self.correlation_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def correlation_id(self):
|
||||||
|
"""Correlation header value."""
|
||||||
|
return self.__correlation_id
|
||||||
|
|
||||||
|
@correlation_id.setter
|
||||||
|
def correlation_id(self, value):
|
||||||
|
self.__correlation_id = value
|
||||||
|
self.set_header(self.__header_name, self.__correlation_id)
|
||||||
|
|
||||||
|
def get_request_header(self, name, default):
|
||||||
|
"""
|
||||||
|
Retrieve the value of a request header.
|
||||||
|
|
||||||
|
:param str name: the name of the header to retrieve
|
||||||
|
:param default: the value to return if the header is not set
|
||||||
|
|
||||||
|
This method abstracts the act of retrieving a header value out
|
||||||
|
from the implementation. This makes it possible to implement
|
||||||
|
a *RequestHandler* that is something other than a
|
||||||
|
:class:`tornado.web.RequestHandler` by simply implementing this
|
||||||
|
method and ``set_header`` over the underlying implementation,
|
||||||
|
for example, say AMQP message properties.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.request.headers.get(name, default)
|
40
tests.py
40
tests.py
|
@ -0,0 +1,40 @@
|
||||||
|
import uuid
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from tornado import testing, web
|
||||||
|
|
||||||
|
from sprockets.mixins import correlation
|
||||||
|
|
||||||
|
|
||||||
|
class CorrelatedRequestHandler(correlation.HandlerMixin, web.RequestHandler):
|
||||||
|
|
||||||
|
def get(self, status_code):
|
||||||
|
status_code = int(status_code)
|
||||||
|
if status_code >= 300:
|
||||||
|
raise web.HTTPError(status_code)
|
||||||
|
self.write('status {0}'.format(status_code))
|
||||||
|
|
||||||
|
|
||||||
|
class CorrelationMixinTests(testing.AsyncHTTPTestCase):
|
||||||
|
|
||||||
|
def get_app(self):
|
||||||
|
return web.Application([
|
||||||
|
(r'/status/(?P<status_code>\d+)', CorrelatedRequestHandler),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_that_correlation_id_is_returned_when_successful(self):
|
||||||
|
self.http_client.fetch(self.get_url('/status/200'), self.stop)
|
||||||
|
response = self.wait()
|
||||||
|
self.assertIsNotNone(response.headers.get('Correlation-ID'))
|
||||||
|
|
||||||
|
def test_that_correlation_id_is_returned_in_error(self):
|
||||||
|
self.http_client.fetch(self.get_url('/status/500'), self.stop)
|
||||||
|
response = self.wait()
|
||||||
|
self.assertIsNotNone(response.headers.get('Correlation-ID'))
|
||||||
|
|
||||||
|
def test_that_correlation_id_is_copied_from_request(self):
|
||||||
|
correlation_id = uuid.uuid4().hex
|
||||||
|
self.http_client.fetch(self.get_url('/status/200'), self.stop,
|
||||||
|
headers={'Correlation-Id': correlation_id})
|
||||||
|
response = self.wait()
|
||||||
|
self.assertEqual(response.headers['correlation-id'], correlation_id)
|
17
tox.ini
17
tox.ini
|
@ -1,5 +1,5 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py27,py33,py34,pypy,pypy3
|
envlist = py27,py33,py34,pypy,pypy3,tornado31,tornado32,tornado40
|
||||||
toxworkdir = {toxinidir}/build/tox
|
toxworkdir = {toxinidir}/build/tox
|
||||||
skip_missing_intepreters = true
|
skip_missing_intepreters = true
|
||||||
|
|
||||||
|
@ -13,3 +13,18 @@ commands = {envbindir}/nosetests
|
||||||
deps =
|
deps =
|
||||||
{[testenv]deps}
|
{[testenv]deps}
|
||||||
mock
|
mock
|
||||||
|
|
||||||
|
[testenv:tornado31]
|
||||||
|
deps =
|
||||||
|
-rtest-requirements.txt
|
||||||
|
tornado>=3.1,<3.2
|
||||||
|
|
||||||
|
[testenv:tornado32]
|
||||||
|
deps =
|
||||||
|
-rtest-requirements.txt
|
||||||
|
tornado>=3.2,<3.3
|
||||||
|
|
||||||
|
[testenv:tornado40]
|
||||||
|
deps =
|
||||||
|
-rtest-requirements.txt
|
||||||
|
tornado>=4.0,<4.1
|
||||||
|
|
Loading…
Reference in a new issue