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