http_server_async: add blueprints (sub-apps)

This commit is contained in:
Izalia Mae 2021-11-07 15:43:34 -05:00
parent 7023f4fa40
commit 0dd58fc06e
5 changed files with 130 additions and 69 deletions

View file

@ -20,3 +20,7 @@ class MethodNotHandledException(Exception):
def __init__(self, method):
super().__init__(f'HTTP method not handled by handler: {method}')
self.method = method
class NoBlueprintForPath(Exception):
'raise when no blueprint is found for a specific path'

View file

@ -1,7 +1,7 @@
http_methods = ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE']
from .application import Application
from .application import Application, Blueprint
from .misc import Cookies, Headers
from .request import Request
from .response import Response

View file

@ -12,7 +12,7 @@ from .view import Static, Manifest, Robots, Style
from .. import logging
from ..dotdict import DotDict
from ..exceptions import MethodNotHandledException
from ..exceptions import MethodNotHandledException, NoBlueprintForPath
from ..misc import signal_handler
from ..path import Path
from ..template import Template
@ -21,26 +21,12 @@ from ..template import Template
frontend = Path(__file__).resolve().parent.parent.join('http_frontend')
class Application:
class ApplicationBase:
ctx = DotDict()
def __init__(self, loop=None, views=[], middleware=[], **kwargs):
if loop:
self.loop = loop
else:
try:
self.loop = asyncio.get_running_loop()
except RuntimeError:
self.loop = asyncio.new_event_loop()
self._server = None
self._tasks = []
def __init__(self, views=[], middleware=[], **kwargs):
self.cfg = Config(**kwargs)
self.router = Router(trim_last_slash=True)
#self.router = Router()
self.middleware = DotDict({'request': [], 'response': []})
for view in views:
@ -49,27 +35,6 @@ class Application:
for mw in middleware:
self.add_middleware(mw)
if self.cfg.tpl_default:
self.template = Template(
self.cfg.tpl_search,
self.cfg.tpl_globals,
self.cfg.tpl_context,
self.cfg.tpl_autoescape
)
self.template.add_env('app', self)
self.template.add_env('cfg', self.cfg)
self.template.add_env('len', len)
self.template.add_search_path(frontend)
self.add_view(Manifest)
#self.add_view(Robots)
self.add_view(Style)
self.add_static('/framework/static/', frontend.join('static'))
else:
self.template = None
def __getitem__(self, key):
return self.ctx[key]
@ -137,6 +102,99 @@ class Application:
mwlist.append(handler)
async def handle_request(self, request, response, path=None):
handler = self.get_route(path or request.path, request.method)
await self.handle_middleware(request)
if handler.params:
handler_response = await handler.target(request, response, **handler.params)
else:
handler_response = await handler.target(request, response)
if isinstance(handler_response, dict):
response = self.cfg.response_class(**handler_response)
elif isinstance(handler_response, Response):
response = handler_response
elif not handler_response:
pass
else:
raise error.InternalServerError()
await self.handle_middleware(request, response)
return response
async def handle_middleware(self, request, response=None):
for middleware in self.middleware['response' if response else 'request']:
if response:
await middleware(request, response)
else:
await middleware(request)
class Application(ApplicationBase):
ctx = DotDict()
def __init__(self, loop=None, **kwargs):
super().__init__(**kwargs)
if loop:
self.loop = loop
else:
try:
self.loop = asyncio.get_running_loop()
except RuntimeError:
self.loop = asyncio.new_event_loop()
self._blueprints = {}
self._server = None
self._tasks = []
if self.cfg.tpl_default:
self.template = Template(
self.cfg.tpl_search,
self.cfg.tpl_globals,
self.cfg.tpl_context,
self.cfg.tpl_autoescape
)
self.template.add_env('app', self)
self.template.add_env('cfg', self.cfg)
self.template.add_env('len', len)
self.template.add_search_path(frontend)
self.add_view(Manifest)
#self.add_view(Robots)
self.add_view(Style)
self.add_static('/framework/static/', frontend.join('static'))
else:
self.template = None
def add_blueprint(self, bp):
assert bp.prefix not in self._blueprints.values()
self._blueprints[bp.prefix] = bp
def get_blueprint_for_path(self, path):
for bppath, bp in self._blueprints.items():
if path.startswith(bppath):
return bp
raise NoBlueprintForPath(path)
def render(self, *args, **kwargs):
return self.template.render(*args, **kwargs)
@ -207,29 +265,16 @@ class Application:
response = self.cfg.response_class(request=request)
await request.parse_headers()
handler = self.get_route(request.path, request.method)
try:
# this doesn't work all the time for some reason
blueprint = self.get_blueprint_for_path(request.path)
response = await blueprint.handle_request(request, response, blueprint.prefix)
await self.handle_middleware(request)
except NoBlueprintForPath:
response = await self.handle_request(request, response)
if handler.params:
handler_response = await handler.target(request, response, **handler.params)
else:
handler_response = await handler.target(request, response)
if isinstance(handler_response, dict):
response = self.cfg.response_class(**handler_response)
elif isinstance(handler_response, Response):
response = handler_response
elif not handler_response:
pass
else:
raise error.InternalServerError()
await self.handle_middleware(request, response)
except Exception as e:
traceback.print_exc()
except NotFound:
response = self.cfg.response_class(request=request).set_error('Not Found', 404)
@ -255,7 +300,7 @@ class Application:
writer.write(response.compile())
await writer.drain()
if request:
if request and not request.path.startswith('/framework'):
logging.info(f'{request.remote} {request.method} {request.path} {response.status} {len(response.body)} {request.agent}')
except:
@ -265,10 +310,8 @@ class Application:
await writer.wait_closed()
async def handle_middleware(self, request, response=None):
for middleware in self.middleware['response' if response else 'request']:
if response:
await middleware(request, response)
class Blueprint(ApplicationBase):
def __init__(self, prefix, **kwargs):
super().__init__(**kwargs)
else:
await middleware(request)
self.prefix = prefix

View file

@ -18,6 +18,11 @@ cookie_params = {
'HttpOnly': 'httponly'
}
request_methods = [
'GET', 'HEAD', 'POST', 'PUT', 'DELETE',
'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'
]
class Cookies(DotDict):
def __setitem__(self, key, value):

View file

@ -1,4 +1,4 @@
from sqlalchemy import text
from sqlalchemy import text, or_
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.session import Session as sqlalchemy_session
@ -80,7 +80,10 @@ class Session(sqlalchemy_session):
def count(self, table_name, **kwargs):
return self.query(self.table[table_name]).filter_by(**kwargs).count()
if kwargs:
return self.query(self.table[table_name]).filter_by(**kwargs).count()
return self.query(self.table[table_name]).count()
def fetch(self, table, single=True, orderby=None, orderdir='asc', **kwargs):
@ -112,6 +115,12 @@ class Session(sqlalchemy_session):
return self.fetch(*args, single=False, **kwargs)
# not finished
def like(self, table, orderby=None, orderdir='asc', **kwargs):
assert len(kwargs)
query = self.query(self.table[table]).filter(or_(self.table[table].filename.like(f'')))
def insert(self, table, return_row=False, **kwargs):
row = self.fetch(table, **kwargs)