commit a3c69c277187a1f9ebd2217e1928ed9f4cf338cd Author: Dustin C. Hatch Date: Thu Dec 31 21:16:07 2015 -0600 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18089fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/dist/ +*.egg-info/ +__pycache__/ +*.py[co] +*.ini diff --git a/debug.py b/debug.py new file mode 100644 index 0000000..36954c0 --- /dev/null +++ b/debug.py @@ -0,0 +1,43 @@ +from __future__ import unicode_literals +import gunicorn.app.base +import os +import webob.static +import logging + + +config = os.path.join(os.path.dirname(__file__), 'development.ini') + + +class DebugApplication(object): + + def __init__(self, app, staticpath='.'): + self.app = app + self.static = webob.static.DirectoryApp(os.path.realpath(staticpath)) + + def __call__(self, environ, start_response): + if os.path.exists(environ['PATH_INFO'].lstrip('/')): + return self.static(environ, start_response) + else: + return self.app(environ, start_response) + + +class Server(gunicorn.app.base.BaseApplication): + + def load_config(self): + self.cfg.set('bind', '[::]:8080') + self.cfg.set('reload', True) + self.cfg.set('workers', 1) + self.cfg.set('threads', 1) + self.cfg.set('accesslog', '-') + self.cfg.set('timeout', 9001) + + def load(self): + import rouse.web + app = rouse.web.Application.create(config) + return DebugApplication(app) + + +if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG) + logging.getLogger('sqlalchemy.engine.base.Engine').propagate = False + Server().run() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1448191 --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +from setuptools import find_packages, setup + +setup( + name='Rouse', + version='0.1', + description='Wake-on-LAN sender with convenient browser user interface', + author='Dustin C. Hatch', + author_email='dustin@hatch.name', + url='', + license='GPL-3', + packages=find_packages('src'), + package_dir={'': 'src'}, + install_requires=[ + 'Milla>=0.3', + ], +) diff --git a/src/rouse/__init__.py b/src/rouse/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/rouse/web/__init__.py b/src/rouse/web/__init__.py new file mode 100644 index 0000000..29f8c41 --- /dev/null +++ b/src/rouse/web/__init__.py @@ -0,0 +1,92 @@ +from __future__ import unicode_literals +from milla.dispatch import routing +import functools +import jinja2 +import json +import milla.util +import os + + +class SmartJSONEncoder(json.JSONEncoder): + + def default(self, obj): # pylint: disable=method-hidden + if hasattr(obj, 'to_json'): + return obj.to_json() + super(SmartJSONEncoder, self).default(obj) + + +class VariedResponse(milla.Response): + + TMPL_LOADER = jinja2.PackageLoader(__name__) + + def __init__(self, request, *args, **kwargs): + super(VariedResponse, self).__init__(*args, **kwargs) + self.request = request + + @classmethod + def for_request(cls, request): + return functools.partial(cls, request) + + def set_payload(self, template, context=None): + if not self.vary: + self.vary = ['Accept'] + elif 'accept' not in (v.lower() for v in self.vary): + self.vary += 'Accept', + if context is None: + context = {} + if template is None or self.request.want == 'json': + self.content_type = str('application/json') + json.dump(context, self.body_file, cls=SmartJSONEncoder) + else: + if self.request.want == 'xhtml': + self.content_type = str('application/xhtml+xml') + self.render(template, context) + + def render(self, template, context=None): + if context is None: + context = {} + env = jinja2.Environment(loader=self.TMPL_LOADER) + env.globals.update( + url=self.request.create_href, + static=self.request.static_resource, + ) + if hasattr(self.request, 'user'): + env.globals['user'] = self.request.user + self.text = env.get_template(template).render(**context) + + +class Application(milla.Application): + + def __init__(self, config=None): # pylint: disable=super-init-not-called + self.config = {} + if config and os.path.exists(config): + self.config.update(milla.util.read_config(config)) + self.config['milla.favicon'] = False + + self.setup_routes() + + @classmethod + def create(cls, config): + app = cls(config) + return app + + def setup_routes(self): + self.dispatcher = r = routing.Router() + + def make_request(self, environ): + request = super(Application, self).make_request(environ) + default_format = VariedResponse.default_content_type + offers = [ + 'application/json', + 'application/xhtml+xml', + 'text/html', + ] + want_fmt = request.accept.best_match(offers, default_format) + if want_fmt == 'application/json': + request.want = 'json' + elif want_fmt == 'text/html': + request.want = 'html' + else: + request.want = 'xhtml' + request.ResponseClass = VariedResponse.for_request(request) + return request