_utils.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 collections import namedtuple 9 10 import anvil 11 from anvil.js.window import location 12 13 from ._logging import logger 14 15 __version__ = "3.1.0" 16 17 18 ANY = object() 19 20 21 def get_url_components(url_hash=None): 22 """returns url_hash, url_pattern, url_dict 23 this will get the components from the current addressbar url_hash unless you provide a url_hash to decode 24 """ 25 if url_hash is None: 26 # url_hash = anvil.get_url_hash() #changed since anvil decodes the url_hash 27 url_hash = location.hash[1:] # without the hash 28 elif isinstance(url_hash, str): 29 url_hash = url_hash if not url_hash.startswith("#") else url_hash[1:] 30 31 if isinstance(url_hash, dict): 32 # this is the case when anvil converts the url hash to a dict automatically 33 url_pattern = "" 34 url_dict = { 35 k: (anvil.http.url_decode(v) if v != "undefined" else "") 36 for k, v in url_hash.items() 37 } # anvil.get_url_hash return 'undefined' for empty parameters 38 url_hash = "?" + "&".join( 39 f"{key}={anvil.http.url_encode(value)}" for key, value in url_dict.items() 40 ) 41 elif "?" not in url_hash: # then we have no parameters as part of the url 42 url_pattern = url_hash 43 url_dict = {} 44 else: 45 url_pattern, url_dict = url_hash.split("?", 1) 46 key_value_pairs = url_dict.split("&") 47 for i, pair in enumerate(key_value_pairs): 48 if "=" not in pair: 49 logger.debug( 50 f"\n\n**WARNING**:\ngot an unusual url parameter with no '=': {pair!r}" 51 f"\nIf this parameter split unexpectedly it probably contains '&'. Use:" 52 f"\nrouting.set_url_hash(url_pattern=url_pattern, url_dict=url_dict)" 53 f"\nFor correct encoding\n" 54 ) 55 key_value_pairs[i] = pair = pair + "=" 56 key, value = pair.split("=", 1) 57 key_value_pairs[i] = f"{key}={anvil.http.url_decode(value)}" 58 url_dict = dict(pair.split("=", 1) for pair in key_value_pairs) 59 60 return url_hash, url_pattern, url_dict 61 62 63 def get_url_hash(url_hash=None) -> str: 64 """returns the current url_hash""" 65 if url_hash is None: 66 return location.hash[1:] 67 return get_url_components(url_hash=url_hash)[0] 68 69 70 def get_url_pattern(url_hash=None) -> str: 71 """returns the current url_dict unless a url_pattern is provided""" 72 return get_url_components(url_hash=url_hash)[1] 73 74 75 def get_url_dict(url_hash=None) -> dict: 76 """returns the current url_dict unless a url_hash is provided""" 77 return get_url_components(url_hash=url_hash)[2] 78 79 80 def _process_url_arguments(url_hash=None, *, url_pattern=None, url_dict=None): 81 """ 82 check and set_up the url_hash, url_pattern and url_dict 83 """ 84 if url_dict is not None and url_pattern is None: 85 raise TypeError( 86 "if you provide a url_dict you must provide a url_pattern as a keyword argument url_pattern=" 87 ) 88 if url_hash is None and url_pattern is None: 89 url_hash = "" # default behaviour should be an empty string 90 elif url_hash is None: 91 url_dict = {} if url_dict is None else url_dict 92 url_hash = _get_url_hash(url_pattern, url_dict) 93 url_hash, url_pattern, url_dict = get_url_components( 94 url_hash 95 ) # will convert to a string 96 return url_hash, url_pattern, url_dict 97 98 99 def _get_url_hash(url_pattern, url_dict): 100 url_params = "&".join( 101 f"{key}={anvil.http.url_encode(str(value))}" for key, value in url_dict.items() 102 ) 103 url_params = "?" + url_params if url_params else "" 104 return url_pattern + url_params 105 106 107 def _as_frozen_str_iterable(obj, attr, allow_none=False, factory=frozenset): 108 if isinstance(obj, str) or (allow_none and obj is None) or obj is ANY: 109 return factory([obj]) 110 rv = [] 111 for o in obj: 112 if not isinstance(o, str) and o is not ANY: 113 msg = f"expected an iterable of strings or a string for {attr} argument" 114 raise TypeError(msg) 115 rv.append(o) 116 return factory(rv) 117 118 119 _RouteInfoBase = namedtuple( 120 "route_info", 121 ["form", "template", "url_pattern", "url_keys", "title", "fwr", "url_parts"], 122 ) 123 124 TemplateInfo = namedtuple("template_info", ["form", "path", "condition"]) 125 RedirectInfo = namedtuple("redirect_info", ["redirect", "path", "condition"]) 126 127 128 class RouteInfo(_RouteInfoBase): 129 @staticmethod 130 def as_dynamic_var(part): 131 if len(part) > 1 and part[0] == "{" and part[-1] == "}": 132 return part[1:-1], True 133 return part, False 134 135 def __new__(cls, form, template, url_pattern, url_keys, title, fwr, url_parts=()): 136 if url_pattern.endswith("/"): 137 url_pattern = url_pattern[:-1] 138 139 url_parts = tuple(cls.as_dynamic_var(part) for part in url_pattern.split("/")) 140 141 return _RouteInfoBase.__new__( 142 cls, form, template, url_pattern, url_keys, title, fwr, url_parts 143 )