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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

314 statements  

1from __future__ import annotations 

2 

3import typing as t 

4import warnings 

5from pprint import pformat 

6from threading import Lock 

7from urllib.parse import quote 

8from urllib.parse import urljoin 

9from urllib.parse import urlunsplit 

10 

11from .._internal import _get_environ 

12from .._internal import _wsgi_decoding_dance 

13from ..datastructures import ImmutableDict 

14from ..datastructures import MultiDict 

15from ..exceptions import BadHost 

16from ..exceptions import HTTPException 

17from ..exceptions import MethodNotAllowed 

18from ..exceptions import NotFound 

19from ..urls import _urlencode 

20from ..wsgi import get_host 

21from .converters import DEFAULT_CONVERTERS 

22from .exceptions import BuildError 

23from .exceptions import NoMatch 

24from .exceptions import RequestAliasRedirect 

25from .exceptions import RequestPath 

26from .exceptions import RequestRedirect 

27from .exceptions import WebsocketMismatch 

28from .matcher import StateMachineMatcher 

29from .rules import _simple_rule_re 

30from .rules import Rule 

31 

32if t.TYPE_CHECKING: 

33 from _typeshed.wsgi import WSGIApplication 

34 from _typeshed.wsgi import WSGIEnvironment 

35 

36 from ..wrappers.request import Request 

37 from .converters import BaseConverter 

38 from .rules import RuleFactory 

39 

40 

41class Map: 

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

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

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

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

46 arguments besides the `rules` as keyword arguments! 

47 

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

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

50 subdomain defined. 

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

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

53 :param merge_slashes: Merge consecutive slashes when matching or 

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

55 Slashes in variable parts are not merged. 

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

57 wasn't visited that way. This helps creating 

58 unique URLs. 

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

60 to the list of converters. If you redefine one 

61 converter this will override the original one. 

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

63 See `url_encode` for more details. 

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

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

66 feature and disables the subdomain one. If 

67 enabled the `host` parameter to rules is used 

68 instead of the `subdomain` one. 

69 

70 .. versionchanged:: 3.0 

71 The ``charset`` and ``encoding_errors`` parameters were removed. 

72 

73 .. versionchanged:: 1.0 

74 If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules will match. 

75 

76 .. versionchanged:: 1.0 

77 The ``merge_slashes`` parameter was added. 

78 

79 .. versionchanged:: 0.7 

80 The ``encoding_errors`` and ``host_matching`` parameters were added. 

81 

82 .. versionchanged:: 0.5 

83 The ``sort_parameters`` and ``sort_key`` paramters were added. 

84 """ 

85 

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

87 default_converters = ImmutableDict(DEFAULT_CONVERTERS) 

88 

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

90 #: 

91 #: .. versionadded:: 1.0 

92 lock_class = Lock 

93 

94 def __init__( 

95 self, 

96 rules: t.Iterable[RuleFactory] | None = None, 

97 default_subdomain: str = "", 

98 strict_slashes: bool = True, 

99 merge_slashes: bool = True, 

100 redirect_defaults: bool = True, 

101 converters: t.Mapping[str, type[BaseConverter]] | None = None, 

102 sort_parameters: bool = False, 

103 sort_key: t.Callable[[t.Any], t.Any] | None = None, 

104 host_matching: bool = False, 

105 ) -> None: 

106 self._matcher = StateMachineMatcher(merge_slashes) 

107 self._rules_by_endpoint: dict[t.Any, list[Rule]] = {} 

108 self._remap = True 

109 self._remap_lock = self.lock_class() 

110 

111 self.default_subdomain = default_subdomain 

112 self.strict_slashes = strict_slashes 

113 self.redirect_defaults = redirect_defaults 

114 self.host_matching = host_matching 

115 

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

117 if converters: 

118 self.converters.update(converters) 

119 

120 self.sort_parameters = sort_parameters 

121 self.sort_key = sort_key 

122 

123 for rulefactory in rules or (): 

124 self.add(rulefactory) 

125 

126 @property 

127 def merge_slashes(self) -> bool: 

128 return self._matcher.merge_slashes 

129 

130 @merge_slashes.setter 

131 def merge_slashes(self, value: bool) -> None: 

132 self._matcher.merge_slashes = value 

133 

134 def is_endpoint_expecting(self, endpoint: t.Any, *arguments: str) -> bool: 

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

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

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

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

139 code is automatically added if not provided but endpoints expect 

140 it. 

141 

142 :param endpoint: the endpoint to check. 

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

144 as positional arguments. Each one of them is 

145 checked. 

146 """ 

147 self.update() 

148 arguments_set = set(arguments) 

149 for rule in self._rules_by_endpoint[endpoint]: 

150 if arguments_set.issubset(rule.arguments): 

151 return True 

152 return False 

153 

154 @property 

155 def _rules(self) -> list[Rule]: 

156 return [rule for rules in self._rules_by_endpoint.values() for rule in rules] 

157 

158 def iter_rules(self, endpoint: t.Any | None = None) -> t.Iterator[Rule]: 

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

160 

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

162 are returned. 

163 :return: an iterator 

164 """ 

165 self.update() 

166 if endpoint is not None: 

167 return iter(self._rules_by_endpoint[endpoint]) 

168 return iter(self._rules) 

169 

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

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

172 rule is not bound to another map. 

173 

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

175 """ 

176 for rule in rulefactory.get_rules(self): 

177 rule.bind(self) 

178 if not rule.build_only: 

179 self._matcher.add(rule) 

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

181 self._remap = True 

182 

183 def bind( 

184 self, 

185 server_name: str, 

186 script_name: str | None = None, 

187 subdomain: str | None = None, 

188 url_scheme: str = "http", 

189 default_method: str = "GET", 

190 path_info: str | None = None, 

191 query_args: t.Mapping[str, t.Any] | str | None = None, 

192 ) -> MapAdapter: 

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

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

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

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

197 redirect exceptions raised by Werkzeug will contain the full canonical 

198 URL. 

199 

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

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

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

203 environment which already contains the path info. 

204 

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

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

207 subdomain feature. 

208 

209 .. versionchanged:: 1.0 

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

211 will match. 

212 

213 .. versionchanged:: 0.15 

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

215 

216 .. versionchanged:: 0.8 

217 ``query_args`` can be a string. 

218 

219 .. versionchanged:: 0.7 

220 Added ``query_args``. 

221 """ 

222 server_name = server_name.lower() 

223 if self.host_matching: 

224 if subdomain is not None: 

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

226 elif subdomain is None: 

227 subdomain = self.default_subdomain 

228 if script_name is None: 

229 script_name = "/" 

230 if path_info is None: 

231 path_info = "/" 

232 

233 # Port isn't part of IDNA, and might push a name over the 63 octet limit. 

234 server_name, port_sep, port = server_name.partition(":") 

235 

236 try: 

237 server_name = server_name.encode("idna").decode("ascii") 

238 except UnicodeError as e: 

239 raise BadHost() from e 

240 

241 return MapAdapter( 

242 self, 

243 f"{server_name}{port_sep}{port}", 

244 script_name, 

245 subdomain, 

246 url_scheme, 

247 path_info, 

248 default_method, 

249 query_args, 

250 ) 

251 

252 def bind_to_environ( 

253 self, 

254 environ: WSGIEnvironment | Request, 

255 server_name: str | None = None, 

256 subdomain: str | None = None, 

257 ) -> MapAdapter: 

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

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

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

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

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

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

264 feature. 

265 

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

267 provided it will calculate the current subdomain automatically. 

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

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

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

271 

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

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

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

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

276 the match method. 

277 

278 .. versionchanged:: 1.0.0 

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

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

281 

282 .. versionchanged:: 1.0.0 

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

284 match the incoming WSGI server name. 

285 

286 .. versionchanged:: 0.8 

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

288 name was passed. 

289 

290 .. versionchanged:: 0.5 

291 previously this method accepted a bogus `calculate_subdomain` 

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

293 of that. 

294 

295 :param environ: a WSGI environment. 

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

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

298 """ 

299 env = _get_environ(environ) 

300 wsgi_server_name = get_host(env).lower() 

301 scheme = env["wsgi.url_scheme"] 

302 upgrade = any( 

303 v.strip() == "upgrade" 

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

305 ) 

306 

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

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

309 

310 if server_name is None: 

311 server_name = wsgi_server_name 

312 else: 

313 server_name = server_name.lower() 

314 

315 # strip standard port to match get_host() 

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

317 server_name = server_name[:-3] 

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

319 server_name = server_name[:-4] 

320 

321 if subdomain is None and not self.host_matching: 

322 cur_server_name = wsgi_server_name.split(".") 

323 real_server_name = server_name.split(".") 

324 offset = -len(real_server_name) 

325 

326 if cur_server_name[offset:] != real_server_name: 

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

328 # accessed directly by IP address under some situations. 

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

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

331 # in a 404 error on matching. 

332 warnings.warn( 

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

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

335 stacklevel=2, 

336 ) 

337 subdomain = "<invalid>" 

338 else: 

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

340 

341 def _get_wsgi_string(name: str) -> str | None: 

342 val = env.get(name) 

343 if val is not None: 

344 return _wsgi_decoding_dance(val) 

345 return None 

346 

347 script_name = _get_wsgi_string("SCRIPT_NAME") 

348 path_info = _get_wsgi_string("PATH_INFO") 

349 query_args = _get_wsgi_string("QUERY_STRING") 

350 return Map.bind( 

351 self, 

352 server_name, 

353 script_name, 

354 subdomain, 

355 scheme, 

356 env["REQUEST_METHOD"], 

357 path_info, 

358 query_args=query_args, 

359 ) 

360 

361 def update(self) -> None: 

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

363 in the correct order after things changed. 

364 """ 

365 if not self._remap: 

366 return 

367 

368 with self._remap_lock: 

369 if not self._remap: 

370 return 

371 

372 self._matcher.update() 

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

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

375 self._remap = False 

376 

377 def __repr__(self) -> str: 

378 rules = self.iter_rules() 

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

380 

381 

382class MapAdapter: 

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

384 the URL matching and building based on runtime information. 

385 """ 

386 

387 def __init__( 

388 self, 

389 map: Map, 

390 server_name: str, 

391 script_name: str, 

392 subdomain: str | None, 

393 url_scheme: str, 

394 path_info: str, 

395 default_method: str, 

396 query_args: t.Mapping[str, t.Any] | str | None = None, 

397 ): 

398 self.map = map 

399 self.server_name = server_name 

400 

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

402 script_name += "/" 

403 

404 self.script_name = script_name 

405 self.subdomain = subdomain 

406 self.url_scheme = url_scheme 

407 self.path_info = path_info 

408 self.default_method = default_method 

409 self.query_args = query_args 

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

411 

412 def dispatch( 

413 self, 

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

415 path_info: str | None = None, 

416 method: str | None = None, 

417 catch_http_exceptions: bool = False, 

418 ) -> WSGIApplication: 

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

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

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

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

423 so that applications can display nicer error messages by just 

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

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

426 it will catch the http exceptions. 

427 

428 Here a small example for the dispatch usage:: 

429 

430 from werkzeug.wrappers import Request, Response 

431 from werkzeug.wsgi import responder 

432 from werkzeug.routing import Map, Rule 

433 

434 def on_index(request): 

435 return Response('Hello from the index') 

436 

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

438 views = {'index': on_index} 

439 

440 @responder 

441 def application(environ, start_response): 

442 request = Request(environ) 

443 urls = url_map.bind_to_environ(environ) 

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

445 catch_http_exceptions=True) 

446 

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

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

449 

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

451 first argument and the value dict as second. Has 

452 to dispatch to the actual view function with this 

453 information. (see above) 

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

455 path info specified on binding. 

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

457 method specified on binding. 

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

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

460 """ 

461 try: 

462 try: 

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

464 except RequestRedirect as e: 

465 return e 

466 return view_func(endpoint, args) 

467 except HTTPException as e: 

468 if catch_http_exceptions: 

469 return e 

470 raise 

471 

472 @t.overload 

473 def match( 

474 self, 

475 path_info: str | None = None, 

476 method: str | None = None, 

477 return_rule: t.Literal[False] = False, 

478 query_args: t.Mapping[str, t.Any] | str | None = None, 

479 websocket: bool | None = None, 

480 ) -> tuple[t.Any, t.Mapping[str, t.Any]]: ... 

481 

482 @t.overload 

483 def match( 

484 self, 

485 path_info: str | None = None, 

486 method: str | None = None, 

487 return_rule: t.Literal[True] = True, 

488 query_args: t.Mapping[str, t.Any] | str | None = None, 

489 websocket: bool | None = None, 

490 ) -> tuple[Rule, t.Mapping[str, t.Any]]: ... 

491 

492 def match( 

493 self, 

494 path_info: str | None = None, 

495 method: str | None = None, 

496 return_rule: bool = False, 

497 query_args: t.Mapping[str, t.Any] | str | None = None, 

498 websocket: bool | None = None, 

499 ) -> tuple[t.Any | Rule, t.Mapping[str, t.Any]]: 

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

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

502 following things can then happen: 

503 

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

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

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

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

508 

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

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

511 This is useful for RESTful applications. 

512 

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

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

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

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

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

518 similar to all other subclasses of `HTTPException`. 

519 

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

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

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

523 request. 

524 

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

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

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

528 

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

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

531 explicitly). 

532 

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

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

535 redirect pages. 

536 

537 Here is a small example for matching: 

538 

539 >>> m = Map([ 

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

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

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

543 ... ]) 

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

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

546 ('index', {}) 

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

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

549 

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

551 

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

553 Traceback (most recent call last): 

554 ... 

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

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

557 Traceback (most recent call last): 

558 ... 

559 NotFound: 404 Not Found 

560 

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

562 path info specified on binding. 

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

564 method specified on binding. 

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

566 endpoint (defaults to `False`). 

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

568 automatic redirects as string or dictionary. It's 

569 currently not possible to use the query arguments 

570 for URL matching. 

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

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

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

574 

575 .. versionadded:: 1.0 

576 Added ``websocket``. 

577 

578 .. versionchanged:: 0.8 

579 ``query_args`` can be a string. 

580 

581 .. versionadded:: 0.7 

582 Added ``query_args``. 

583 

584 .. versionadded:: 0.6 

585 Added ``return_rule``. 

586 """ 

587 self.map.update() 

588 if path_info is None: 

589 path_info = self.path_info 

590 if query_args is None: 

591 query_args = self.query_args or {} 

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

593 

594 if websocket is None: 

595 websocket = self.websocket 

596 

597 domain_part = self.server_name 

598 

599 if not self.map.host_matching and self.subdomain is not None: 

600 domain_part = self.subdomain 

601 

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

603 

604 try: 

605 result = self.map._matcher.match(domain_part, path_part, method, websocket) 

606 except RequestPath as e: 

607 # safe = https://url.spec.whatwg.org/#url-path-segment-string 

608 new_path = quote(e.path_info, safe="!$&'()*+,/:;=@") 

609 raise RequestRedirect( 

610 self.make_redirect_url(new_path, query_args) 

611 ) from None 

612 except RequestAliasRedirect as e: 

613 raise RequestRedirect( 

614 self.make_alias_redirect_url( 

615 f"{domain_part}|{path_part}", 

616 e.endpoint, 

617 e.matched_values, 

618 method, 

619 query_args, 

620 ) 

621 ) from None 

622 except NoMatch as e: 

623 if e.have_match_for: 

624 raise MethodNotAllowed(valid_methods=list(e.have_match_for)) from None 

625 

626 if e.websocket_mismatch: 

627 raise WebsocketMismatch() from None 

628 

629 raise NotFound() from None 

630 else: 

631 rule, rv = result 

632 

633 if self.map.redirect_defaults: 

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

635 if redirect_url is not None: 

636 raise RequestRedirect(redirect_url) 

637 

638 if rule.redirect_to is not None: 

639 if isinstance(rule.redirect_to, str): 

640 

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

642 value = rv[match.group(1)] 

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

644 

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

646 else: 

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

648 

649 if self.subdomain: 

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

651 else: 

652 netloc = self.server_name 

653 

654 raise RequestRedirect( 

655 urljoin( 

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

657 redirect_url, 

658 ) 

659 ) 

660 

661 if return_rule: 

662 return rule, rv 

663 else: 

664 return rule.endpoint, rv 

665 

666 def test(self, path_info: str | None = None, method: str | None = None) -> bool: 

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

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

669 

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

671 path info specified on binding. 

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

673 method specified on binding. 

674 """ 

675 try: 

676 self.match(path_info, method) 

677 except RequestRedirect: 

678 pass 

679 except HTTPException: 

680 return False 

681 return True 

682 

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

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

685 

686 .. versionadded:: 0.7 

687 """ 

688 try: 

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

690 except MethodNotAllowed as e: 

691 return e.valid_methods # type: ignore 

692 except HTTPException: 

693 pass 

694 return [] 

695 

696 def get_host(self, domain_part: str | None) -> str: 

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

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

699 a full host name. 

700 """ 

701 if self.map.host_matching: 

702 if domain_part is None: 

703 return self.server_name 

704 

705 return domain_part 

706 

707 if domain_part is None: 

708 subdomain = self.subdomain 

709 else: 

710 subdomain = domain_part 

711 

712 if subdomain: 

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

714 else: 

715 return self.server_name 

716 

717 def get_default_redirect( 

718 self, 

719 rule: Rule, 

720 method: str, 

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

722 query_args: t.Mapping[str, t.Any] | str, 

723 ) -> str | None: 

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

725 This is used for default redirecting only. 

726 

727 :internal: 

728 """ 

729 assert self.map.redirect_defaults 

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

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

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

733 # with the highest priority up for building. 

734 if r is rule: 

735 break 

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

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

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

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

740 return None 

741 

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

743 if not isinstance(query_args, str): 

744 return _urlencode(query_args) 

745 return query_args 

746 

747 def make_redirect_url( 

748 self, 

749 path_info: str, 

750 query_args: t.Mapping[str, t.Any] | str | None = None, 

751 domain_part: str | None = None, 

752 ) -> str: 

753 """Creates a redirect URL. 

754 

755 :internal: 

756 """ 

757 if query_args is None: 

758 query_args = self.query_args 

759 

760 if query_args: 

761 query_str = self.encode_query_args(query_args) 

762 else: 

763 query_str = None 

764 

765 scheme = self.url_scheme or "http" 

766 host = self.get_host(domain_part) 

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

768 return urlunsplit((scheme, host, path, query_str, None)) 

769 

770 def make_alias_redirect_url( 

771 self, 

772 path: str, 

773 endpoint: t.Any, 

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

775 method: str, 

776 query_args: t.Mapping[str, t.Any] | str, 

777 ) -> str: 

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

779 url = self.build( 

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

781 ) 

782 if query_args: 

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

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

785 return url 

786 

787 def _partial_build( 

788 self, 

789 endpoint: t.Any, 

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

791 method: str | None, 

792 append_unknown: bool, 

793 ) -> tuple[str, str, bool] | None: 

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

795 rule that accepts this endpoint, values and method. 

796 

797 :internal: 

798 """ 

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

800 if method is None: 

801 rv = self._partial_build( 

802 endpoint, values, self.default_method, append_unknown 

803 ) 

804 if rv is not None: 

805 return rv 

806 

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

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

809 # host is found, go with first result. 

810 first_match = None 

811 

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

813 if rule.suitable_for(values, method): 

814 build_rv = rule.build(values, append_unknown) 

815 

816 if build_rv is not None: 

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

818 if self.map.host_matching: 

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

820 return rv 

821 elif first_match is None: 

822 first_match = rv 

823 else: 

824 return rv 

825 

826 return first_match 

827 

828 def build( 

829 self, 

830 endpoint: t.Any, 

831 values: t.Mapping[str, t.Any] | None = None, 

832 method: str | None = None, 

833 force_external: bool = False, 

834 append_unknown: bool = True, 

835 url_scheme: str | None = None, 

836 ) -> str: 

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

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

839 arguments for the placeholders. 

840 

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

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

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

844 target URL is on a different subdomain. 

845 

846 >>> m = Map([ 

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

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

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

850 ... ]) 

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

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

853 '/' 

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

855 '/downloads/42' 

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

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

858 

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

860 bytes back. Non ASCII characters are urlencoded with the 

861 charset defined on the map instance. 

862 

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

864 URL querystring parameters: 

865 

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

867 '/?q=My+Searchstring' 

868 

869 When processing those additional values, lists are furthermore 

870 interpreted as multiple values (as per 

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

872 

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

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

875 

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

877 

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

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

880 

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

882 raised. 

883 

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

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

886 different methods for the same endpoint specified. 

887 

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

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

890 appended to the URL as query parameters. 

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

892 URLs for different methods on the same endpoint. 

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

894 scheme is not provided, this will generate 

895 a protocol-relative URL. 

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

897 URL as query string argument. Disable this 

898 if you want the builder to ignore those. 

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

900 :attr:`url_scheme`. 

901 

902 .. versionchanged:: 2.0 

903 Added the ``url_scheme`` parameter. 

904 

905 .. versionadded:: 0.6 

906 Added the ``append_unknown`` parameter. 

907 """ 

908 self.map.update() 

909 

910 if values: 

911 if isinstance(values, MultiDict): 

912 values = { 

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

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

915 if len(v) != 0 

916 } 

917 else: # plain dict 

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

919 else: 

920 values = {} 

921 

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

923 if rv is None: 

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

925 

926 domain_part, path, websocket = rv 

927 host = self.get_host(domain_part) 

928 

929 if url_scheme is None: 

930 url_scheme = self.url_scheme 

931 

932 # Always build WebSocket routes with the scheme (browsers 

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

934 # routes are built with an HTTP scheme. 

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

936 

937 if websocket: 

938 force_external = True 

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

940 elif url_scheme: 

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

942 

943 # shortcut this. 

944 if not force_external and ( 

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

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

947 ): 

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

949 

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

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