/ client_code / routing / _decorators.py
_decorators.py
  1  # SPDX-License-Identifier: MIT
  2  #
  3  # Copyright (c) 2021 The Anvil Extras project team members listed at
  4  # https://github.com/anvilistas/anvil-extras/graphs/contributors
  5  #
  6  # This software is published at https://github.com/anvilistas/anvil-extras
  7  
  8  from functools import wraps
  9  
 10  from . import _router
 11  from ._utils import RedirectInfo, RouteInfo, TemplateInfo, _as_frozen_str_iterable
 12  
 13  __version__ = "3.1.0"
 14  
 15  
 16  def _check_types_common(path, priority, condition):
 17      if not isinstance(priority, int):
 18          raise TypeError("the template priority must be an int")
 19      if condition is not None and not callable(condition):
 20          raise TypeError("the condition must be None or a callable")
 21      return _as_frozen_str_iterable(path, "path")
 22  
 23  
 24  def template(path="", priority=0, condition=None):
 25      path = _check_types_common(path, priority, condition)
 26  
 27      def template_wrapper(cls):
 28          info = TemplateInfo(cls, path, condition)
 29          _router.add_info("template", cls, priority, info)
 30  
 31          cls_init = cls.__init__
 32  
 33          def on_show(sender, **e):
 34              sender.remove_event_handler("show", on_show)
 35              # wait till the show event so that this template is the open_form before re-navigating
 36              _router.launch()
 37  
 38          @wraps(cls_init)
 39          def init_and_route(self, *args, **kws):
 40              _router._ready = False
 41              try:
 42                  cls_init(self, *args, **kws)
 43                  handlers = self.get_event_handlers("show")
 44                  self.set_event_handler("show", on_show)
 45                  # make us the first show event handler and re-add existing
 46                  for handler in handlers:
 47                      self.add_event_handler("show", handler)
 48              finally:
 49                  _router.ready = True
 50  
 51          cls.__init__ = init_and_route
 52  
 53          return cls
 54  
 55      return template_wrapper
 56  
 57  
 58  def redirect(path, priority=0, condition=None):
 59      path = _check_types_common(path, priority, condition)
 60  
 61      def redirect_wrapper(fn):
 62          info = RedirectInfo(fn, path, condition)
 63          _router.add_info("redirect", redirect, priority, info)
 64          return fn
 65  
 66      return redirect_wrapper
 67  
 68  
 69  def route(url_pattern="", url_keys=[], title=None, full_width_row=False, template=None):
 70      """
 71      the route decorator above any form you want to load in the content_panel
 72      @routing.route(url_pattern=str,url_keys=List[str], title=str)
 73      """
 74      if not isinstance(url_pattern, str):
 75          raise TypeError(f"url_pattern must be type str not {type(url_pattern)}")
 76      if not (title is None or isinstance(title, str)):
 77          raise TypeError(f"title must be type str or None not {type(title)}")
 78      url_keys = _as_frozen_str_iterable(url_keys, "url_keys")
 79      template = _as_frozen_str_iterable(template, "template", allow_none=True)
 80  
 81      def route_wrapper(cls):
 82          info = RouteInfo(cls, template, url_pattern, url_keys, title, full_width_row)
 83          _router.add_route_info(info)
 84          return cls
 85  
 86      return route_wrapper
 87  
 88  
 89  def error_form(cls):
 90      """optional decorator - this is the error form simply use the decorator above your error Form
 91      @routing.error_form
 92      """
 93      cls._route_props = {"title": None, "layout_props": {}}
 94      _router._error_form = cls
 95      return cls
 96  
 97  
 98  def lazy_route(
 99      url_pattern="", url_keys=[], title=None, full_width_row=False, template=None
100  ):
101      route_wrapper = route(url_pattern, url_keys, title, full_width_row, template)
102  
103      def wrapper(fn):
104          class Lazy:
105              _form = None
106  
107              def __new__(cls, **properties):
108                  form_class = cls._form
109                  if form_class is None:
110                      form_class = cls._form = fn()
111                  return form_class.__new__(form_class, **properties)
112  
113          route_wrapper(Lazy)
114          return fn
115  
116      return wrapper