Source code for glass.app

import logging
import os

from glass.sessions import SessionManager
from glass.config import Config
from glass.exception import (HTTPError, InternalServerError)
from glass.requests import request
from glass.response import JsonResponse, Response, Redirect, send_static
from glass.routing import Router, Rule
from glass.templating import AppTemplateEnviron, AppTemplateLoader, Cache
from glass.templating import JinjaEnvironment, JinjaFileLoader
from glass.utils import cached_property
from ._helpers import app_stack

logger = logging.getLogger('glass.app')
stream = logging.StreamHandler()
stream.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s:%(levelname)s: %(message)s',
                              datefmt='%d/%m/%Y %H:%M:%S %p')
stream.setFormatter(formatter)
logger.addHandler(stream)
logger.setLevel(logging.DEBUG)


[docs]class GlassApp: """Main application object :: from glass import GlassApp app = GlassApp() @app.route('/') def home(): return 'Hello' """ def __init__(self, **kwargs): self.session_cls = SessionManager() self.config = Config() self.router = Router() self.error_code_handlers = {} self.error_handlers = {} self.before_request_funcs = [] self.after_request_funcs = [] self.url_rules = [] self.view_func = {} static_url = self.config['STATIC_URL'] or 'static' static_url = static_url.strip('/') self.add_url_rule('/%s/<path:filename>' % static_url, self.send_static, view_name='static')
[docs] @cached_property() def template_env(self): """Returns :class:`~glass.template.main.Environment` instance.""" env = AppTemplateEnviron( self, loader=AppTemplateLoader(path=self.config['TEMPLATES_FOLDER']), cache=self.template_cache) return env
@cached_property() def jinja_env(self): path = self.config['TEMPLATES_FOLDER'] if not path: path = os.path.abspath(os.path.join(os.getcwd(), 'templates')) env = JinjaEnvironment(self, loader=JinjaFileLoader(path)) return env def send_static(self, filename): return send_static(filename, self, request) @cached_property() def template_cache(self): return Cache()
[docs] def route(self, url_rule, methods='GET', view_name=None, **kwargs): """Register a view function for URL as decorator :: @app.route('/') def index(): return 'Hello' """ def decorator(func): self._add_rule(url_rule, func, methods, view_name, **kwargs) return func return decorator
def _add_rule(self, url_rule, func, methods, view_name=None, **kwargs): if not methods: methods = ["GET"] if isinstance(methods, str): methods = [methods] methods = list(map(str.upper, methods)) rule = Rule(url_rule, func, methods, **kwargs) self.router.add(rule) self.url_rules.append(rule) if not view_name: view_name = func.__name__ self.view_func[view_name] = rule def add_url_rule(self, rule, func, methods=None, view_name=None): return self.route(rule, methods, view_name)(func) def get(self, url_rule, **kwargs): return self.route(url_rule, 'GET', **kwargs) def post(self, url_rule, **kwargs): return self.route(url_rule, 'POST', **kwargs)
[docs] def before_request(self, func): """Register a function to run before each request. For example, this can be used to open a database connection, or to load logged in user from session. :: @app.before_request def load_user(): id = session.get('user_id') if id: user = db.get(id=id) request.user = user else: # make sure to set request.user = value # to avoid python raising AttributeError request.user = None If the function doest not return None, the return value will be used as the response. :: @app.before_request def maintenance(): return "This site is under maintenance" :: def load_user(): pass app.before_request(load_user) """ self.before_request_funcs.append(func) return func
[docs] def after_request(self, func): """Register a function to run after each request. This can be used to add header(s) or cookie(s) to the response. The function takes one argument,(:class:`glass.response.Response`) and must return the same response object. :: @app.after_request def set(response): response.set_header('name','value') response.set_cookie('name','value',path='/') return response @app.after_request def turn_upper(response): if isinstance(response.content,(str,bytes)): response.content = response.content.upper() return response """ self.after_request_funcs.append(func) return func
[docs] def error(self, error): """Register a function to call when an error occurs in the application. The function can be registered with code or exception class. The function takes the exception class as argument. :: @app.error(404): def not_found(err): return "path not found" # with exception class @app.error(TypeError) def type_error(error): assert error.__class__ is TypeError return 'TypeError exception occurs' """ def decorator(func): if isinstance(error, int): self.error_code_handlers[error] = func else: self.error_handlers[error] = func return func return decorator
def url_converter(self, name, regex, func): self.router.add_converter(name, regex, func)
[docs] def run(self, host='127.0.0.1', port=8000, debug=None, auto_reload=False): """Run the application development server. :param host: ip address to listen on. default to localhost ``127.0.0.1`` :param port: port for the server to listen. default to ``8000`` :param auto_reload: enable reloader. reload the server when the app source files change. default to ``False`` :param debug: run the app in debug mode. :: app = GlassApp() @app.route('/') def index(): return "Hello" app.run(debug=True) """ if debug is not None: self.config['DEBUG'] = bool(debug) from glass.server import GlassServer GlassServer().run_app(self, host, port, auto_reload)
[docs] def mount(self, environ=None): ''' see :ref:`doc <mount-app>`''' app_stack.push(self) if environ is not None: request.bind(environ) self.session_cls.open() return app_stack
def _call_before_request(self): for func in self.before_request_funcs: response = func() if response: return response def _call_after_request(self, response): return_value = None for func in self.after_request_funcs: return_value = func(response) if not isinstance(return_value, response.__class__): raise TypeError( 'after_request function should return %s not %s' % (response.__class__, return_value.__class__)) if not return_value: return response return return_value def _call_callback(self, environ): try: rule, kwargs = self.router.match(environ) method = environ.get("REQUEST_METHOD", 'GET') if rule.url_rule.endswith('/'): if not environ['PATH_INFO'].endswith('/'): return Redirect(environ['PATH_INFO'] + '/', status_code=307) callback = rule.get_callback(method) response = self._call_before_request() if not response: response = callback(**kwargs) except HTTPError as exc: response = self._handle_http_exc(exc) except Exception as exc: response = self._handle_app_exc(exc) response = self._build_response(response) return self._call_after_request(response) def _handle_app_exc(self, exc): if not self.config['DEBUG']: if hasattr(exc, 'code'): handler = self.error_code_handlers.get(exc.code) else: handler = self.error_handlers.get(exc.__class__) if handler: self.log_exception(exc) return handler(exc) exc = InternalServerError(code=500) return self._handle_http_exc(exc) def _handle_http_exc(self, exc): self.log_exception(exc) debug = self.config['DEBUG'] if not debug: error_handler = self.error_code_handlers.get(exc.code) if error_handler: return error_handler(exc) if exc.code < 500: debug = False headers = exc.headers() resp = exc.get_response(debug=debug) return Response(resp, status_code=exc.code, headers=headers) # response = exc.response(debug=config['DEBUG']) def log_exception(self, exc): code = 0 if hasattr(exc, 'code'): code = exc.code if code and code < 500: logger.debug('''[{exc}] {cls} {path} {code}'''.format( exc=exc, cls=exc.__class__.__name__, path=request.path, code=code)) else: logger.exception('Error in path [%s]' % request.path) def _build_response(self, response): if isinstance(response, Response): return response if isinstance(response, (str, bytes)): return Response(response) if isinstance(response, dict): return JsonResponse(response) if isinstance(response, tuple): try: response, code = response except (ValueError, TypeError): raise TypeError('view return unknown response') response = self._build_response(response) response.status_code = code return response raise TypeError('view return unknown response type %s' % response.__class__) def _get_response(self, environ): request.bind(environ) request.app = self with self.mount(): self.session_cls.open() response = self._call_callback(environ) self.session_cls.save(response) return response def __call__(self, environ, start_response): response = self._get_response(environ) response.start_response(environ, start_response) return response