Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/routing.py: 43%

819 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:03 +0000

1"""When it comes to combining multiple controller or view functions 

2(however you want to call them) you need a dispatcher. A simple way 

3would be applying regular expression tests on the ``PATH_INFO`` and 

4calling registered callback functions that return the value then. 

5 

6This module implements a much more powerful system than simple regular 

7expression matching because it can also convert values in the URLs and 

8build URLs. 

9 

10Here a simple example that creates a URL map for an application with 

11two subdomains (www and kb) and some URL rules: 

12 

13.. code-block:: python 

14 

15 m = Map([ 

16 # Static URLs 

17 Rule('/', endpoint='static/index'), 

18 Rule('/about', endpoint='static/about'), 

19 Rule('/help', endpoint='static/help'), 

20 # Knowledge Base 

21 Subdomain('kb', [ 

22 Rule('/', endpoint='kb/index'), 

23 Rule('/browse/', endpoint='kb/browse'), 

24 Rule('/browse/<int:id>/', endpoint='kb/browse'), 

25 Rule('/browse/<int:id>/<int:page>', endpoint='kb/browse') 

26 ]) 

27 ], default_subdomain='www') 

28 

29If the application doesn't use subdomains it's perfectly fine to not set 

30the default subdomain and not use the `Subdomain` rule factory. The 

31endpoint in the rules can be anything, for example import paths or 

32unique identifiers. The WSGI application can use those endpoints to get the 

33handler for that URL. It doesn't have to be a string at all but it's 

34recommended. 

35 

36Now it's possible to create a URL adapter for one of the subdomains and 

37build URLs: 

38 

39.. code-block:: python 

40 

41 c = m.bind('example.com') 

42 

43 c.build("kb/browse", dict(id=42)) 

44 'http://kb.example.com/browse/42/' 

45 

46 c.build("kb/browse", dict()) 

47 'http://kb.example.com/browse/' 

48 

49 c.build("kb/browse", dict(id=42, page=3)) 

50 'http://kb.example.com/browse/42/3' 

51 

52 c.build("static/about") 

53 '/about' 

54 

55 c.build("static/index", force_external=True) 

56 'http://www.example.com/' 

57 

58 c = m.bind('example.com', subdomain='kb') 

59 

60 c.build("static/about") 

61 'http://www.example.com/about' 

62 

63The first argument to bind is the server name *without* the subdomain. 

64Per default it will assume that the script is mounted on the root, but 

65often that's not the case so you can provide the real mount point as 

66second argument: 

67 

68.. code-block:: python 

69 

70 c = m.bind('example.com', '/applications/example') 

71 

72The third argument can be the subdomain, if not given the default 

73subdomain is used. For more details about binding have a look at the 

74documentation of the `MapAdapter`. 

75 

76And here is how you can match URLs: 

77 

78.. code-block:: python 

79 

80 c = m.bind('example.com') 

81 

82 c.match("/") 

83 ('static/index', {}) 

84 

85 c.match("/about") 

86 ('static/about', {}) 

87 

88 c = m.bind('example.com', '/', 'kb') 

89 

90 c.match("/") 

91 ('kb/index', {}) 

92 

93 c.match("/browse/42/23") 

94 ('kb/browse', {'id': 42, 'page': 23}) 

95 

96If matching fails you get a ``NotFound`` exception, if the rule thinks 

97it's a good idea to redirect (for example because the URL was defined 

98to have a slash at the end but the request was missing that slash) it 

99will raise a ``RequestRedirect`` exception. Both are subclasses of 

100``HTTPException`` so you can use those errors as responses in the 

101application. 

102 

103If matching succeeded but the URL rule was incompatible to the given 

104method (for example there were only rules for ``GET`` and ``HEAD`` but 

105routing tried to match a ``POST`` request) a ``MethodNotAllowed`` 

106exception is raised. 

107""" 

108import ast 

109import difflib 

110import posixpath 

111import re 

112import typing 

113import typing as t 

114import uuid 

115import warnings 

116from pprint import pformat 

117from string import Template 

118from threading import Lock 

119from types import CodeType 

120 

121from ._internal import _encode_idna 

122from ._internal import _get_environ 

123from ._internal import _to_bytes 

124from ._internal import _to_str 

125from ._internal import _wsgi_decoding_dance 

126from .datastructures import ImmutableDict 

127from .datastructures import MultiDict 

128from .exceptions import BadHost 

129from .exceptions import BadRequest 

130from .exceptions import HTTPException 

131from .exceptions import MethodNotAllowed 

132from .exceptions import NotFound 

133from .urls import _fast_url_quote 

134from .urls import url_encode 

135from .urls import url_join 

136from .urls import url_quote 

137from .urls import url_unquote 

138from .utils import cached_property 

139from .utils import redirect 

140from .wsgi import get_host 

141 

142if t.TYPE_CHECKING: 

143 import typing_extensions as te 

144 from _typeshed.wsgi import WSGIApplication 

145 from _typeshed.wsgi import WSGIEnvironment 

146 from .wrappers.request import Request 

147 from .wrappers.response import Response 

148 

149_rule_re = re.compile( 

150 r""" 

151 (?P<static>[^<]*) # static rule data 

152 < 

153 (?: 

154 (?P<converter>[a-zA-Z_][a-zA-Z0-9_]*) # converter name 

155 (?:\((?P<args>.*?)\))? # converter arguments 

156 \: # variable delimiter 

157 )? 

158 (?P<variable>[a-zA-Z_][a-zA-Z0-9_]*) # variable name 

159 > 

160 """, 

161 re.VERBOSE, 

162) 

163_simple_rule_re = re.compile(r"<([^>]+)>") 

164_converter_args_re = re.compile( 

165 r""" 

166 ((?P<name>\w+)\s*=\s*)? 

167 (?P<value> 

168 True|False| 

169 \d+.\d+| 

170 \d+.| 

171 \d+| 

172 [\w\d_.]+| 

173 [urUR]?(?P<stringval>"[^"]*?"|'[^']*') 

174 )\s*, 

175 """, 

176 re.VERBOSE, 

177) 

178 

179 

180_PYTHON_CONSTANTS = {"None": None, "True": True, "False": False} 

181 

182 

183def _pythonize(value: str) -> t.Union[None, bool, int, float, str]: 

184 if value in _PYTHON_CONSTANTS: 

185 return _PYTHON_CONSTANTS[value] 

186 for convert in int, float: 

187 try: 

188 return convert(value) # type: ignore 

189 except ValueError: 

190 pass 

191 if value[:1] == value[-1:] and value[0] in "\"'": 

192 value = value[1:-1] 

193 return str(value) 

194 

195 

196def parse_converter_args(argstr: str) -> t.Tuple[t.Tuple, t.Dict[str, t.Any]]: 

197 argstr += "," 

198 args = [] 

199 kwargs = {} 

200 

201 for item in _converter_args_re.finditer(argstr): 

202 value = item.group("stringval") 

203 if value is None: 

204 value = item.group("value") 

205 value = _pythonize(value) 

206 if not item.group("name"): 

207 args.append(value) 

208 else: 

209 name = item.group("name") 

210 kwargs[name] = value 

211 

212 return tuple(args), kwargs 

213 

214 

215def parse_rule(rule: str) -> t.Iterator[t.Tuple[t.Optional[str], t.Optional[str], str]]: 

216 """Parse a rule and return it as generator. Each iteration yields tuples 

217 in the form ``(converter, arguments, variable)``. If the converter is 

218 `None` it's a static url part, otherwise it's a dynamic one. 

219 

220 :internal: 

221 """ 

222 pos = 0 

223 end = len(rule) 

224 do_match = _rule_re.match 

225 used_names = set() 

226 while pos < end: 

227 m = do_match(rule, pos) 

228 if m is None: 

229 break 

230 data = m.groupdict() 

231 if data["static"]: 

232 yield None, None, data["static"] 

233 variable = data["variable"] 

234 converter = data["converter"] or "default" 

235 if variable in used_names: 

236 raise ValueError(f"variable name {variable!r} used twice.") 

237 used_names.add(variable) 

238 yield converter, data["args"] or None, variable 

239 pos = m.end() 

240 if pos < end: 

241 remaining = rule[pos:] 

242 if ">" in remaining or "<" in remaining: 

243 raise ValueError(f"malformed url rule: {rule!r}") 

244 yield None, None, remaining 

245 

246 

247class RoutingException(Exception): 

248 """Special exceptions that require the application to redirect, notifying 

249 about missing urls, etc. 

250 

251 :internal: 

252 """ 

253 

254 

255class RequestRedirect(HTTPException, RoutingException): 

256 """Raise if the map requests a redirect. This is for example the case if 

257 `strict_slashes` are activated and an url that requires a trailing slash. 

258 

259 The attribute `new_url` contains the absolute destination url. 

260 """ 

261 

262 code = 308 

263 

264 def __init__(self, new_url: str) -> None: 

265 super().__init__(new_url) 

266 self.new_url = new_url 

267 

268 def get_response( 

269 self, 

270 environ: t.Optional[t.Union["WSGIEnvironment", "Request"]] = None, 

271 scope: t.Optional[dict] = None, 

272 ) -> "Response": 

273 return redirect(self.new_url, self.code) 

274 

275 

276class RequestPath(RoutingException): 

277 """Internal exception.""" 

278 

279 __slots__ = ("path_info",) 

280 

281 def __init__(self, path_info: str) -> None: 

282 super().__init__() 

283 self.path_info = path_info 

284 

285 

286class RequestAliasRedirect(RoutingException): # noqa: B903 

287 """This rule is an alias and wants to redirect to the canonical URL.""" 

288 

289 def __init__(self, matched_values: t.Mapping[str, t.Any]) -> None: 

290 super().__init__() 

291 self.matched_values = matched_values 

292 

293 

294class BuildError(RoutingException, LookupError): 

295 """Raised if the build system cannot find a URL for an endpoint with the 

296 values provided. 

297 """ 

298 

299 def __init__( 

300 self, 

301 endpoint: str, 

302 values: t.Mapping[str, t.Any], 

303 method: t.Optional[str], 

304 adapter: t.Optional["MapAdapter"] = None, 

305 ) -> None: 

306 super().__init__(endpoint, values, method) 

307 self.endpoint = endpoint 

308 self.values = values 

309 self.method = method 

310 self.adapter = adapter 

311 

312 @cached_property 

313 def suggested(self) -> t.Optional["Rule"]: 

314 return self.closest_rule(self.adapter) 

315 

316 def closest_rule(self, adapter: t.Optional["MapAdapter"]) -> t.Optional["Rule"]: 

317 def _score_rule(rule: "Rule") -> float: 

318 return sum( 

319 [ 

320 0.98 

321 * difflib.SequenceMatcher( 

322 None, rule.endpoint, self.endpoint 

323 ).ratio(), 

324 0.01 * bool(set(self.values or ()).issubset(rule.arguments)), 

325 0.01 * bool(rule.methods and self.method in rule.methods), 

326 ] 

327 ) 

328 

329 if adapter and adapter.map._rules: 

330 return max(adapter.map._rules, key=_score_rule) 

331 

332 return None 

333 

334 def __str__(self) -> str: 

335 message = [f"Could not build url for endpoint {self.endpoint!r}"] 

336 if self.method: 

337 message.append(f" ({self.method!r})") 

338 if self.values: 

339 message.append(f" with values {sorted(self.values)!r}") 

340 message.append(".") 

341 if self.suggested: 

342 if self.endpoint == self.suggested.endpoint: 

343 if ( 

344 self.method 

345 and self.suggested.methods is not None 

346 and self.method not in self.suggested.methods 

347 ): 

348 message.append( 

349 " Did you mean to use methods" 

350 f" {sorted(self.suggested.methods)!r}?" 

351 ) 

352 missing_values = self.suggested.arguments.union( 

353 set(self.suggested.defaults or ()) 

354 ) - set(self.values.keys()) 

355 if missing_values: 

356 message.append( 

357 f" Did you forget to specify values {sorted(missing_values)!r}?" 

358 ) 

359 else: 

360 message.append(f" Did you mean {self.suggested.endpoint!r} instead?") 

361 return "".join(message) 

362 

363 

364class WebsocketMismatch(BadRequest): 

365 """The only matched rule is either a WebSocket and the request is 

366 HTTP, or the rule is HTTP and the request is a WebSocket. 

367 """ 

368 

369 

370class ValidationError(ValueError): 

371 """Validation error. If a rule converter raises this exception the rule 

372 does not match the current URL and the next URL is tried. 

373 """ 

374 

375 

376class RuleFactory: 

377 """As soon as you have more complex URL setups it's a good idea to use rule 

378 factories to avoid repetitive tasks. Some of them are builtin, others can 

379 be added by subclassing `RuleFactory` and overriding `get_rules`. 

380 """ 

381 

382 def get_rules(self, map: "Map") -> t.Iterable["Rule"]: 

383 """Subclasses of `RuleFactory` have to override this method and return 

384 an iterable of rules.""" 

385 raise NotImplementedError() 

386 

387 

388class Subdomain(RuleFactory): 

389 """All URLs provided by this factory have the subdomain set to a 

390 specific domain. For example if you want to use the subdomain for 

391 the current language this can be a good setup:: 

392 

393 url_map = Map([ 

394 Rule('/', endpoint='#select_language'), 

395 Subdomain('<string(length=2):lang_code>', [ 

396 Rule('/', endpoint='index'), 

397 Rule('/about', endpoint='about'), 

398 Rule('/help', endpoint='help') 

399 ]) 

400 ]) 

401 

402 All the rules except for the ``'#select_language'`` endpoint will now 

403 listen on a two letter long subdomain that holds the language code 

404 for the current request. 

405 """ 

406 

407 def __init__(self, subdomain: str, rules: t.Iterable[RuleFactory]) -> None: 

408 self.subdomain = subdomain 

409 self.rules = rules 

410 

411 def get_rules(self, map: "Map") -> t.Iterator["Rule"]: 

412 for rulefactory in self.rules: 

413 for rule in rulefactory.get_rules(map): 

414 rule = rule.empty() 

415 rule.subdomain = self.subdomain 

416 yield rule 

417 

418 

419class Submount(RuleFactory): 

420 """Like `Subdomain` but prefixes the URL rule with a given string:: 

421 

422 url_map = Map([ 

423 Rule('/', endpoint='index'), 

424 Submount('/blog', [ 

425 Rule('/', endpoint='blog/index'), 

426 Rule('/entry/<entry_slug>', endpoint='blog/show') 

427 ]) 

428 ]) 

429 

430 Now the rule ``'blog/show'`` matches ``/blog/entry/<entry_slug>``. 

431 """ 

432 

433 def __init__(self, path: str, rules: t.Iterable[RuleFactory]) -> None: 

434 self.path = path.rstrip("/") 

435 self.rules = rules 

436 

437 def get_rules(self, map: "Map") -> t.Iterator["Rule"]: 

438 for rulefactory in self.rules: 

439 for rule in rulefactory.get_rules(map): 

440 rule = rule.empty() 

441 rule.rule = self.path + rule.rule 

442 yield rule 

443 

444 

445class EndpointPrefix(RuleFactory): 

446 """Prefixes all endpoints (which must be strings for this factory) with 

447 another string. This can be useful for sub applications:: 

448 

449 url_map = Map([ 

450 Rule('/', endpoint='index'), 

451 EndpointPrefix('blog/', [Submount('/blog', [ 

452 Rule('/', endpoint='index'), 

453 Rule('/entry/<entry_slug>', endpoint='show') 

454 ])]) 

455 ]) 

456 """ 

457 

458 def __init__(self, prefix: str, rules: t.Iterable[RuleFactory]) -> None: 

459 self.prefix = prefix 

460 self.rules = rules 

461 

462 def get_rules(self, map: "Map") -> t.Iterator["Rule"]: 

463 for rulefactory in self.rules: 

464 for rule in rulefactory.get_rules(map): 

465 rule = rule.empty() 

466 rule.endpoint = self.prefix + rule.endpoint 

467 yield rule 

468 

469 

470class RuleTemplate: 

471 """Returns copies of the rules wrapped and expands string templates in 

472 the endpoint, rule, defaults or subdomain sections. 

473 

474 Here a small example for such a rule template:: 

475 

476 from werkzeug.routing import Map, Rule, RuleTemplate 

477 

478 resource = RuleTemplate([ 

479 Rule('/$name/', endpoint='$name.list'), 

480 Rule('/$name/<int:id>', endpoint='$name.show') 

481 ]) 

482 

483 url_map = Map([resource(name='user'), resource(name='page')]) 

484 

485 When a rule template is called the keyword arguments are used to 

486 replace the placeholders in all the string parameters. 

487 """ 

488 

489 def __init__(self, rules: t.Iterable["Rule"]) -> None: 

490 self.rules = list(rules) 

491 

492 def __call__(self, *args: t.Any, **kwargs: t.Any) -> "RuleTemplateFactory": 

493 return RuleTemplateFactory(self.rules, dict(*args, **kwargs)) 

494 

495 

496class RuleTemplateFactory(RuleFactory): 

497 """A factory that fills in template variables into rules. Used by 

498 `RuleTemplate` internally. 

499 

500 :internal: 

501 """ 

502 

503 def __init__( 

504 self, rules: t.Iterable[RuleFactory], context: t.Dict[str, t.Any] 

505 ) -> None: 

506 self.rules = rules 

507 self.context = context 

508 

509 def get_rules(self, map: "Map") -> t.Iterator["Rule"]: 

510 for rulefactory in self.rules: 

511 for rule in rulefactory.get_rules(map): 

512 new_defaults = subdomain = None 

513 if rule.defaults: 

514 new_defaults = {} 

515 for key, value in rule.defaults.items(): 

516 if isinstance(value, str): 

517 value = Template(value).substitute(self.context) 

518 new_defaults[key] = value 

519 if rule.subdomain is not None: 

520 subdomain = Template(rule.subdomain).substitute(self.context) 

521 new_endpoint = rule.endpoint 

522 if isinstance(new_endpoint, str): 

523 new_endpoint = Template(new_endpoint).substitute(self.context) 

524 yield Rule( 

525 Template(rule.rule).substitute(self.context), 

526 new_defaults, 

527 subdomain, 

528 rule.methods, 

529 rule.build_only, 

530 new_endpoint, 

531 rule.strict_slashes, 

532 ) 

533 

534 

535def _prefix_names(src: str) -> ast.stmt: 

536 """ast parse and prefix names with `.` to avoid collision with user vars""" 

537 tree = ast.parse(src).body[0] 

538 if isinstance(tree, ast.Expr): 

539 tree = tree.value # type: ignore 

540 for node in ast.walk(tree): 

541 if isinstance(node, ast.Name): 

542 node.id = f".{node.id}" 

543 return tree 

544 

545 

546_CALL_CONVERTER_CODE_FMT = "self._converters[{elem!r}].to_url()" 

547_IF_KWARGS_URL_ENCODE_CODE = """\ 

548if kwargs: 

549 params = self._encode_query_vars(kwargs) 

550 q = "?" if params else "" 

551else: 

552 q = params = "" 

553""" 

554_IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE) 

555_URL_ENCODE_AST_NAMES = (_prefix_names("q"), _prefix_names("params")) 

556 

557 

558class Rule(RuleFactory): 

559 """A Rule represents one URL pattern. There are some options for `Rule` 

560 that change the way it behaves and are passed to the `Rule` constructor. 

561 Note that besides the rule-string all arguments *must* be keyword arguments 

562 in order to not break the application on Werkzeug upgrades. 

563 

564 `string` 

565 Rule strings basically are just normal URL paths with placeholders in 

566 the format ``<converter(arguments):name>`` where the converter and the 

567 arguments are optional. If no converter is defined the `default` 

568 converter is used which means `string` in the normal configuration. 

569 

570 URL rules that end with a slash are branch URLs, others are leaves. 

571 If you have `strict_slashes` enabled (which is the default), all 

572 branch URLs that are matched without a trailing slash will trigger a 

573 redirect to the same URL with the missing slash appended. 

574 

575 The converters are defined on the `Map`. 

576 

577 `endpoint` 

578 The endpoint for this rule. This can be anything. A reference to a 

579 function, a string, a number etc. The preferred way is using a string 

580 because the endpoint is used for URL generation. 

581 

582 `defaults` 

583 An optional dict with defaults for other rules with the same endpoint. 

584 This is a bit tricky but useful if you want to have unique URLs:: 

585 

586 url_map = Map([ 

587 Rule('/all/', defaults={'page': 1}, endpoint='all_entries'), 

588 Rule('/all/page/<int:page>', endpoint='all_entries') 

589 ]) 

590 

591 If a user now visits ``http://example.com/all/page/1`` they will be 

592 redirected to ``http://example.com/all/``. If `redirect_defaults` is 

593 disabled on the `Map` instance this will only affect the URL 

594 generation. 

595 

596 `subdomain` 

597 The subdomain rule string for this rule. If not specified the rule 

598 only matches for the `default_subdomain` of the map. If the map is 

599 not bound to a subdomain this feature is disabled. 

600 

601 Can be useful if you want to have user profiles on different subdomains 

602 and all subdomains are forwarded to your application:: 

603 

604 url_map = Map([ 

605 Rule('/', subdomain='<username>', endpoint='user/homepage'), 

606 Rule('/stats', subdomain='<username>', endpoint='user/stats') 

607 ]) 

608 

609 `methods` 

610 A sequence of http methods this rule applies to. If not specified, all 

611 methods are allowed. For example this can be useful if you want different 

612 endpoints for `POST` and `GET`. If methods are defined and the path 

613 matches but the method matched against is not in this list or in the 

614 list of another rule for that path the error raised is of the type 

615 `MethodNotAllowed` rather than `NotFound`. If `GET` is present in the 

616 list of methods and `HEAD` is not, `HEAD` is added automatically. 

617 

618 `strict_slashes` 

619 Override the `Map` setting for `strict_slashes` only for this rule. If 

620 not specified the `Map` setting is used. 

621 

622 `merge_slashes` 

623 Override :attr:`Map.merge_slashes` for this rule. 

624 

625 `build_only` 

626 Set this to True and the rule will never match but will create a URL 

627 that can be build. This is useful if you have resources on a subdomain 

628 or folder that are not handled by the WSGI application (like static data) 

629 

630 `redirect_to` 

631 If given this must be either a string or callable. In case of a 

632 callable it's called with the url adapter that triggered the match and 

633 the values of the URL as keyword arguments and has to return the target 

634 for the redirect, otherwise it has to be a string with placeholders in 

635 rule syntax:: 

636 

637 def foo_with_slug(adapter, id): 

638 # ask the database for the slug for the old id. this of 

639 # course has nothing to do with werkzeug. 

640 return f'foo/{Foo.get_slug_for_id(id)}' 

641 

642 url_map = Map([ 

643 Rule('/foo/<slug>', endpoint='foo'), 

644 Rule('/some/old/url/<slug>', redirect_to='foo/<slug>'), 

645 Rule('/other/old/url/<int:id>', redirect_to=foo_with_slug) 

646 ]) 

647 

648 When the rule is matched the routing system will raise a 

649 `RequestRedirect` exception with the target for the redirect. 

650 

651 Keep in mind that the URL will be joined against the URL root of the 

652 script so don't use a leading slash on the target URL unless you 

653 really mean root of that domain. 

654 

655 `alias` 

656 If enabled this rule serves as an alias for another rule with the same 

657 endpoint and arguments. 

658 

659 `host` 

660 If provided and the URL map has host matching enabled this can be 

661 used to provide a match rule for the whole host. This also means 

662 that the subdomain feature is disabled. 

663 

664 `websocket` 

665 If ``True``, this rule is only matches for WebSocket (``ws://``, 

666 ``wss://``) requests. By default, rules will only match for HTTP 

667 requests. 

668 

669 .. versionchanged:: 2.1 

670 Percent-encoded newlines (``%0a``), which are decoded by WSGI 

671 servers, are considered when routing instead of terminating the 

672 match early. 

673 

674 .. versionadded:: 1.0 

675 Added ``websocket``. 

676 

677 .. versionadded:: 1.0 

678 Added ``merge_slashes``. 

679 

680 .. versionadded:: 0.7 

681 Added ``alias`` and ``host``. 

682 

683 .. versionchanged:: 0.6.1 

684 ``HEAD`` is added to ``methods`` if ``GET`` is present. 

685 """ 

686 

687 def __init__( 

688 self, 

689 string: str, 

690 defaults: t.Optional[t.Mapping[str, t.Any]] = None, 

691 subdomain: t.Optional[str] = None, 

692 methods: t.Optional[t.Iterable[str]] = None, 

693 build_only: bool = False, 

694 endpoint: t.Optional[str] = None, 

695 strict_slashes: t.Optional[bool] = None, 

696 merge_slashes: t.Optional[bool] = None, 

697 redirect_to: t.Optional[t.Union[str, t.Callable[..., str]]] = None, 

698 alias: bool = False, 

699 host: t.Optional[str] = None, 

700 websocket: bool = False, 

701 ) -> None: 

702 if not string.startswith("/"): 

703 raise ValueError("urls must start with a leading slash") 

704 self.rule = string 

705 self.is_leaf = not string.endswith("/") 

706 

707 self.map: "Map" = None # type: ignore 

708 self.strict_slashes = strict_slashes 

709 self.merge_slashes = merge_slashes 

710 self.subdomain = subdomain 

711 self.host = host 

712 self.defaults = defaults 

713 self.build_only = build_only 

714 self.alias = alias 

715 self.websocket = websocket 

716 

717 if methods is not None: 

718 if isinstance(methods, str): 

719 raise TypeError("'methods' should be a list of strings.") 

720 

721 methods = {x.upper() for x in methods} 

722 

723 if "HEAD" not in methods and "GET" in methods: 

724 methods.add("HEAD") 

725 

726 if websocket and methods - {"GET", "HEAD", "OPTIONS"}: 

727 raise ValueError( 

728 "WebSocket rules can only use 'GET', 'HEAD', and 'OPTIONS' methods." 

729 ) 

730 

731 self.methods = methods 

732 self.endpoint: str = endpoint # type: ignore 

733 self.redirect_to = redirect_to 

734 

735 if defaults: 

736 self.arguments = set(map(str, defaults)) 

737 else: 

738 self.arguments = set() 

739 

740 self._trace: t.List[t.Tuple[bool, str]] = [] 

741 

742 def empty(self) -> "Rule": 

743 """ 

744 Return an unbound copy of this rule. 

745 

746 This can be useful if want to reuse an already bound URL for another 

747 map. See ``get_empty_kwargs`` to override what keyword arguments are 

748 provided to the new copy. 

749 """ 

750 return type(self)(self.rule, **self.get_empty_kwargs()) 

751 

752 def get_empty_kwargs(self) -> t.Mapping[str, t.Any]: 

753 """ 

754 Provides kwargs for instantiating empty copy with empty() 

755 

756 Use this method to provide custom keyword arguments to the subclass of 

757 ``Rule`` when calling ``some_rule.empty()``. Helpful when the subclass 

758 has custom keyword arguments that are needed at instantiation. 

759 

760 Must return a ``dict`` that will be provided as kwargs to the new 

761 instance of ``Rule``, following the initial ``self.rule`` value which 

762 is always provided as the first, required positional argument. 

763 """ 

764 defaults = None 

765 if self.defaults: 

766 defaults = dict(self.defaults) 

767 return dict( 

768 defaults=defaults, 

769 subdomain=self.subdomain, 

770 methods=self.methods, 

771 build_only=self.build_only, 

772 endpoint=self.endpoint, 

773 strict_slashes=self.strict_slashes, 

774 redirect_to=self.redirect_to, 

775 alias=self.alias, 

776 host=self.host, 

777 ) 

778 

779 def get_rules(self, map: "Map") -> t.Iterator["Rule"]: 

780 yield self 

781 

782 def refresh(self) -> None: 

783 """Rebinds and refreshes the URL. Call this if you modified the 

784 rule in place. 

785 

786 :internal: 

787 """ 

788 self.bind(self.map, rebind=True) 

789 

790 def bind(self, map: "Map", rebind: bool = False) -> None: 

791 """Bind the url to a map and create a regular expression based on 

792 the information from the rule itself and the defaults from the map. 

793 

794 :internal: 

795 """ 

796 if self.map is not None and not rebind: 

797 raise RuntimeError(f"url rule {self!r} already bound to map {self.map!r}") 

798 self.map = map 

799 if self.strict_slashes is None: 

800 self.strict_slashes = map.strict_slashes 

801 if self.merge_slashes is None: 

802 self.merge_slashes = map.merge_slashes 

803 if self.subdomain is None: 

804 self.subdomain = map.default_subdomain 

805 self.compile() 

806 

807 def get_converter( 

808 self, 

809 variable_name: str, 

810 converter_name: str, 

811 args: t.Tuple, 

812 kwargs: t.Mapping[str, t.Any], 

813 ) -> "BaseConverter": 

814 """Looks up the converter for the given parameter. 

815 

816 .. versionadded:: 0.9 

817 """ 

818 if converter_name not in self.map.converters: 

819 raise LookupError(f"the converter {converter_name!r} does not exist") 

820 return self.map.converters[converter_name](self.map, *args, **kwargs) 

821 

822 def _encode_query_vars(self, query_vars: t.Mapping[str, t.Any]) -> str: 

823 return url_encode( 

824 query_vars, 

825 charset=self.map.charset, 

826 sort=self.map.sort_parameters, 

827 key=self.map.sort_key, 

828 ) 

829 

830 def compile(self) -> None: 

831 """Compiles the regular expression and stores it.""" 

832 assert self.map is not None, "rule not bound" 

833 

834 if self.map.host_matching: 

835 domain_rule = self.host or "" 

836 else: 

837 domain_rule = self.subdomain or "" 

838 

839 self._trace = [] 

840 self._converters: t.Dict[str, "BaseConverter"] = {} 

841 self._static_weights: t.List[t.Tuple[int, int]] = [] 

842 self._argument_weights: t.List[int] = [] 

843 regex_parts = [] 

844 

845 def _build_regex(rule: str) -> None: 

846 index = 0 

847 for converter, arguments, variable in parse_rule(rule): 

848 if converter is None: 

849 for match in re.finditer(r"/+|[^/]+", variable): 

850 part = match.group(0) 

851 if part.startswith("/"): 

852 if self.merge_slashes: 

853 regex_parts.append(r"/+?") 

854 self._trace.append((False, "/")) 

855 else: 

856 regex_parts.append(part) 

857 self._trace.append((False, part)) 

858 continue 

859 self._trace.append((False, part)) 

860 regex_parts.append(re.escape(part)) 

861 if part: 

862 self._static_weights.append((index, -len(part))) 

863 else: 

864 if arguments: 

865 c_args, c_kwargs = parse_converter_args(arguments) 

866 else: 

867 c_args = () 

868 c_kwargs = {} 

869 convobj = self.get_converter(variable, converter, c_args, c_kwargs) 

870 regex_parts.append(f"(?P<{variable}>{convobj.regex})") 

871 self._converters[variable] = convobj 

872 self._trace.append((True, variable)) 

873 self._argument_weights.append(convobj.weight) 

874 self.arguments.add(str(variable)) 

875 index = index + 1 

876 

877 _build_regex(domain_rule) 

878 regex_parts.append("\\|") 

879 self._trace.append((False, "|")) 

880 _build_regex(self.rule if self.is_leaf else self.rule.rstrip("/")) 

881 if not self.is_leaf: 

882 self._trace.append((False, "/")) 

883 

884 self._build: t.Callable[..., t.Tuple[str, str]] 

885 self._build = self._compile_builder(False).__get__(self, None) 

886 self._build_unknown: t.Callable[..., t.Tuple[str, str]] 

887 self._build_unknown = self._compile_builder(True).__get__(self, None) 

888 

889 if self.build_only: 

890 return 

891 

892 if not (self.is_leaf and self.strict_slashes): 

893 reps = "*" if self.merge_slashes else "?" 

894 tail = f"(?<!/)(?P<__suffix__>/{reps})" 

895 else: 

896 tail = "" 

897 

898 # Use \Z instead of $ to avoid matching before a %0a decoded to 

899 # a \n by WSGI. 

900 regex = rf"^{''.join(regex_parts)}{tail}$\Z" 

901 self._regex = re.compile(regex) 

902 

903 def match( 

904 self, path: str, method: t.Optional[str] = None 

905 ) -> t.Optional[t.MutableMapping[str, t.Any]]: 

906 """Check if the rule matches a given path. Path is a string in the 

907 form ``"subdomain|/path"`` and is assembled by the map. If 

908 the map is doing host matching the subdomain part will be the host 

909 instead. 

910 

911 If the rule matches a dict with the converted values is returned, 

912 otherwise the return value is `None`. 

913 

914 :internal: 

915 """ 

916 if not self.build_only: 

917 require_redirect = False 

918 

919 m = self._regex.search(path) 

920 if m is not None: 

921 groups = m.groupdict() 

922 # we have a folder like part of the url without a trailing 

923 # slash and strict slashes enabled. raise an exception that 

924 # tells the map to redirect to the same url but with a 

925 # trailing slash 

926 if ( 

927 self.strict_slashes 

928 and not self.is_leaf 

929 and not groups.pop("__suffix__") 

930 and ( 

931 method is None or self.methods is None or method in self.methods 

932 ) 

933 ): 

934 path += "/" 

935 require_redirect = True 

936 # if we are not in strict slashes mode we have to remove 

937 # a __suffix__ 

938 elif not self.strict_slashes: 

939 del groups["__suffix__"] 

940 

941 result = {} 

942 for name, value in groups.items(): 

943 try: 

944 value = self._converters[name].to_python(value) 

945 except ValidationError: 

946 return None 

947 result[str(name)] = value 

948 if self.defaults: 

949 result.update(self.defaults) 

950 

951 if self.merge_slashes: 

952 new_path = "|".join(self.build(result, False)) # type: ignore 

953 if path.endswith("/") and not new_path.endswith("/"): 

954 new_path += "/" 

955 if new_path.count("/") < path.count("/"): 

956 # The URL will be encoded when MapAdapter.match 

957 # handles the RequestPath raised below. Decode 

958 # the URL here to avoid a double encoding. 

959 path = url_unquote(new_path) 

960 require_redirect = True 

961 

962 if require_redirect: 

963 path = path.split("|", 1)[1] 

964 raise RequestPath(path) 

965 

966 if self.alias and self.map.redirect_defaults: 

967 raise RequestAliasRedirect(result) 

968 

969 return result 

970 

971 return None 

972 

973 @staticmethod 

974 def _get_func_code(code: CodeType, name: str) -> t.Callable[..., t.Tuple[str, str]]: 

975 globs: t.Dict[str, t.Any] = {} 

976 locs: t.Dict[str, t.Any] = {} 

977 exec(code, globs, locs) 

978 return locs[name] # type: ignore 

979 

980 def _compile_builder( 

981 self, append_unknown: bool = True 

982 ) -> t.Callable[..., t.Tuple[str, str]]: 

983 defaults = self.defaults or {} 

984 dom_ops: t.List[t.Tuple[bool, str]] = [] 

985 url_ops: t.List[t.Tuple[bool, str]] = [] 

986 

987 opl = dom_ops 

988 for is_dynamic, data in self._trace: 

989 if data == "|" and opl is dom_ops: 

990 opl = url_ops 

991 continue 

992 # this seems like a silly case to ever come up but: 

993 # if a default is given for a value that appears in the rule, 

994 # resolve it to a constant ahead of time 

995 if is_dynamic and data in defaults: 

996 data = self._converters[data].to_url(defaults[data]) 

997 opl.append((False, data)) 

998 elif not is_dynamic: 

999 opl.append( 

1000 (False, url_quote(_to_bytes(data, self.map.charset), safe="/:|+")) 

1001 ) 

1002 else: 

1003 opl.append((True, data)) 

1004 

1005 def _convert(elem: str) -> ast.stmt: 

1006 ret = _prefix_names(_CALL_CONVERTER_CODE_FMT.format(elem=elem)) 

1007 ret.args = [ast.Name(str(elem), ast.Load())] # type: ignore # str for py2 

1008 return ret 

1009 

1010 def _parts(ops: t.List[t.Tuple[bool, str]]) -> t.List[ast.AST]: 

1011 parts = [ 

1012 _convert(elem) if is_dynamic else ast.Str(s=elem) 

1013 for is_dynamic, elem in ops 

1014 ] 

1015 parts = parts or [ast.Str("")] 

1016 # constant fold 

1017 ret = [parts[0]] 

1018 for p in parts[1:]: 

1019 if isinstance(p, ast.Str) and isinstance(ret[-1], ast.Str): 

1020 ret[-1] = ast.Str(ret[-1].s + p.s) 

1021 else: 

1022 ret.append(p) 

1023 return ret 

1024 

1025 dom_parts = _parts(dom_ops) 

1026 url_parts = _parts(url_ops) 

1027 if not append_unknown: 

1028 body = [] 

1029 else: 

1030 body = [_IF_KWARGS_URL_ENCODE_AST] 

1031 url_parts.extend(_URL_ENCODE_AST_NAMES) 

1032 

1033 def _join(parts: t.List[ast.AST]) -> ast.AST: 

1034 if len(parts) == 1: # shortcut 

1035 return parts[0] 

1036 return ast.JoinedStr(parts) 

1037 

1038 body.append( 

1039 ast.Return(ast.Tuple([_join(dom_parts), _join(url_parts)], ast.Load())) 

1040 ) 

1041 

1042 pargs = [ 

1043 elem 

1044 for is_dynamic, elem in dom_ops + url_ops 

1045 if is_dynamic and elem not in defaults 

1046 ] 

1047 kargs = [str(k) for k in defaults] 

1048 

1049 func_ast: ast.FunctionDef = _prefix_names("def _(): pass") # type: ignore 

1050 func_ast.name = f"<builder:{self.rule!r}>" 

1051 func_ast.args.args.append(ast.arg(".self", None)) 

1052 for arg in pargs + kargs: 

1053 func_ast.args.args.append(ast.arg(arg, None)) 

1054 func_ast.args.kwarg = ast.arg(".kwargs", None) 

1055 for _ in kargs: 

1056 func_ast.args.defaults.append(ast.Str("")) 

1057 func_ast.body = body 

1058 

1059 # use `ast.parse` instead of `ast.Module` for better portability 

1060 # Python 3.8 changes the signature of `ast.Module` 

1061 module = ast.parse("") 

1062 module.body = [func_ast] 

1063 

1064 # mark everything as on line 1, offset 0 

1065 # less error-prone than `ast.fix_missing_locations` 

1066 # bad line numbers cause an assert to fail in debug builds 

1067 for node in ast.walk(module): 

1068 if "lineno" in node._attributes: 

1069 node.lineno = 1 

1070 if "col_offset" in node._attributes: 

1071 node.col_offset = 0 

1072 

1073 code = compile(module, "<werkzeug routing>", "exec") 

1074 return self._get_func_code(code, func_ast.name) 

1075 

1076 def build( 

1077 self, values: t.Mapping[str, t.Any], append_unknown: bool = True 

1078 ) -> t.Optional[t.Tuple[str, str]]: 

1079 """Assembles the relative url for that rule and the subdomain. 

1080 If building doesn't work for some reasons `None` is returned. 

1081 

1082 :internal: 

1083 """ 

1084 try: 

1085 if append_unknown: 

1086 return self._build_unknown(**values) 

1087 else: 

1088 return self._build(**values) 

1089 except ValidationError: 

1090 return None 

1091 

1092 def provides_defaults_for(self, rule: "Rule") -> bool: 

1093 """Check if this rule has defaults for a given rule. 

1094 

1095 :internal: 

1096 """ 

1097 return bool( 

1098 not self.build_only 

1099 and self.defaults 

1100 and self.endpoint == rule.endpoint 

1101 and self != rule 

1102 and self.arguments == rule.arguments 

1103 ) 

1104 

1105 def suitable_for( 

1106 self, values: t.Mapping[str, t.Any], method: t.Optional[str] = None 

1107 ) -> bool: 

1108 """Check if the dict of values has enough data for url generation. 

1109 

1110 :internal: 

1111 """ 

1112 # if a method was given explicitly and that method is not supported 

1113 # by this rule, this rule is not suitable. 

1114 if ( 

1115 method is not None 

1116 and self.methods is not None 

1117 and method not in self.methods 

1118 ): 

1119 return False 

1120 

1121 defaults = self.defaults or () 

1122 

1123 # all arguments required must be either in the defaults dict or 

1124 # the value dictionary otherwise it's not suitable 

1125 for key in self.arguments: 

1126 if key not in defaults and key not in values: 

1127 return False 

1128 

1129 # in case defaults are given we ensure that either the value was 

1130 # skipped or the value is the same as the default value. 

1131 if defaults: 

1132 for key, value in defaults.items(): 

1133 if key in values and value != values[key]: 

1134 return False 

1135 

1136 return True 

1137 

1138 def match_compare_key( 

1139 self, 

1140 ) -> t.Tuple[bool, int, t.Iterable[t.Tuple[int, int]], int, t.Iterable[int]]: 

1141 """The match compare key for sorting. 

1142 

1143 Current implementation: 

1144 

1145 1. rules without any arguments come first for performance 

1146 reasons only as we expect them to match faster and some 

1147 common ones usually don't have any arguments (index pages etc.) 

1148 2. rules with more static parts come first so the second argument 

1149 is the negative length of the number of the static weights. 

1150 3. we order by static weights, which is a combination of index 

1151 and length 

1152 4. The more complex rules come first so the next argument is the 

1153 negative length of the number of argument weights. 

1154 5. lastly we order by the actual argument weights. 

1155 

1156 :internal: 

1157 """ 

1158 return ( 

1159 bool(self.arguments), 

1160 -len(self._static_weights), 

1161 self._static_weights, 

1162 -len(self._argument_weights), 

1163 self._argument_weights, 

1164 ) 

1165 

1166 def build_compare_key(self) -> t.Tuple[int, int, int]: 

1167 """The build compare key for sorting. 

1168 

1169 :internal: 

1170 """ 

1171 return (1 if self.alias else 0, -len(self.arguments), -len(self.defaults or ())) 

1172 

1173 def __eq__(self, other: object) -> bool: 

1174 return isinstance(other, type(self)) and self._trace == other._trace 

1175 

1176 __hash__ = None # type: ignore 

1177 

1178 def __str__(self) -> str: 

1179 return self.rule 

1180 

1181 def __repr__(self) -> str: 

1182 if self.map is None: 

1183 return f"<{type(self).__name__} (unbound)>" 

1184 parts = [] 

1185 for is_dynamic, data in self._trace: 

1186 if is_dynamic: 

1187 parts.append(f"<{data}>") 

1188 else: 

1189 parts.append(data) 

1190 parts = "".join(parts).lstrip("|") 

1191 methods = f" ({', '.join(self.methods)})" if self.methods is not None else "" 

1192 return f"<{type(self).__name__} {parts!r}{methods} -> {self.endpoint}>" 

1193 

1194 

1195class BaseConverter: 

1196 """Base class for all converters.""" 

1197 

1198 regex = "[^/]+" 

1199 weight = 100 

1200 

1201 def __init__(self, map: "Map", *args: t.Any, **kwargs: t.Any) -> None: 

1202 self.map = map 

1203 

1204 def to_python(self, value: str) -> t.Any: 

1205 return value 

1206 

1207 def to_url(self, value: t.Any) -> str: 

1208 if isinstance(value, (bytes, bytearray)): 

1209 return _fast_url_quote(value) 

1210 return _fast_url_quote(str(value).encode(self.map.charset)) 

1211 

1212 

1213class UnicodeConverter(BaseConverter): 

1214 """This converter is the default converter and accepts any string but 

1215 only one path segment. Thus the string can not include a slash. 

1216 

1217 This is the default validator. 

1218 

1219 Example:: 

1220 

1221 Rule('/pages/<page>'), 

1222 Rule('/<string(length=2):lang_code>') 

1223 

1224 :param map: the :class:`Map`. 

1225 :param minlength: the minimum length of the string. Must be greater 

1226 or equal 1. 

1227 :param maxlength: the maximum length of the string. 

1228 :param length: the exact length of the string. 

1229 """ 

1230 

1231 def __init__( 

1232 self, 

1233 map: "Map", 

1234 minlength: int = 1, 

1235 maxlength: t.Optional[int] = None, 

1236 length: t.Optional[int] = None, 

1237 ) -> None: 

1238 super().__init__(map) 

1239 if length is not None: 

1240 length_regex = f"{{{int(length)}}}" 

1241 else: 

1242 if maxlength is None: 

1243 maxlength_value = "" 

1244 else: 

1245 maxlength_value = str(int(maxlength)) 

1246 length_regex = f"{{{int(minlength)},{maxlength_value}}}" 

1247 self.regex = f"[^/]{length_regex}" 

1248 

1249 

1250class AnyConverter(BaseConverter): 

1251 """Matches one of the items provided. Items can either be Python 

1252 identifiers or strings:: 

1253 

1254 Rule('/<any(about, help, imprint, class, "foo,bar"):page_name>') 

1255 

1256 :param map: the :class:`Map`. 

1257 :param items: this function accepts the possible items as positional 

1258 arguments. 

1259 """ 

1260 

1261 def __init__(self, map: "Map", *items: str) -> None: 

1262 super().__init__(map) 

1263 self.regex = f"(?:{'|'.join([re.escape(x) for x in items])})" 

1264 

1265 

1266class PathConverter(BaseConverter): 

1267 """Like the default :class:`UnicodeConverter`, but it also matches 

1268 slashes. This is useful for wikis and similar applications:: 

1269 

1270 Rule('/<path:wikipage>') 

1271 Rule('/<path:wikipage>/edit') 

1272 

1273 :param map: the :class:`Map`. 

1274 """ 

1275 

1276 regex = "[^/].*?" 

1277 weight = 200 

1278 

1279 

1280class NumberConverter(BaseConverter): 

1281 """Baseclass for `IntegerConverter` and `FloatConverter`. 

1282 

1283 :internal: 

1284 """ 

1285 

1286 weight = 50 

1287 num_convert: t.Callable = int 

1288 

1289 def __init__( 

1290 self, 

1291 map: "Map", 

1292 fixed_digits: int = 0, 

1293 min: t.Optional[int] = None, 

1294 max: t.Optional[int] = None, 

1295 signed: bool = False, 

1296 ) -> None: 

1297 if signed: 

1298 self.regex = self.signed_regex 

1299 super().__init__(map) 

1300 self.fixed_digits = fixed_digits 

1301 self.min = min 

1302 self.max = max 

1303 self.signed = signed 

1304 

1305 def to_python(self, value: str) -> t.Any: 

1306 if self.fixed_digits and len(value) != self.fixed_digits: 

1307 raise ValidationError() 

1308 value = self.num_convert(value) 

1309 if (self.min is not None and value < self.min) or ( 

1310 self.max is not None and value > self.max 

1311 ): 

1312 raise ValidationError() 

1313 return value 

1314 

1315 def to_url(self, value: t.Any) -> str: 

1316 value = str(self.num_convert(value)) 

1317 if self.fixed_digits: 

1318 value = value.zfill(self.fixed_digits) 

1319 return value 

1320 

1321 @property 

1322 def signed_regex(self) -> str: 

1323 return f"-?{self.regex}" 

1324 

1325 

1326class IntegerConverter(NumberConverter): 

1327 """This converter only accepts integer values:: 

1328 

1329 Rule("/page/<int:page>") 

1330 

1331 By default it only accepts unsigned, positive values. The ``signed`` 

1332 parameter will enable signed, negative values. :: 

1333 

1334 Rule("/page/<int(signed=True):page>") 

1335 

1336 :param map: The :class:`Map`. 

1337 :param fixed_digits: The number of fixed digits in the URL. If you 

1338 set this to ``4`` for example, the rule will only match if the 

1339 URL looks like ``/0001/``. The default is variable length. 

1340 :param min: The minimal value. 

1341 :param max: The maximal value. 

1342 :param signed: Allow signed (negative) values. 

1343 

1344 .. versionadded:: 0.15 

1345 The ``signed`` parameter. 

1346 """ 

1347 

1348 regex = r"\d+" 

1349 

1350 

1351class FloatConverter(NumberConverter): 

1352 """This converter only accepts floating point values:: 

1353 

1354 Rule("/probability/<float:probability>") 

1355 

1356 By default it only accepts unsigned, positive values. The ``signed`` 

1357 parameter will enable signed, negative values. :: 

1358 

1359 Rule("/offset/<float(signed=True):offset>") 

1360 

1361 :param map: The :class:`Map`. 

1362 :param min: The minimal value. 

1363 :param max: The maximal value. 

1364 :param signed: Allow signed (negative) values. 

1365 

1366 .. versionadded:: 0.15 

1367 The ``signed`` parameter. 

1368 """ 

1369 

1370 regex = r"\d+\.\d+" 

1371 num_convert = float 

1372 

1373 def __init__( 

1374 self, 

1375 map: "Map", 

1376 min: t.Optional[float] = None, 

1377 max: t.Optional[float] = None, 

1378 signed: bool = False, 

1379 ) -> None: 

1380 super().__init__(map, min=min, max=max, signed=signed) # type: ignore 

1381 

1382 

1383class UUIDConverter(BaseConverter): 

1384 """This converter only accepts UUID strings:: 

1385 

1386 Rule('/object/<uuid:identifier>') 

1387 

1388 .. versionadded:: 0.10 

1389 

1390 :param map: the :class:`Map`. 

1391 """ 

1392 

1393 regex = ( 

1394 r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-" 

1395 r"[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}" 

1396 ) 

1397 

1398 def to_python(self, value: str) -> uuid.UUID: 

1399 return uuid.UUID(value) 

1400 

1401 def to_url(self, value: uuid.UUID) -> str: 

1402 return str(value) 

1403 

1404 

1405#: the default converter mapping for the map. 

1406DEFAULT_CONVERTERS: t.Mapping[str, t.Type[BaseConverter]] = { 

1407 "default": UnicodeConverter, 

1408 "string": UnicodeConverter, 

1409 "any": AnyConverter, 

1410 "path": PathConverter, 

1411 "int": IntegerConverter, 

1412 "float": FloatConverter, 

1413 "uuid": UUIDConverter, 

1414} 

1415 

1416 

1417class Map: 

1418 """The map class stores all the URL rules and some configuration 

1419 parameters. Some of the configuration values are only stored on the 

1420 `Map` instance since those affect all rules, others are just defaults 

1421 and can be overridden for each rule. Note that you have to specify all 

1422 arguments besides the `rules` as keyword arguments! 

1423 

1424 :param rules: sequence of url rules for this map. 

1425 :param default_subdomain: The default subdomain for rules without a 

1426 subdomain defined. 

1427 :param charset: charset of the url. defaults to ``"utf-8"`` 

1428 :param strict_slashes: If a rule ends with a slash but the matched 

1429 URL does not, redirect to the URL with a trailing slash. 

1430 :param merge_slashes: Merge consecutive slashes when matching or 

1431 building URLs. Matches will redirect to the normalized URL. 

1432 Slashes in variable parts are not merged. 

1433 :param redirect_defaults: This will redirect to the default rule if it 

1434 wasn't visited that way. This helps creating 

1435 unique URLs. 

1436 :param converters: A dict of converters that adds additional converters 

1437 to the list of converters. If you redefine one 

1438 converter this will override the original one. 

1439 :param sort_parameters: If set to `True` the url parameters are sorted. 

1440 See `url_encode` for more details. 

1441 :param sort_key: The sort key function for `url_encode`. 

1442 :param encoding_errors: the error method to use for decoding 

1443 :param host_matching: if set to `True` it enables the host matching 

1444 feature and disables the subdomain one. If 

1445 enabled the `host` parameter to rules is used 

1446 instead of the `subdomain` one. 

1447 

1448 .. versionchanged:: 1.0 

1449 If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules 

1450 will match. 

1451 

1452 .. versionchanged:: 1.0 

1453 Added ``merge_slashes``. 

1454 

1455 .. versionchanged:: 0.7 

1456 Added ``encoding_errors`` and ``host_matching``. 

1457 

1458 .. versionchanged:: 0.5 

1459 Added ``sort_parameters`` and ``sort_key``. 

1460 """ 

1461 

1462 #: A dict of default converters to be used. 

1463 default_converters = ImmutableDict(DEFAULT_CONVERTERS) 

1464 

1465 #: The type of lock to use when updating. 

1466 #: 

1467 #: .. versionadded:: 1.0 

1468 lock_class = Lock 

1469 

1470 def __init__( 

1471 self, 

1472 rules: t.Optional[t.Iterable[RuleFactory]] = None, 

1473 default_subdomain: str = "", 

1474 charset: str = "utf-8", 

1475 strict_slashes: bool = True, 

1476 merge_slashes: bool = True, 

1477 redirect_defaults: bool = True, 

1478 converters: t.Optional[t.Mapping[str, t.Type[BaseConverter]]] = None, 

1479 sort_parameters: bool = False, 

1480 sort_key: t.Optional[t.Callable[[t.Any], t.Any]] = None, 

1481 encoding_errors: str = "replace", 

1482 host_matching: bool = False, 

1483 ) -> None: 

1484 self._rules: t.List[Rule] = [] 

1485 self._rules_by_endpoint: t.Dict[str, t.List[Rule]] = {} 

1486 self._remap = True 

1487 self._remap_lock = self.lock_class() 

1488 

1489 self.default_subdomain = default_subdomain 

1490 self.charset = charset 

1491 self.encoding_errors = encoding_errors 

1492 self.strict_slashes = strict_slashes 

1493 self.merge_slashes = merge_slashes 

1494 self.redirect_defaults = redirect_defaults 

1495 self.host_matching = host_matching 

1496 

1497 self.converters = self.default_converters.copy() 

1498 if converters: 

1499 self.converters.update(converters) 

1500 

1501 self.sort_parameters = sort_parameters 

1502 self.sort_key = sort_key 

1503 

1504 for rulefactory in rules or (): 

1505 self.add(rulefactory) 

1506 

1507 def is_endpoint_expecting(self, endpoint: str, *arguments: str) -> bool: 

1508 """Iterate over all rules and check if the endpoint expects 

1509 the arguments provided. This is for example useful if you have 

1510 some URLs that expect a language code and others that do not and 

1511 you want to wrap the builder a bit so that the current language 

1512 code is automatically added if not provided but endpoints expect 

1513 it. 

1514 

1515 :param endpoint: the endpoint to check. 

1516 :param arguments: this function accepts one or more arguments 

1517 as positional arguments. Each one of them is 

1518 checked. 

1519 """ 

1520 self.update() 

1521 arguments = set(arguments) 

1522 for rule in self._rules_by_endpoint[endpoint]: 

1523 if arguments.issubset(rule.arguments): 

1524 return True 

1525 return False 

1526 

1527 def iter_rules(self, endpoint: t.Optional[str] = None) -> t.Iterator[Rule]: 

1528 """Iterate over all rules or the rules of an endpoint. 

1529 

1530 :param endpoint: if provided only the rules for that endpoint 

1531 are returned. 

1532 :return: an iterator 

1533 """ 

1534 self.update() 

1535 if endpoint is not None: 

1536 return iter(self._rules_by_endpoint[endpoint]) 

1537 return iter(self._rules) 

1538 

1539 def add(self, rulefactory: RuleFactory) -> None: 

1540 """Add a new rule or factory to the map and bind it. Requires that the 

1541 rule is not bound to another map. 

1542 

1543 :param rulefactory: a :class:`Rule` or :class:`RuleFactory` 

1544 """ 

1545 for rule in rulefactory.get_rules(self): 

1546 rule.bind(self) 

1547 self._rules.append(rule) 

1548 self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule) 

1549 self._remap = True 

1550 

1551 def bind( 

1552 self, 

1553 server_name: str, 

1554 script_name: t.Optional[str] = None, 

1555 subdomain: t.Optional[str] = None, 

1556 url_scheme: str = "http", 

1557 default_method: str = "GET", 

1558 path_info: t.Optional[str] = None, 

1559 query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, 

1560 ) -> "MapAdapter": 

1561 """Return a new :class:`MapAdapter` with the details specified to the 

1562 call. Note that `script_name` will default to ``'/'`` if not further 

1563 specified or `None`. The `server_name` at least is a requirement 

1564 because the HTTP RFC requires absolute URLs for redirects and so all 

1565 redirect exceptions raised by Werkzeug will contain the full canonical 

1566 URL. 

1567 

1568 If no path_info is passed to :meth:`match` it will use the default path 

1569 info passed to bind. While this doesn't really make sense for 

1570 manual bind calls, it's useful if you bind a map to a WSGI 

1571 environment which already contains the path info. 

1572 

1573 `subdomain` will default to the `default_subdomain` for this map if 

1574 no defined. If there is no `default_subdomain` you cannot use the 

1575 subdomain feature. 

1576 

1577 .. versionchanged:: 1.0 

1578 If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules 

1579 will match. 

1580 

1581 .. versionchanged:: 0.15 

1582 ``path_info`` defaults to ``'/'`` if ``None``. 

1583 

1584 .. versionchanged:: 0.8 

1585 ``query_args`` can be a string. 

1586 

1587 .. versionchanged:: 0.7 

1588 Added ``query_args``. 

1589 """ 

1590 server_name = server_name.lower() 

1591 if self.host_matching: 

1592 if subdomain is not None: 

1593 raise RuntimeError("host matching enabled and a subdomain was provided") 

1594 elif subdomain is None: 

1595 subdomain = self.default_subdomain 

1596 if script_name is None: 

1597 script_name = "/" 

1598 if path_info is None: 

1599 path_info = "/" 

1600 

1601 try: 

1602 server_name = _encode_idna(server_name) # type: ignore 

1603 except UnicodeError as e: 

1604 raise BadHost() from e 

1605 

1606 return MapAdapter( 

1607 self, 

1608 server_name, 

1609 script_name, 

1610 subdomain, 

1611 url_scheme, 

1612 path_info, 

1613 default_method, 

1614 query_args, 

1615 ) 

1616 

1617 def bind_to_environ( 

1618 self, 

1619 environ: t.Union["WSGIEnvironment", "Request"], 

1620 server_name: t.Optional[str] = None, 

1621 subdomain: t.Optional[str] = None, 

1622 ) -> "MapAdapter": 

1623 """Like :meth:`bind` but you can pass it an WSGI environment and it 

1624 will fetch the information from that dictionary. Note that because of 

1625 limitations in the protocol there is no way to get the current 

1626 subdomain and real `server_name` from the environment. If you don't 

1627 provide it, Werkzeug will use `SERVER_NAME` and `SERVER_PORT` (or 

1628 `HTTP_HOST` if provided) as used `server_name` with disabled subdomain 

1629 feature. 

1630 

1631 If `subdomain` is `None` but an environment and a server name is 

1632 provided it will calculate the current subdomain automatically. 

1633 Example: `server_name` is ``'example.com'`` and the `SERVER_NAME` 

1634 in the wsgi `environ` is ``'staging.dev.example.com'`` the calculated 

1635 subdomain will be ``'staging.dev'``. 

1636 

1637 If the object passed as environ has an environ attribute, the value of 

1638 this attribute is used instead. This allows you to pass request 

1639 objects. Additionally `PATH_INFO` added as a default of the 

1640 :class:`MapAdapter` so that you don't have to pass the path info to 

1641 the match method. 

1642 

1643 .. versionchanged:: 1.0.0 

1644 If the passed server name specifies port 443, it will match 

1645 if the incoming scheme is ``https`` without a port. 

1646 

1647 .. versionchanged:: 1.0.0 

1648 A warning is shown when the passed server name does not 

1649 match the incoming WSGI server name. 

1650 

1651 .. versionchanged:: 0.8 

1652 This will no longer raise a ValueError when an unexpected server 

1653 name was passed. 

1654 

1655 .. versionchanged:: 0.5 

1656 previously this method accepted a bogus `calculate_subdomain` 

1657 parameter that did not have any effect. It was removed because 

1658 of that. 

1659 

1660 :param environ: a WSGI environment. 

1661 :param server_name: an optional server name hint (see above). 

1662 :param subdomain: optionally the current subdomain (see above). 

1663 """ 

1664 env = _get_environ(environ) 

1665 wsgi_server_name = get_host(env).lower() 

1666 scheme = env["wsgi.url_scheme"] 

1667 upgrade = any( 

1668 v.strip() == "upgrade" 

1669 for v in env.get("HTTP_CONNECTION", "").lower().split(",") 

1670 ) 

1671 

1672 if upgrade and env.get("HTTP_UPGRADE", "").lower() == "websocket": 

1673 scheme = "wss" if scheme == "https" else "ws" 

1674 

1675 if server_name is None: 

1676 server_name = wsgi_server_name 

1677 else: 

1678 server_name = server_name.lower() 

1679 

1680 # strip standard port to match get_host() 

1681 if scheme in {"http", "ws"} and server_name.endswith(":80"): 

1682 server_name = server_name[:-3] 

1683 elif scheme in {"https", "wss"} and server_name.endswith(":443"): 

1684 server_name = server_name[:-4] 

1685 

1686 if subdomain is None and not self.host_matching: 

1687 cur_server_name = wsgi_server_name.split(".") 

1688 real_server_name = server_name.split(".") 

1689 offset = -len(real_server_name) 

1690 

1691 if cur_server_name[offset:] != real_server_name: 

1692 # This can happen even with valid configs if the server was 

1693 # accessed directly by IP address under some situations. 

1694 # Instead of raising an exception like in Werkzeug 0.7 or 

1695 # earlier we go by an invalid subdomain which will result 

1696 # in a 404 error on matching. 

1697 warnings.warn( 

1698 f"Current server name {wsgi_server_name!r} doesn't match configured" 

1699 f" server name {server_name!r}", 

1700 stacklevel=2, 

1701 ) 

1702 subdomain = "<invalid>" 

1703 else: 

1704 subdomain = ".".join(filter(None, cur_server_name[:offset])) 

1705 

1706 def _get_wsgi_string(name: str) -> t.Optional[str]: 

1707 val = env.get(name) 

1708 if val is not None: 

1709 return _wsgi_decoding_dance(val, self.charset) 

1710 return None 

1711 

1712 script_name = _get_wsgi_string("SCRIPT_NAME") 

1713 path_info = _get_wsgi_string("PATH_INFO") 

1714 query_args = _get_wsgi_string("QUERY_STRING") 

1715 return Map.bind( 

1716 self, 

1717 server_name, 

1718 script_name, 

1719 subdomain, 

1720 scheme, 

1721 env["REQUEST_METHOD"], 

1722 path_info, 

1723 query_args=query_args, 

1724 ) 

1725 

1726 def update(self) -> None: 

1727 """Called before matching and building to keep the compiled rules 

1728 in the correct order after things changed. 

1729 """ 

1730 if not self._remap: 

1731 return 

1732 

1733 with self._remap_lock: 

1734 if not self._remap: 

1735 return 

1736 

1737 self._rules.sort(key=lambda x: x.match_compare_key()) 

1738 for rules in self._rules_by_endpoint.values(): 

1739 rules.sort(key=lambda x: x.build_compare_key()) 

1740 self._remap = False 

1741 

1742 def __repr__(self) -> str: 

1743 rules = self.iter_rules() 

1744 return f"{type(self).__name__}({pformat(list(rules))})" 

1745 

1746 

1747class MapAdapter: 

1748 

1749 """Returned by :meth:`Map.bind` or :meth:`Map.bind_to_environ` and does 

1750 the URL matching and building based on runtime information. 

1751 """ 

1752 

1753 def __init__( 

1754 self, 

1755 map: Map, 

1756 server_name: str, 

1757 script_name: str, 

1758 subdomain: t.Optional[str], 

1759 url_scheme: str, 

1760 path_info: str, 

1761 default_method: str, 

1762 query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, 

1763 ): 

1764 self.map = map 

1765 self.server_name = _to_str(server_name) 

1766 script_name = _to_str(script_name) 

1767 if not script_name.endswith("/"): 

1768 script_name += "/" 

1769 self.script_name = script_name 

1770 self.subdomain = _to_str(subdomain) 

1771 self.url_scheme = _to_str(url_scheme) 

1772 self.path_info = _to_str(path_info) 

1773 self.default_method = _to_str(default_method) 

1774 self.query_args = query_args 

1775 self.websocket = self.url_scheme in {"ws", "wss"} 

1776 

1777 def dispatch( 

1778 self, 

1779 view_func: t.Callable[[str, t.Mapping[str, t.Any]], "WSGIApplication"], 

1780 path_info: t.Optional[str] = None, 

1781 method: t.Optional[str] = None, 

1782 catch_http_exceptions: bool = False, 

1783 ) -> "WSGIApplication": 

1784 """Does the complete dispatching process. `view_func` is called with 

1785 the endpoint and a dict with the values for the view. It should 

1786 look up the view function, call it, and return a response object 

1787 or WSGI application. http exceptions are not caught by default 

1788 so that applications can display nicer error messages by just 

1789 catching them by hand. If you want to stick with the default 

1790 error messages you can pass it ``catch_http_exceptions=True`` and 

1791 it will catch the http exceptions. 

1792 

1793 Here a small example for the dispatch usage:: 

1794 

1795 from werkzeug.wrappers import Request, Response 

1796 from werkzeug.wsgi import responder 

1797 from werkzeug.routing import Map, Rule 

1798 

1799 def on_index(request): 

1800 return Response('Hello from the index') 

1801 

1802 url_map = Map([Rule('/', endpoint='index')]) 

1803 views = {'index': on_index} 

1804 

1805 @responder 

1806 def application(environ, start_response): 

1807 request = Request(environ) 

1808 urls = url_map.bind_to_environ(environ) 

1809 return urls.dispatch(lambda e, v: views[e](request, **v), 

1810 catch_http_exceptions=True) 

1811 

1812 Keep in mind that this method might return exception objects, too, so 

1813 use :class:`Response.force_type` to get a response object. 

1814 

1815 :param view_func: a function that is called with the endpoint as 

1816 first argument and the value dict as second. Has 

1817 to dispatch to the actual view function with this 

1818 information. (see above) 

1819 :param path_info: the path info to use for matching. Overrides the 

1820 path info specified on binding. 

1821 :param method: the HTTP method used for matching. Overrides the 

1822 method specified on binding. 

1823 :param catch_http_exceptions: set to `True` to catch any of the 

1824 werkzeug :class:`HTTPException`\\s. 

1825 """ 

1826 try: 

1827 try: 

1828 endpoint, args = self.match(path_info, method) 

1829 except RequestRedirect as e: 

1830 return e 

1831 return view_func(endpoint, args) 

1832 except HTTPException as e: 

1833 if catch_http_exceptions: 

1834 return e 

1835 raise 

1836 

1837 @typing.overload 

1838 def match( # type: ignore 

1839 self, 

1840 path_info: t.Optional[str] = None, 

1841 method: t.Optional[str] = None, 

1842 return_rule: "te.Literal[False]" = False, 

1843 query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, 

1844 websocket: t.Optional[bool] = None, 

1845 ) -> t.Tuple[str, t.Mapping[str, t.Any]]: 

1846 ... 

1847 

1848 @typing.overload 

1849 def match( 

1850 self, 

1851 path_info: t.Optional[str] = None, 

1852 method: t.Optional[str] = None, 

1853 return_rule: "te.Literal[True]" = True, 

1854 query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, 

1855 websocket: t.Optional[bool] = None, 

1856 ) -> t.Tuple[Rule, t.Mapping[str, t.Any]]: 

1857 ... 

1858 

1859 def match( 

1860 self, 

1861 path_info: t.Optional[str] = None, 

1862 method: t.Optional[str] = None, 

1863 return_rule: bool = False, 

1864 query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, 

1865 websocket: t.Optional[bool] = None, 

1866 ) -> t.Tuple[t.Union[str, Rule], t.Mapping[str, t.Any]]: 

1867 """The usage is simple: you just pass the match method the current 

1868 path info as well as the method (which defaults to `GET`). The 

1869 following things can then happen: 

1870 

1871 - you receive a `NotFound` exception that indicates that no URL is 

1872 matching. A `NotFound` exception is also a WSGI application you 

1873 can call to get a default page not found page (happens to be the 

1874 same object as `werkzeug.exceptions.NotFound`) 

1875 

1876 - you receive a `MethodNotAllowed` exception that indicates that there 

1877 is a match for this URL but not for the current request method. 

1878 This is useful for RESTful applications. 

1879 

1880 - you receive a `RequestRedirect` exception with a `new_url` 

1881 attribute. This exception is used to notify you about a request 

1882 Werkzeug requests from your WSGI application. This is for example the 

1883 case if you request ``/foo`` although the correct URL is ``/foo/`` 

1884 You can use the `RequestRedirect` instance as response-like object 

1885 similar to all other subclasses of `HTTPException`. 

1886 

1887 - you receive a ``WebsocketMismatch`` exception if the only 

1888 match is a WebSocket rule but the bind is an HTTP request, or 

1889 if the match is an HTTP rule but the bind is a WebSocket 

1890 request. 

1891 

1892 - you get a tuple in the form ``(endpoint, arguments)`` if there is 

1893 a match (unless `return_rule` is True, in which case you get a tuple 

1894 in the form ``(rule, arguments)``) 

1895 

1896 If the path info is not passed to the match method the default path 

1897 info of the map is used (defaults to the root URL if not defined 

1898 explicitly). 

1899 

1900 All of the exceptions raised are subclasses of `HTTPException` so they 

1901 can be used as WSGI responses. They will all render generic error or 

1902 redirect pages. 

1903 

1904 Here is a small example for matching: 

1905 

1906 >>> m = Map([ 

1907 ... Rule('/', endpoint='index'), 

1908 ... Rule('/downloads/', endpoint='downloads/index'), 

1909 ... Rule('/downloads/<int:id>', endpoint='downloads/show') 

1910 ... ]) 

1911 >>> urls = m.bind("example.com", "/") 

1912 >>> urls.match("/", "GET") 

1913 ('index', {}) 

1914 >>> urls.match("/downloads/42") 

1915 ('downloads/show', {'id': 42}) 

1916 

1917 And here is what happens on redirect and missing URLs: 

1918 

1919 >>> urls.match("/downloads") 

1920 Traceback (most recent call last): 

1921 ... 

1922 RequestRedirect: http://example.com/downloads/ 

1923 >>> urls.match("/missing") 

1924 Traceback (most recent call last): 

1925 ... 

1926 NotFound: 404 Not Found 

1927 

1928 :param path_info: the path info to use for matching. Overrides the 

1929 path info specified on binding. 

1930 :param method: the HTTP method used for matching. Overrides the 

1931 method specified on binding. 

1932 :param return_rule: return the rule that matched instead of just the 

1933 endpoint (defaults to `False`). 

1934 :param query_args: optional query arguments that are used for 

1935 automatic redirects as string or dictionary. It's 

1936 currently not possible to use the query arguments 

1937 for URL matching. 

1938 :param websocket: Match WebSocket instead of HTTP requests. A 

1939 websocket request has a ``ws`` or ``wss`` 

1940 :attr:`url_scheme`. This overrides that detection. 

1941 

1942 .. versionadded:: 1.0 

1943 Added ``websocket``. 

1944 

1945 .. versionchanged:: 0.8 

1946 ``query_args`` can be a string. 

1947 

1948 .. versionadded:: 0.7 

1949 Added ``query_args``. 

1950 

1951 .. versionadded:: 0.6 

1952 Added ``return_rule``. 

1953 """ 

1954 self.map.update() 

1955 if path_info is None: 

1956 path_info = self.path_info 

1957 else: 

1958 path_info = _to_str(path_info, self.map.charset) 

1959 if query_args is None: 

1960 query_args = self.query_args or {} 

1961 method = (method or self.default_method).upper() 

1962 

1963 if websocket is None: 

1964 websocket = self.websocket 

1965 

1966 require_redirect = False 

1967 

1968 domain_part = self.server_name if self.map.host_matching else self.subdomain 

1969 path_part = f"/{path_info.lstrip('/')}" if path_info else "" 

1970 path = f"{domain_part}|{path_part}" 

1971 

1972 have_match_for = set() 

1973 websocket_mismatch = False 

1974 

1975 for rule in self.map._rules: 

1976 try: 

1977 rv = rule.match(path, method) 

1978 except RequestPath as e: 

1979 raise RequestRedirect( 

1980 self.make_redirect_url( 

1981 url_quote(e.path_info, self.map.charset, safe="/:|+"), 

1982 query_args, 

1983 ) 

1984 ) from None 

1985 except RequestAliasRedirect as e: 

1986 raise RequestRedirect( 

1987 self.make_alias_redirect_url( 

1988 path, rule.endpoint, e.matched_values, method, query_args 

1989 ) 

1990 ) from None 

1991 if rv is None: 

1992 continue 

1993 if rule.methods is not None and method not in rule.methods: 

1994 have_match_for.update(rule.methods) 

1995 continue 

1996 

1997 if rule.websocket != websocket: 

1998 websocket_mismatch = True 

1999 continue 

2000 

2001 if self.map.redirect_defaults: 

2002 redirect_url = self.get_default_redirect(rule, method, rv, query_args) 

2003 if redirect_url is not None: 

2004 raise RequestRedirect(redirect_url) 

2005 

2006 if rule.redirect_to is not None: 

2007 if isinstance(rule.redirect_to, str): 

2008 

2009 def _handle_match(match: t.Match[str]) -> str: 

2010 value = rv[match.group(1)] # type: ignore 

2011 return rule._converters[match.group(1)].to_url(value) 

2012 

2013 redirect_url = _simple_rule_re.sub(_handle_match, rule.redirect_to) 

2014 else: 

2015 redirect_url = rule.redirect_to(self, **rv) 

2016 

2017 if self.subdomain: 

2018 netloc = f"{self.subdomain}.{self.server_name}" 

2019 else: 

2020 netloc = self.server_name 

2021 

2022 raise RequestRedirect( 

2023 url_join( 

2024 f"{self.url_scheme or 'http'}://{netloc}{self.script_name}", 

2025 redirect_url, 

2026 ) 

2027 ) 

2028 

2029 if require_redirect: 

2030 raise RequestRedirect( 

2031 self.make_redirect_url( 

2032 url_quote(path_info, self.map.charset, safe="/:|+"), query_args 

2033 ) 

2034 ) 

2035 

2036 if return_rule: 

2037 return rule, rv 

2038 else: 

2039 return rule.endpoint, rv 

2040 

2041 if have_match_for: 

2042 raise MethodNotAllowed(valid_methods=list(have_match_for)) 

2043 

2044 if websocket_mismatch: 

2045 raise WebsocketMismatch() 

2046 

2047 raise NotFound() 

2048 

2049 def test( 

2050 self, path_info: t.Optional[str] = None, method: t.Optional[str] = None 

2051 ) -> bool: 

2052 """Test if a rule would match. Works like `match` but returns `True` 

2053 if the URL matches, or `False` if it does not exist. 

2054 

2055 :param path_info: the path info to use for matching. Overrides the 

2056 path info specified on binding. 

2057 :param method: the HTTP method used for matching. Overrides the 

2058 method specified on binding. 

2059 """ 

2060 try: 

2061 self.match(path_info, method) 

2062 except RequestRedirect: 

2063 pass 

2064 except HTTPException: 

2065 return False 

2066 return True 

2067 

2068 def allowed_methods(self, path_info: t.Optional[str] = None) -> t.Iterable[str]: 

2069 """Returns the valid methods that match for a given path. 

2070 

2071 .. versionadded:: 0.7 

2072 """ 

2073 try: 

2074 self.match(path_info, method="--") 

2075 except MethodNotAllowed as e: 

2076 return e.valid_methods # type: ignore 

2077 except HTTPException: 

2078 pass 

2079 return [] 

2080 

2081 def get_host(self, domain_part: t.Optional[str]) -> str: 

2082 """Figures out the full host name for the given domain part. The 

2083 domain part is a subdomain in case host matching is disabled or 

2084 a full host name. 

2085 """ 

2086 if self.map.host_matching: 

2087 if domain_part is None: 

2088 return self.server_name 

2089 return _to_str(domain_part, "ascii") 

2090 subdomain = domain_part 

2091 if subdomain is None: 

2092 subdomain = self.subdomain 

2093 else: 

2094 subdomain = _to_str(subdomain, "ascii") 

2095 

2096 if subdomain: 

2097 return f"{subdomain}.{self.server_name}" 

2098 else: 

2099 return self.server_name 

2100 

2101 def get_default_redirect( 

2102 self, 

2103 rule: Rule, 

2104 method: str, 

2105 values: t.MutableMapping[str, t.Any], 

2106 query_args: t.Union[t.Mapping[str, t.Any], str], 

2107 ) -> t.Optional[str]: 

2108 """A helper that returns the URL to redirect to if it finds one. 

2109 This is used for default redirecting only. 

2110 

2111 :internal: 

2112 """ 

2113 assert self.map.redirect_defaults 

2114 for r in self.map._rules_by_endpoint[rule.endpoint]: 

2115 # every rule that comes after this one, including ourself 

2116 # has a lower priority for the defaults. We order the ones 

2117 # with the highest priority up for building. 

2118 if r is rule: 

2119 break 

2120 if r.provides_defaults_for(rule) and r.suitable_for(values, method): 

2121 values.update(r.defaults) # type: ignore 

2122 domain_part, path = r.build(values) # type: ignore 

2123 return self.make_redirect_url(path, query_args, domain_part=domain_part) 

2124 return None 

2125 

2126 def encode_query_args(self, query_args: t.Union[t.Mapping[str, t.Any], str]) -> str: 

2127 if not isinstance(query_args, str): 

2128 return url_encode(query_args, self.map.charset) 

2129 return query_args 

2130 

2131 def make_redirect_url( 

2132 self, 

2133 path_info: str, 

2134 query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, 

2135 domain_part: t.Optional[str] = None, 

2136 ) -> str: 

2137 """Creates a redirect URL. 

2138 

2139 :internal: 

2140 """ 

2141 if query_args: 

2142 suffix = f"?{self.encode_query_args(query_args)}" 

2143 else: 

2144 suffix = "" 

2145 

2146 scheme = self.url_scheme or "http" 

2147 host = self.get_host(domain_part) 

2148 path = posixpath.join(self.script_name.strip("/"), path_info.lstrip("/")) 

2149 return f"{scheme}://{host}/{path}{suffix}" 

2150 

2151 def make_alias_redirect_url( 

2152 self, 

2153 path: str, 

2154 endpoint: str, 

2155 values: t.Mapping[str, t.Any], 

2156 method: str, 

2157 query_args: t.Union[t.Mapping[str, t.Any], str], 

2158 ) -> str: 

2159 """Internally called to make an alias redirect URL.""" 

2160 url = self.build( 

2161 endpoint, values, method, append_unknown=False, force_external=True 

2162 ) 

2163 if query_args: 

2164 url += f"?{self.encode_query_args(query_args)}" 

2165 assert url != path, "detected invalid alias setting. No canonical URL found" 

2166 return url 

2167 

2168 def _partial_build( 

2169 self, 

2170 endpoint: str, 

2171 values: t.Mapping[str, t.Any], 

2172 method: t.Optional[str], 

2173 append_unknown: bool, 

2174 ) -> t.Optional[t.Tuple[str, str, bool]]: 

2175 """Helper for :meth:`build`. Returns subdomain and path for the 

2176 rule that accepts this endpoint, values and method. 

2177 

2178 :internal: 

2179 """ 

2180 # in case the method is none, try with the default method first 

2181 if method is None: 

2182 rv = self._partial_build( 

2183 endpoint, values, self.default_method, append_unknown 

2184 ) 

2185 if rv is not None: 

2186 return rv 

2187 

2188 # Default method did not match or a specific method is passed. 

2189 # Check all for first match with matching host. If no matching 

2190 # host is found, go with first result. 

2191 first_match = None 

2192 

2193 for rule in self.map._rules_by_endpoint.get(endpoint, ()): 

2194 if rule.suitable_for(values, method): 

2195 build_rv = rule.build(values, append_unknown) 

2196 

2197 if build_rv is not None: 

2198 rv = (build_rv[0], build_rv[1], rule.websocket) 

2199 if self.map.host_matching: 

2200 if rv[0] == self.server_name: 

2201 return rv 

2202 elif first_match is None: 

2203 first_match = rv 

2204 else: 

2205 return rv 

2206 

2207 return first_match 

2208 

2209 def build( 

2210 self, 

2211 endpoint: str, 

2212 values: t.Optional[t.Mapping[str, t.Any]] = None, 

2213 method: t.Optional[str] = None, 

2214 force_external: bool = False, 

2215 append_unknown: bool = True, 

2216 url_scheme: t.Optional[str] = None, 

2217 ) -> str: 

2218 """Building URLs works pretty much the other way round. Instead of 

2219 `match` you call `build` and pass it the endpoint and a dict of 

2220 arguments for the placeholders. 

2221 

2222 The `build` function also accepts an argument called `force_external` 

2223 which, if you set it to `True` will force external URLs. Per default 

2224 external URLs (include the server name) will only be used if the 

2225 target URL is on a different subdomain. 

2226 

2227 >>> m = Map([ 

2228 ... Rule('/', endpoint='index'), 

2229 ... Rule('/downloads/', endpoint='downloads/index'), 

2230 ... Rule('/downloads/<int:id>', endpoint='downloads/show') 

2231 ... ]) 

2232 >>> urls = m.bind("example.com", "/") 

2233 >>> urls.build("index", {}) 

2234 '/' 

2235 >>> urls.build("downloads/show", {'id': 42}) 

2236 '/downloads/42' 

2237 >>> urls.build("downloads/show", {'id': 42}, force_external=True) 

2238 'http://example.com/downloads/42' 

2239 

2240 Because URLs cannot contain non ASCII data you will always get 

2241 bytes back. Non ASCII characters are urlencoded with the 

2242 charset defined on the map instance. 

2243 

2244 Additional values are converted to strings and appended to the URL as 

2245 URL querystring parameters: 

2246 

2247 >>> urls.build("index", {'q': 'My Searchstring'}) 

2248 '/?q=My+Searchstring' 

2249 

2250 When processing those additional values, lists are furthermore 

2251 interpreted as multiple values (as per 

2252 :py:class:`werkzeug.datastructures.MultiDict`): 

2253 

2254 >>> urls.build("index", {'q': ['a', 'b', 'c']}) 

2255 '/?q=a&q=b&q=c' 

2256 

2257 Passing a ``MultiDict`` will also add multiple values: 

2258 

2259 >>> urls.build("index", MultiDict((('p', 'z'), ('q', 'a'), ('q', 'b')))) 

2260 '/?p=z&q=a&q=b' 

2261 

2262 If a rule does not exist when building a `BuildError` exception is 

2263 raised. 

2264 

2265 The build method accepts an argument called `method` which allows you 

2266 to specify the method you want to have an URL built for if you have 

2267 different methods for the same endpoint specified. 

2268 

2269 :param endpoint: the endpoint of the URL to build. 

2270 :param values: the values for the URL to build. Unhandled values are 

2271 appended to the URL as query parameters. 

2272 :param method: the HTTP method for the rule if there are different 

2273 URLs for different methods on the same endpoint. 

2274 :param force_external: enforce full canonical external URLs. If the URL 

2275 scheme is not provided, this will generate 

2276 a protocol-relative URL. 

2277 :param append_unknown: unknown parameters are appended to the generated 

2278 URL as query string argument. Disable this 

2279 if you want the builder to ignore those. 

2280 :param url_scheme: Scheme to use in place of the bound 

2281 :attr:`url_scheme`. 

2282 

2283 .. versionchanged:: 2.0 

2284 Added the ``url_scheme`` parameter. 

2285 

2286 .. versionadded:: 0.6 

2287 Added the ``append_unknown`` parameter. 

2288 """ 

2289 self.map.update() 

2290 

2291 if values: 

2292 if isinstance(values, MultiDict): 

2293 values = { 

2294 k: (v[0] if len(v) == 1 else v) 

2295 for k, v in dict.items(values) 

2296 if len(v) != 0 

2297 } 

2298 else: # plain dict 

2299 values = {k: v for k, v in values.items() if v is not None} 

2300 else: 

2301 values = {} 

2302 

2303 rv = self._partial_build(endpoint, values, method, append_unknown) 

2304 if rv is None: 

2305 raise BuildError(endpoint, values, method, self) 

2306 

2307 domain_part, path, websocket = rv 

2308 host = self.get_host(domain_part) 

2309 

2310 if url_scheme is None: 

2311 url_scheme = self.url_scheme 

2312 

2313 # Always build WebSocket routes with the scheme (browsers 

2314 # require full URLs). If bound to a WebSocket, ensure that HTTP 

2315 # routes are built with an HTTP scheme. 

2316 secure = url_scheme in {"https", "wss"} 

2317 

2318 if websocket: 

2319 force_external = True 

2320 url_scheme = "wss" if secure else "ws" 

2321 elif url_scheme: 

2322 url_scheme = "https" if secure else "http" 

2323 

2324 # shortcut this. 

2325 if not force_external and ( 

2326 (self.map.host_matching and host == self.server_name) 

2327 or (not self.map.host_matching and domain_part == self.subdomain) 

2328 ): 

2329 return f"{self.script_name.rstrip('/')}/{path.lstrip('/')}" 

2330 

2331 scheme = f"{url_scheme}:" if url_scheme else "" 

2332 return f"{scheme}//{host}{self.script_name[:-1]}/{path.lstrip('/')}"