Source code for dazzler.system._page

import os
import typing
from aiohttp import web

from .transforms import TiedTransform
from ..electron import ElectronWindowSettings
from ..tools import get_package_path
from ._binding import (
    Binding, BoundAspect, Trigger, State, Target, coerce_binding
)
from ._component import Component
from ._package import Package
from ._requirements import Requirement, collect_requirements
from ._route import Route, RouteMethod


# pylint: disable=too-many-instance-attributes
[docs]class Page: """A page of the application."""
[docs] def __init__( self, name: str, layout: typing.Union[Component, typing.Callable[[web.Request], typing.Awaitable[Component]]], # noqa: E501 url: str = None, bindings: list = None, routes: typing.List[Route] = None, requirements: typing.List[Requirement] = None, requirements_dir: str = 'requirements', title: str = None, lang: str = 'en', header: str = '', footer: str = '', favicon: str = '', meta_tags: typing.List[typing.Dict[str, str]] = None, packages: typing.List[str] = None, require_login: bool = False, electron_window: ElectronWindowSettings = None, authorizations: list = None ): """ :param name: Unique name for the page, usually give __name__. :param layout: Root component or function returning a Component. In the case of a function, the request will be None on setup. :param url: Url, default to ``/name``. :param bindings: Bindings of component aspects for this page. :param routes: Additional routes to register with this page. :param requirements: List of requirements to :param title: Title of the page. :param lang: lang attribute of the html tag. :param header: Static header to include as first child on the page. :param footer: Static footer to include as last child on the page. :param favicon: Url to favicon for the page. :param meta_tags: Meta tags of the page. :param packages: Packages list to use on the page instead of the whole registry. :param require_login: Page requires that user is logged in. :param electron_window: Settings for the electron window like size. :param authorizations: Rules for authorization. """ self.name = name.split('.')[-1] self.base_name = name try: self.page_path = get_package_path(name) except ImportError: # The requirements dir is then absolute. self.page_path = '' self.title = title or self.name self.url = url or f'/{self.name}' self.routes = routes or [] self.layout = layout self.bindings = bindings or [] self.requirements = requirements or [] for requirement in self.requirements: requirement.page = self.name self.requirements_dir = os.path.join(self.page_path, requirements_dir) # Collect all requirement in the requirement folder. if os.path.exists(self.requirements_dir): self.requirements += collect_requirements( self.requirements_dir, page=self.name ) self.header = header self.footer = footer self.favicon = favicon self.meta_tags = meta_tags or [] self.packages = packages self.lang = lang self._bindings = {str(x.trigger): x for x in self.bindings} self.require_login = require_login self._ties = [] self.electron_window = electron_window self.authorizations = authorizations
[docs] async def prepare( self, request: web.Request, debug=False, external=False ) -> dict: """ Prepare the page for rendering. :param request: The request to prepare for. :param debug: To collect dev package :param external: Serve external requirements if available. :return: prepared dict with layout and page name. """ layout = self.layout if callable(layout): layout = await layout(request) packages = [ Package.package_registry[x].prepare( dev=debug, external=external) for x in self.packages ] if self.packages is not None else [ x.prepare(dev=debug, external=external) for x in Package.package_registry.values() if x.name != 'dazzler_renderer' ] # noinspection PyProtectedMember return { 'layout': layout._prepare(), 'page': self.name, 'bindings': { x['key']: x for y in self.bindings for x in y.prepare() }, 'requirements': [ x.prepare(external=external) for x in self.requirements ], 'packages': packages, 'ties': [ t.prepare() for t in self._ties ] }
def _bind( self, trigger: typing.Union[Trigger, str], *states: typing.Union[State, str], once: bool = False, call: bool = False, ): trg = coerce_binding(trigger) sts = coerce_binding(list(states), State) if isinstance(trg, list): for t in trg: t.once = once elif trg.once is None: trg.once = once def _wrapper(func): binding = Binding(trg, sts, call)(func) self.bindings.append(binding) for trig in binding.triggers: self._bindings[str(trig)] = binding return func return _wrapper
[docs] def bind( self, trigger: typing.Union[Trigger, str], *states: typing.Union[State, str], once: bool = False, ): """ Attach a function to be called when the trigger update on the client. :param trigger: Aspect to trigger the binding. :param states: States to includes in the binding message. :param once: Execute the binding only once :return: """ return self._bind(trigger, *states, once=once)
[docs] def get_binding(self, key) -> BoundAspect: """ Get the binding from the trigger key. :param key: Trigger key :return: """ return self._bindings.get(key)
[docs] def route( self, path, method: typing.Union[str, RouteMethod] = RouteMethod.GET, name=None, prefix=True ): """ Add a route with a decorator. :param path: Url to handle. :param method: Method of the route. :param name: Unique name for the route. :param prefix: Prefix the path with the page path. :return: """ if prefix: url = '/'.join([self.url.rstrip('/'), path.lstrip('/')]) else: url = path def _page_route(func): self.routes.append( Route(url, func, name=name, method=method) ) return func return _page_route
[docs] def tie( self, trigger: typing.Union[Trigger, str], *target: typing.Union[Target, str], once: bool = None ): """ Update target(s) aspect on trigger from the frontend. **Example** .. code-block:: python from dazzler.system import Page from dazzler.components import core page = Page( __name__, core.Container([ core.Input(identity='input'), core.Container(identity='output') ]) ) page.tie('value@input', 'children@output') :param trigger: Aspect to set the target when the value change. :param target: Aspect to update from the trigger value. :param once: Execute the tie only once. :return: """ trig = coerce_binding(trigger, Trigger) targ = coerce_binding(list(target), Target) if trig.once is None: trig.once = once tied = TiedTransform(trig, targ) self._ties.append(tied) return tied
[docs] def call( self, trigger: typing.Union[Trigger, str], *states: typing.Union[State, str], once: bool = False, ): """ A binding that execute via traditional request. """ return self._bind(trigger, *states, once=once, call=True)
def __str__(self): return f'{self.name}@{self.url}' def __hash__(self): return hash((self.name, self.url)) def __repr__(self): return f'<Page name="{self.name}" href="{self.url}">'