http_server_async: add blueprints (sub-apps)
This commit is contained in:
parent
7023f4fa40
commit
0dd58fc06e
|
@ -20,3 +20,7 @@ class MethodNotHandledException(Exception):
|
||||||
def __init__(self, method):
|
def __init__(self, method):
|
||||||
super().__init__(f'HTTP method not handled by handler: {method}')
|
super().__init__(f'HTTP method not handled by handler: {method}')
|
||||||
self.method = method
|
self.method = method
|
||||||
|
|
||||||
|
|
||||||
|
class NoBlueprintForPath(Exception):
|
||||||
|
'raise when no blueprint is found for a specific path'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
http_methods = ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE']
|
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 .misc import Cookies, Headers
|
||||||
from .request import Request
|
from .request import Request
|
||||||
from .response import Response
|
from .response import Response
|
||||||
|
|
|
@ -12,7 +12,7 @@ from .view import Static, Manifest, Robots, Style
|
||||||
|
|
||||||
from .. import logging
|
from .. import logging
|
||||||
from ..dotdict import DotDict
|
from ..dotdict import DotDict
|
||||||
from ..exceptions import MethodNotHandledException
|
from ..exceptions import MethodNotHandledException, NoBlueprintForPath
|
||||||
from ..misc import signal_handler
|
from ..misc import signal_handler
|
||||||
from ..path import Path
|
from ..path import Path
|
||||||
from ..template import Template
|
from ..template import Template
|
||||||
|
@ -21,26 +21,12 @@ from ..template import Template
|
||||||
frontend = Path(__file__).resolve().parent.parent.join('http_frontend')
|
frontend = Path(__file__).resolve().parent.parent.join('http_frontend')
|
||||||
|
|
||||||
|
|
||||||
class Application:
|
class ApplicationBase:
|
||||||
ctx = DotDict()
|
ctx = DotDict()
|
||||||
|
|
||||||
def __init__(self, loop=None, views=[], middleware=[], **kwargs):
|
def __init__(self, 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 = []
|
|
||||||
|
|
||||||
self.cfg = Config(**kwargs)
|
self.cfg = Config(**kwargs)
|
||||||
self.router = Router(trim_last_slash=True)
|
self.router = Router(trim_last_slash=True)
|
||||||
#self.router = Router()
|
|
||||||
self.middleware = DotDict({'request': [], 'response': []})
|
self.middleware = DotDict({'request': [], 'response': []})
|
||||||
|
|
||||||
for view in views:
|
for view in views:
|
||||||
|
@ -49,27 +35,6 @@ class Application:
|
||||||
for mw in middleware:
|
for mw in middleware:
|
||||||
self.add_middleware(mw)
|
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):
|
def __getitem__(self, key):
|
||||||
return self.ctx[key]
|
return self.ctx[key]
|
||||||
|
@ -137,6 +102,99 @@ class Application:
|
||||||
mwlist.append(handler)
|
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):
|
def render(self, *args, **kwargs):
|
||||||
return self.template.render(*args, **kwargs)
|
return self.template.render(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -207,29 +265,16 @@ class Application:
|
||||||
response = self.cfg.response_class(request=request)
|
response = self.cfg.response_class(request=request)
|
||||||
await request.parse_headers()
|
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:
|
except Exception as e:
|
||||||
handler_response = await handler.target(request, response, **handler.params)
|
traceback.print_exc()
|
||||||
|
|
||||||
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 NotFound:
|
except NotFound:
|
||||||
response = self.cfg.response_class(request=request).set_error('Not Found', 404)
|
response = self.cfg.response_class(request=request).set_error('Not Found', 404)
|
||||||
|
@ -255,7 +300,7 @@ class Application:
|
||||||
writer.write(response.compile())
|
writer.write(response.compile())
|
||||||
await writer.drain()
|
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}')
|
logging.info(f'{request.remote} {request.method} {request.path} {response.status} {len(response.body)} {request.agent}')
|
||||||
|
|
||||||
except:
|
except:
|
||||||
|
@ -265,10 +310,8 @@ class Application:
|
||||||
await writer.wait_closed()
|
await writer.wait_closed()
|
||||||
|
|
||||||
|
|
||||||
async def handle_middleware(self, request, response=None):
|
class Blueprint(ApplicationBase):
|
||||||
for middleware in self.middleware['response' if response else 'request']:
|
def __init__(self, prefix, **kwargs):
|
||||||
if response:
|
super().__init__(**kwargs)
|
||||||
await middleware(request, response)
|
|
||||||
|
|
||||||
else:
|
self.prefix = prefix
|
||||||
await middleware(request)
|
|
||||||
|
|
|
@ -18,6 +18,11 @@ cookie_params = {
|
||||||
'HttpOnly': 'httponly'
|
'HttpOnly': 'httponly'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request_methods = [
|
||||||
|
'GET', 'HEAD', 'POST', 'PUT', 'DELETE',
|
||||||
|
'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Cookies(DotDict):
|
class Cookies(DotDict):
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text, or_
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from sqlalchemy.orm.session import Session as sqlalchemy_session
|
from sqlalchemy.orm.session import Session as sqlalchemy_session
|
||||||
|
|
||||||
|
@ -80,7 +80,10 @@ class Session(sqlalchemy_session):
|
||||||
|
|
||||||
|
|
||||||
def count(self, table_name, **kwargs):
|
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):
|
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)
|
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):
|
def insert(self, table, return_row=False, **kwargs):
|
||||||
row = self.fetch(table, **kwargs)
|
row = self.fetch(table, **kwargs)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue