_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