diff --git a/izzylib/exceptions.py b/izzylib/exceptions.py index febfd8d..5a151f9 100644 --- a/izzylib/exceptions.py +++ b/izzylib/exceptions.py @@ -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' diff --git a/izzylib/http_server_async/__init__.py b/izzylib/http_server_async/__init__.py index 8a03eb7..90b1734 100644 --- a/izzylib/http_server_async/__init__.py +++ b/izzylib/http_server_async/__init__.py @@ -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 diff --git a/izzylib/http_server_async/application.py b/izzylib/http_server_async/application.py index 8a7d6df..c532b9b 100644 --- a/izzylib/http_server_async/application.py +++ b/izzylib/http_server_async/application.py @@ -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 diff --git a/izzylib/http_server_async/misc.py b/izzylib/http_server_async/misc.py index a0cc879..eb6701c 100644 --- a/izzylib/http_server_async/misc.py +++ b/izzylib/http_server_async/misc.py @@ -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): diff --git a/izzylib/sql/session.py b/izzylib/sql/session.py index 74cd8ed..d5bf2c9 100644 --- a/izzylib/sql/session.py +++ b/izzylib/sql/session.py @@ -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)