Skip to content
Browse files

Updates for newer versions of WebOb and pykestrel.

 * Removed dependency on external dream package.
  • Loading branch information...
1 parent 65bed43 commit 62486f32b18a5f652e58619391bf5b2b955f446f @matterkkila committed Jan 5, 2012
Showing with 170 additions and 37 deletions.
  1. +8 −14 README.mkd
  2. +106 −0 kestrelweb/dream.py
  3. +4 −4 kestrelweb/kestrel_actions.py
  4. +16 −12 kestrelweb/main.py
  5. +3 −0 local_settings.py.example
  6. +22 −0 logging.conf
  7. +5 −0 requirement.txt
  8. +6 −7 setup.py
View
22 README.mkd
@@ -10,14 +10,11 @@ https://github.com/matterkkila/kestrelweb
Dependencies:
- * decoroute - used by dream
- * dream - https://github.com/matterkkila/dream
- * gevent
- * gunicorn
- * pykestrel <= 0.0.6
- * webob <= 1.0.7
-
-Note: All are available on pypi except for the dream library.
+ * decoroute >= 0.8.1
+ * gevent >= 0.13.6
+ * gunicorn >= 0.13.4
+ * pykestrel >= 0.5.1
+ * webob >= 1.2b2
Prerequisites
@@ -35,16 +32,13 @@ Installing
cd kestrelweb
virtualenv --no-site-packages .
. bin/activate
- pip install gevent
- pip install gunicorn
- pip install pykestrel
- pip install webob
- pip install decoroute
- pip install https://github.com/matterkkila/dream/tarball/master
+ pip install -r requirement.txt
python ./setup.py develop
Note: If libevent is installed in a non-standard location (I'm looking at you macports) you'll need to download the source distribution for gevent and install using something similar to:
+ pip install --no-install gevent #download only
+ cd build/gevent
python ./setup.py install --libevent /opt/local
View
106 kestrelweb/dream.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+#
+# © 2010, 2011 SimpleGeo, Inc. All rights reserved.
+# Author: Ian Eure <ian@simplegeo.com>
+#
+
+import sys
+import logging
+import json
+from traceback import extract_stack
+
+import decoroute
+from webob import Request, Response, exc
+
+
+logger = logging.getLogger('kestrelweb')
+
+
+class JSONResponse(Response):
+
+ """A response in JSON format."""
+
+ default_content_type = 'application/json'
+
+ def __init__(self, callback=None, **kwargs):
+ body = self.serialize(kwargs.pop('body'))
+ if callback is not None:
+ self.default_content_type = 'application/javascript'
+ body = '%s(%s);' % (callback, body)
+ Response.__init__(self, body=body, charset='utf8', **kwargs)
+
+ def serialize(self, obj):
+ """Return this object as a JSON string."""
+ return json.dumps(obj)
+
+
+class App(decoroute.App):
+
+ """API Core dispatcher."""
+
+ def __init__(self, prefix='', key='dream.app'):
+ decoroute.App.__init__(self, prefix, key)
+ self.map = dict(((method, decoroute.UrlMap()) for method in ('HEAD', 'GET', 'POST', 'PUT', 'DELETE')))
+ self.not_found(lambda e: exc.HTTPNotFound(detail='Not found'))
+ self._render = self._render_response
+
+ def route(self, env):
+ """Route a request.
+
+ Checks the method-specific map first, then the global as a fallback.
+ """
+ env[self._key] = self
+ path, num = self._prefix[1].subn('', env['PATH_INFO'])
+ if num != 1:
+ raise exc.HTTPNotFound()
+
+ try:
+ endpoint, kwargs = self.map[env['REQUEST_METHOD']].route(path)
+ return endpoint(Request(env, charset='utf-8'), **kwargs)
+
+ except decoroute.NotFound, nfex:
+ new_ex = exc.HTTPNotFound(' '.join(nfex.args))
+ if not hasattr(new_ex, '__traceback__'):
+ new_ex.__traceback__ = sys.exc_info()[-1]
+ return new_ex
+
+ except Exception, ex:
+ logger.exception(ex)
+ if not hasattr(ex, '__traceback__'):
+ ex.__traceback__ = sys.exc_info()[-1]
+ return ex
+
+ def expose(self, pattern, method='GET', function=None, **kwargs):
+ """Register a URL pattern for a specific HTTP method."""
+ if method not in self.map:
+ raise Exception('No such method: %s' % method)
+
+ def decorate(function):
+ """Add this function to the method map."""
+ self.map[method].add(pattern, function, **kwargs)
+ return function
+
+ return decorate(function) if function else decorate
+
+ def _mangle_response(self, resp):
+ """Mangle the response, if warranted."""
+ if (isinstance(resp, Response) and not isinstance(resp, exc.HTTPInternalServerError)):
+ return resp
+
+ if not isinstance(resp, Exception):
+ resp = Exception('Expected a Response object, got %s instead.' % str(type(resp)))
+ resp.__traceback__ = extract_stack()
+
+ return _exception_to_response(resp)
+
+ def _render_response(self, env, in_resp):
+ """Render the Response object into WSGI format."""
+ resp = self._mangle_response(in_resp)
+ return (resp.status, resp.headers.items(), resp.app_iter)
+
+
+def _exception_to_response(exception):
+ """Return a JSONResponse representing an uncaught Exception."""
+ return JSONResponse(status=getattr(exception, 'status', 500), body={
+ 'detail': (exception.detail or exception.explanation) if isinstance(exception, exc.HTTPException) else 'An internal error occured.',
+ })
View
8 kestrelweb/kestrel_actions.py
@@ -5,18 +5,18 @@
import kestrel
-def action(command, server_queues):
+def action(command, server_params):
jobs = [
- gevent.spawn(getattr(kestrel.Client(servers=[server], queue=queue), command))
- for server, queue in server_queues
+ gevent.spawn(getattr(kestrel.Client(servers=[server]), command), *params)
+ for server, params in server_params
]
gevent.joinall(jobs)
return [job.value for job in jobs if job.value is not None]
def stats(servers):
def worker(server):
- return kestrel.Client(servers=[server], queue=None).stats()
+ return kestrel.Client(servers=[server]).stats()
jobs = [gevent.spawn(worker, server) for server in servers]
gevent.joinall(jobs)
View
28 kestrelweb/main.py
@@ -1,8 +1,9 @@
-import dream
+import logging.config
-import local_settings
+import local_settings; logging.config.fileConfig(local_settings.logging_config)
+import dream
import kestrel_actions
import util
@@ -17,9 +18,9 @@ def home(request):
@App.expose('/ajax/action.json')
def ajax_action(request):
- callback = request.str_params['callback'] if 'callback' in request.str_params else None
- action = request.str_params['action'] if 'action' in request.str_params else None
- server_queue = request.str_params.getall('server') if 'server' in request.str_params else []
+ callback = request.params['callback'] if 'callback' in request.params else None
+ action = request.params['action'] if 'action' in request.params else None
+ server_queue = request.params.getall('server') if 'server' in request.params else []
data = {}
status = 200
@@ -31,7 +32,10 @@ def ajax_action(request):
actions = []
for _sq in server_queue:
(server, queue) = _sq.split(',', 1) if _sq.count(',') else (_sq, None)
- actions.append((server, queue))
+ if action in ['flush', 'delete', 'peek']:
+ actions.append((server, [queue]))
+ else:
+ actions.append((server, []))
data['results'] = kestrel_actions.action(action, actions)
else:
data['error'] = 'Invalid action'
@@ -42,11 +46,11 @@ def ajax_action(request):
@App.expose('/ajax/stats.json')
def ajax_stats(request):
- callback = request.str_params['callback'] if 'callback' in request.str_params else None
- servers = request.str_params['servers'] if 'servers' in request.str_params else None
- qsort = request.str_params['qsort'] if 'qsort' in request.str_params else None
- qreverse = int(request.str_params['qreverse']) if 'qreverse' in request.str_params else 0
- qfilter = request.str_params['qfilter'] if 'qfilter' in request.str_params else None
+ callback = request.params['callback'] if 'callback' in request.params else None
+ servers = request.params['servers'] if 'servers' in request.params else None
+ qsort = request.params['qsort'] if 'qsort' in request.params else None
+ qreverse = int(request.params['qreverse']) if 'qreverse' in request.params else 0
+ qfilter = request.params['qfilter'] if 'qfilter' in request.params else None
response = {}
if servers:
@@ -78,7 +82,7 @@ def ajax_stats(request):
@App.expose('/ajax/config.json')
def templates(request):
- callback = request.str_params['callback'] if 'callback' in request.str_params else None
+ callback = request.params['callback'] if 'callback' in request.params else None
return dream.JSONResponse(callback=callback, body={
'servers': [{'server': server} for server in local_settings.servers],
View
3 local_settings.py.example
@@ -1,3 +1,6 @@
+
+logging_config = 'logging.conf'
+
servers = [
'127.0.0.1:22133',
]
View
22 logging.conf
@@ -0,0 +1,22 @@
+[loggers]
+keys=root
+
+[handlers]
+keys=consoleHandler
+
+[formatters]
+keys=simpleFormatter
+
+[logger_root]
+level=WARNING
+handlers=consoleHandler
+
+[handler_consoleHandler]
+class=StreamHandler
+level=DEBUG
+formatter=simpleFormatter
+args=(sys.stdout,)
+
+[formatter_simpleFormatter]
+format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
+datefmt=
View
5 requirement.txt
@@ -0,0 +1,5 @@
+WebOb==1.2b2
+gevent==0.13.6
+decoroute==0.8.1
+gunicorn==0.13.4
+pykestrel==0.5.1
View
13 setup.py
@@ -7,22 +7,21 @@
setup(
name='kestrelweb',
- version='0.0.1',
+ version='0.5.0',
description='Kestrel Web UI',
author='Matt Erkkila',
author_email='matt@matterkkila.com',
url='http://github.com/matterkkila/kestrelweb',
install_requires=[
- 'pykestrel>=0.0.6',
- 'dream',
- 'gevent>=0.13.5',
- 'gunicorn>=0.12.1'
+ 'decoroute>=0.8.1',
+ 'gevent>=0.13.6',
+ 'gunicorn>=0.13.4',
+ 'pykestrel>=0.5.1',
+ 'WebOb>=1.2b2',
],
setup_requires=[],
packages=find_packages(exclude=['ez_setup']),
include_package_data=True,
- test_suite='nose.collector',
- tests_require=['nose>=0.11.4'],
package_data={},
zip_safe=False,
)

0 comments on commit 62486f3

Please sign in to comment.
Something went wrong with that request. Please try again.