Source code for glass.app

import logging
import os

from glass.config import Config
from glass.exception import HTTPError, InternalServerError
from glass.requests import request
from glass.response import JsonResponse, Redirect, Response, send_static
from glass.routing import Router, Rule
from glass.sessions import SessionManager
from glass.templating import (
    AppTemplateEnviron,
    AppTemplateLoader,
    Cache,
    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) def use_converter(self, converter): self.router.use_converter(converter)
[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) """ from_cli = os.environ.get("GLASS_FROM_CLI") if from_cli: return 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 return None 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 close_resources(self): request.close() 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) self.close_resources() return response def __call__(self, environ, start_response): response = self._get_response(environ) response.start_response(environ, start_response) return response