ooze/ooze/web.py

190 lines
6.0 KiB
Python

import os
import functools
import logging
from . import exceptions
from . import protocol
from . import routing
from . import streaming
from . import rendering
class SiteCompositionPiece:
"""
Piece of a site, the composition of a multiple of which makes
up an entire site. Base class for Application and Subsite
handling the functionality both have in common.
"""
request_class = protocol.Request
response_class = protocol.Response
router_class = routing.Router
#renderer_class = rendering.JinjaRenderer
renderer_class = rendering.DOMinionRenderer
def __init__(self):
self.ooze_path = os.path.dirname(os.path.realpath(__file__))
self.project_path = os.getcwd()
self.logger = logging.getLogger(__name__)
self.logger.setLevel('DEBUG')
theme = 'default' # TODO: Read from config
self.theme = theme
self.theme_directories = []
ooze_default_path = f'{self.ooze_path}/themes/default'
project_default_path = f'{self.project_path}/themes/default'
if theme != 'default':
ooze_theme_path = f'{self.ooze_path}/themes/{theme}'
project_theme_path = f'{self.project_path}/themes/{theme}'
self.theme_directories.append(project_theme_path)
self.theme_directories.append(ooze_theme_path)
self.theme_directories.append(project_default_path)
self.theme_directories.append(ooze_default_path)
self.theme_directories = tuple(self.theme_directories)
self.subsites = {}
self.router = self.router_class(self)
self.renderer = self.renderer_class(self)
@property
def root(self):
raise exceptions.OozeProgrammingError("SiteCompositionPiece MUST be subclassed and can't be used directly!")
@property
def absolute_root(self):
raise exceptions.OozeProgrammingError("SiteCompositionPiece MUST be subclassed and can't be used directly!")
def route(self, rule):
def decorator(func):
self.router.add_route(rule, func)
return func
return decorator
def expose(self, rule, mode='full'):
def decorator(cls):
self.router.add_route(rule, functools.partial(cls.view, mode=mode))
return cls
return decorator
def register_subsite(self, root, subsite):
if root.startswith('/') or root.startswith('/'):
raise exceptions.OozeProgrammingError(f"Subsite path MUST NOT start or end with '/', but {path} does!")
self.subsites[root] = subsite
subsite._register(self, root)
def handle_request(self, request):
relative_path = request.path[len(self.absolute_root):]
for subsite_root, subsite in self.subsites.items():
if relative_path.startswith(subsite_root):
self.logger.debug(f'Dispatching request to subsite of {self}: /{relative_path}')
return subsite.handle_request(request)
self.logger.debug(f"Trying to resolve request to {request.path}")
route, view_kwargs = self.router.find_route(request.path)
self.logger.debug(f"Found matching route: {route}!")
rv = route.view(request, **view_kwargs)
response = self.response_class(request)
if isinstance(rv, self.response_class):
response = rv
elif isinstance(rv, tuple): # string + HTTPStatus
rv, response.status = rv
if isinstance(rv, rendering.Renderable):
response.body = rv.render(self, format='html', mode='full')
if isinstance(rv, streaming.FileStream):
if request.ranges:
response.body = rv.stream(request.ranges)
else:
response.body = rv.stream()
else:
response.body = str(rv)
return response
class Subsite(SiteCompositionPiece):
"""
Composable piece of a site that can be bound to a path and will handle any
requests going to the "directory" denoted by its path.
Essentially the Ooze equivalent of Flasks Blueprints.
Can be used to organize sites as well as to implement re-usable
subsites independent from any particular Ooze application or to
use different request, response, router and renderer classes in
parallel within the same project.
## Class attributes ##
* request_class: See [Application.request_class]
* response_class: See [Application.response_class]
* router_class: See [Application.router_class]
* renderer_class: See [Application.renderer_class]
## Object attributes ##
* router: See [Application.router]
* renderer: See [Application.renderer]
* dispatcher: The object dispatching calls to this class.
For non-nested Subsites, this is the [Application].
* root: The path relative to the app or parent Subsite acting
as "root directory" for this Subsite.
"""
dispatcher = None
root = None
absolute_root = None
def __init__(self):
super().__init__()
def __repr__(self):
if self.absolute_root == None:
return f"<{self.__class__.__name__}, unregistered>"
return f"<{self.__class__.__name__} at {self.absolute_root}>"
def _register(self, dispatcher, root):
if dispatcher.absolute_root == None:
raise exceptions.OozeProgrammingError(f"Tried to register a subsite to an unregistered dispatcher. You likely want to do app.register_subsite(<parent of {root}>. 'some/path').")
self.dispatcher = dispatcher
self.root = root
self.absolute_root = f'{dispatcher.absolute_root}{root}/'
self.logger.debug(f"Registered subsite at '{self.absolute_root}'.")
def handle_request(self, request):
if self.dispatcher == None:
raise exceptions.OozeProgrammingError(f"Tried to dispatch request to unregistered Subsite: { request.path }. Call app.register_subsite(<subsite>).")
return super().handle_request(request)