Given a path string, like: "/{username:str}", or a host string, like: "{subdomain}.mydomain.org", return a three-tuple of (regex, format, {param_name:convertor}). regex: "/(?P<username>[^/]+)" format: "/{username}" convertors: {"username": StringConvertor()}
(
path: str,
)
| 108 | |
| 109 | |
| 110 | def compile_path( |
| 111 | path: str, |
| 112 | ) -> tuple[Pattern[str], str, dict[str, Convertor[Any]]]: |
| 113 | """ |
| 114 | Given a path string, like: "/{username:str}", |
| 115 | or a host string, like: "{subdomain}.mydomain.org", return a three-tuple |
| 116 | of (regex, format, {param_name:convertor}). |
| 117 | |
| 118 | regex: "/(?P<username>[^/]+)" |
| 119 | format: "/{username}" |
| 120 | convertors: {"username": StringConvertor()} |
| 121 | """ |
| 122 | is_host = not path.startswith("/") |
| 123 | |
| 124 | path_regex = "^" |
| 125 | path_format = "" |
| 126 | duplicated_params: set[str] = set() |
| 127 | |
| 128 | idx = 0 |
| 129 | param_convertors = {} |
| 130 | for match in PARAM_REGEX.finditer(path): |
| 131 | param_name, convertor_type = match.groups("str") |
| 132 | convertor_type = convertor_type.lstrip(":") |
| 133 | assert convertor_type in CONVERTOR_TYPES, f"Unknown path convertor '{convertor_type}'" |
| 134 | convertor = CONVERTOR_TYPES[convertor_type] |
| 135 | |
| 136 | path_regex += re.escape(path[idx : match.start()]) |
| 137 | path_regex += f"(?P<{param_name}>{convertor.regex})" |
| 138 | |
| 139 | path_format += path[idx : match.start()] |
| 140 | path_format += "{%s}" % param_name |
| 141 | |
| 142 | if param_name in param_convertors: |
| 143 | duplicated_params.add(param_name) |
| 144 | |
| 145 | param_convertors[param_name] = convertor |
| 146 | |
| 147 | idx = match.end() |
| 148 | |
| 149 | if duplicated_params: |
| 150 | names = ", ".join(sorted(duplicated_params)) |
| 151 | ending = "s" if len(duplicated_params) > 1 else "" |
| 152 | raise ValueError(f"Duplicated param name{ending} {names} at path {path}") |
| 153 | |
| 154 | if is_host: |
| 155 | # Align with `Host.matches()` behavior, which ignores port. |
| 156 | hostname = path[idx:].split(":")[0] |
| 157 | path_regex += re.escape(hostname) + "$" |
| 158 | else: |
| 159 | path_regex += re.escape(path[idx:]) + "$" |
| 160 | |
| 161 | path_format += path[idx:] |
| 162 | |
| 163 | return re.compile(path_regex), path_format, param_convertors |
| 164 | |
| 165 | |
| 166 | class BaseRoute: |