Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tornado/routing.py: 28%

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

219 statements  

1# Copyright 2015 The Tornado Authors 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); you may 

4# not use this file except in compliance with the License. You may obtain 

5# a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

12# License for the specific language governing permissions and limitations 

13# under the License. 

14 

15"""Flexible routing implementation. 

16 

17Tornado routes HTTP requests to appropriate handlers using `Router` 

18class implementations. The `tornado.web.Application` class is a 

19`Router` implementation and may be used directly, or the classes in 

20this module may be used for additional flexibility. The `RuleRouter` 

21class can match on more criteria than `.Application`, or the `Router` 

22interface can be subclassed for maximum customization. 

23 

24`Router` interface extends `~.httputil.HTTPServerConnectionDelegate` 

25to provide additional routing capabilities. This also means that any 

26`Router` implementation can be used directly as a ``request_callback`` 

27for `~.httpserver.HTTPServer` constructor. 

28 

29`Router` subclass must implement a ``find_handler`` method to provide 

30a suitable `~.httputil.HTTPMessageDelegate` instance to handle the 

31request: 

32 

33.. code-block:: python 

34 

35 class CustomRouter(Router): 

36 def find_handler(self, request, **kwargs): 

37 # some routing logic providing a suitable HTTPMessageDelegate instance 

38 return MessageDelegate(request.connection) 

39 

40 class MessageDelegate(HTTPMessageDelegate): 

41 def __init__(self, connection): 

42 self.connection = connection 

43 

44 def finish(self): 

45 self.connection.write_headers( 

46 ResponseStartLine("HTTP/1.1", 200, "OK"), 

47 HTTPHeaders({"Content-Length": "2"}), 

48 b"OK") 

49 self.connection.finish() 

50 

51 router = CustomRouter() 

52 server = HTTPServer(router) 

53 

54The main responsibility of `Router` implementation is to provide a 

55mapping from a request to `~.httputil.HTTPMessageDelegate` instance 

56that will handle this request. In the example above we can see that 

57routing is possible even without instantiating an `~.web.Application`. 

58 

59For routing to `~.web.RequestHandler` implementations we need an 

60`~.web.Application` instance. `~.web.Application.get_handler_delegate` 

61provides a convenient way to create `~.httputil.HTTPMessageDelegate` 

62for a given request and `~.web.RequestHandler`. 

63 

64Here is a simple example of how we can we route to 

65`~.web.RequestHandler` subclasses by HTTP method: 

66 

67.. code-block:: python 

68 

69 resources = {} 

70 

71 class GetResource(RequestHandler): 

72 def get(self, path): 

73 if path not in resources: 

74 raise HTTPError(404) 

75 

76 self.finish(resources[path]) 

77 

78 class PostResource(RequestHandler): 

79 def post(self, path): 

80 resources[path] = self.request.body 

81 

82 class HTTPMethodRouter(Router): 

83 def __init__(self, app): 

84 self.app = app 

85 

86 def find_handler(self, request, **kwargs): 

87 handler = GetResource if request.method == "GET" else PostResource 

88 return self.app.get_handler_delegate(request, handler, path_args=[request.path]) 

89 

90 router = HTTPMethodRouter(Application()) 

91 server = HTTPServer(router) 

92 

93`ReversibleRouter` interface adds the ability to distinguish between 

94the routes and reverse them to the original urls using route's name 

95and additional arguments. `~.web.Application` is itself an 

96implementation of `ReversibleRouter` class. 

97 

98`RuleRouter` and `ReversibleRuleRouter` are implementations of 

99`Router` and `ReversibleRouter` interfaces and can be used for 

100creating rule-based routing configurations. 

101 

102Rules are instances of `Rule` class. They contain a `Matcher`, which 

103provides the logic for determining whether the rule is a match for a 

104particular request and a target, which can be one of the following. 

105 

1061) An instance of `~.httputil.HTTPServerConnectionDelegate`: 

107 

108.. code-block:: python 

109 

110 router = RuleRouter([ 

111 Rule(PathMatches("/handler"), ConnectionDelegate()), 

112 # ... more rules 

113 ]) 

114 

115 class ConnectionDelegate(HTTPServerConnectionDelegate): 

116 def start_request(self, server_conn, request_conn): 

117 return MessageDelegate(request_conn) 

118 

1192) A callable accepting a single argument of `~.httputil.HTTPServerRequest` type: 

120 

121.. code-block:: python 

122 

123 router = RuleRouter([ 

124 Rule(PathMatches("/callable"), request_callable) 

125 ]) 

126 

127 def request_callable(request): 

128 request.write(b"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\nOK") 

129 request.finish() 

130 

1313) Another `Router` instance: 

132 

133.. code-block:: python 

134 

135 router = RuleRouter([ 

136 Rule(PathMatches("/router.*"), CustomRouter()) 

137 ]) 

138 

139Of course a nested `RuleRouter` or a `~.web.Application` is allowed: 

140 

141.. code-block:: python 

142 

143 router = RuleRouter([ 

144 Rule(HostMatches("example.com"), RuleRouter([ 

145 Rule(PathMatches("/app1/.*"), Application([(r"/app1/handler", Handler)])), 

146 ])) 

147 ]) 

148 

149 server = HTTPServer(router) 

150 

151In the example below `RuleRouter` is used to route between applications: 

152 

153.. code-block:: python 

154 

155 app1 = Application([ 

156 (r"/app1/handler", Handler1), 

157 # other handlers ... 

158 ]) 

159 

160 app2 = Application([ 

161 (r"/app2/handler", Handler2), 

162 # other handlers ... 

163 ]) 

164 

165 router = RuleRouter([ 

166 Rule(PathMatches("/app1.*"), app1), 

167 Rule(PathMatches("/app2.*"), app2) 

168 ]) 

169 

170 server = HTTPServer(router) 

171 

172For more information on application-level routing see docs for `~.web.Application`. 

173 

174.. versionadded:: 4.5 

175 

176""" 

177 

178import re 

179from functools import partial 

180 

181from tornado import httputil 

182from tornado.httpserver import _CallableAdapter 

183from tornado.escape import url_escape, url_unescape, utf8 

184from tornado.log import app_log 

185from tornado.util import basestring_type, import_object, re_unescape, unicode_type 

186 

187from typing import ( 

188 Any, 

189 Union, 

190 Optional, 

191 Awaitable, 

192 List, 

193 Dict, 

194 Pattern, 

195 Tuple, 

196 overload, 

197 Sequence, 

198) 

199 

200 

201class Router(httputil.HTTPServerConnectionDelegate): 

202 """Abstract router interface.""" 

203 

204 def find_handler( 

205 self, request: httputil.HTTPServerRequest, **kwargs: Any 

206 ) -> Optional[httputil.HTTPMessageDelegate]: 

207 """Must be implemented to return an appropriate instance of `~.httputil.HTTPMessageDelegate` 

208 that can serve the request. 

209 Routing implementations may pass additional kwargs to extend the routing logic. 

210 

211 :arg httputil.HTTPServerRequest request: current HTTP request. 

212 :arg kwargs: additional keyword arguments passed by routing implementation. 

213 :returns: an instance of `~.httputil.HTTPMessageDelegate` that will be used to 

214 process the request. 

215 """ 

216 raise NotImplementedError() 

217 

218 def start_request( 

219 self, server_conn: object, request_conn: httputil.HTTPConnection 

220 ) -> httputil.HTTPMessageDelegate: 

221 return _RoutingDelegate(self, server_conn, request_conn) 

222 

223 

224class ReversibleRouter(Router): 

225 """Abstract router interface for routers that can handle named routes 

226 and support reversing them to original urls. 

227 """ 

228 

229 def reverse_url(self, name: str, *args: Any) -> Optional[str]: 

230 """Returns url string for a given route name and arguments 

231 or ``None`` if no match is found. 

232 

233 :arg str name: route name. 

234 :arg args: url parameters. 

235 :returns: parametrized url string for a given route name (or ``None``). 

236 """ 

237 raise NotImplementedError() 

238 

239 

240class _RoutingDelegate(httputil.HTTPMessageDelegate): 

241 def __init__( 

242 self, router: Router, server_conn: object, request_conn: httputil.HTTPConnection 

243 ) -> None: 

244 self.server_conn = server_conn 

245 self.request_conn = request_conn 

246 self.delegate = None # type: Optional[httputil.HTTPMessageDelegate] 

247 self.router = router # type: Router 

248 

249 def headers_received( 

250 self, 

251 start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], 

252 headers: httputil.HTTPHeaders, 

253 ) -> Optional[Awaitable[None]]: 

254 assert isinstance(start_line, httputil.RequestStartLine) 

255 request = httputil.HTTPServerRequest( 

256 connection=self.request_conn, 

257 server_connection=self.server_conn, 

258 start_line=start_line, 

259 headers=headers, 

260 ) 

261 

262 self.delegate = self.router.find_handler(request) 

263 if self.delegate is None: 

264 app_log.debug( 

265 "Delegate for %s %s request not found", 

266 start_line.method, 

267 start_line.path, 

268 ) 

269 self.delegate = _DefaultMessageDelegate(self.request_conn) 

270 

271 return self.delegate.headers_received(start_line, headers) 

272 

273 def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]: 

274 assert self.delegate is not None 

275 return self.delegate.data_received(chunk) 

276 

277 def finish(self) -> None: 

278 assert self.delegate is not None 

279 self.delegate.finish() 

280 

281 def on_connection_close(self) -> None: 

282 assert self.delegate is not None 

283 self.delegate.on_connection_close() 

284 

285 

286class _DefaultMessageDelegate(httputil.HTTPMessageDelegate): 

287 def __init__(self, connection: httputil.HTTPConnection) -> None: 

288 self.connection = connection 

289 

290 def finish(self) -> None: 

291 self.connection.write_headers( 

292 httputil.ResponseStartLine("HTTP/1.1", 404, "Not Found"), 

293 httputil.HTTPHeaders(), 

294 ) 

295 self.connection.finish() 

296 

297 

298# _RuleList can either contain pre-constructed Rules or a sequence of 

299# arguments to be passed to the Rule constructor. 

300_RuleList = Sequence[ 

301 Union[ 

302 "Rule", 

303 List[Any], # Can't do detailed typechecking of lists. 

304 Tuple[Union[str, "Matcher"], Any], 

305 Tuple[Union[str, "Matcher"], Any, Dict[str, Any]], 

306 Tuple[Union[str, "Matcher"], Any, Dict[str, Any], str], 

307 ] 

308] 

309 

310 

311class RuleRouter(Router): 

312 """Rule-based router implementation.""" 

313 

314 def __init__(self, rules: Optional[_RuleList] = None) -> None: 

315 """Constructs a router from an ordered list of rules:: 

316 

317 RuleRouter([ 

318 Rule(PathMatches("/handler"), Target), 

319 # ... more rules 

320 ]) 

321 

322 You can also omit explicit `Rule` constructor and use tuples of arguments:: 

323 

324 RuleRouter([ 

325 (PathMatches("/handler"), Target), 

326 ]) 

327 

328 `PathMatches` is a default matcher, so the example above can be simplified:: 

329 

330 RuleRouter([ 

331 ("/handler", Target), 

332 ]) 

333 

334 In the examples above, ``Target`` can be a nested `Router` instance, an instance of 

335 `~.httputil.HTTPServerConnectionDelegate` or an old-style callable, 

336 accepting a request argument. 

337 

338 :arg rules: a list of `Rule` instances or tuples of `Rule` 

339 constructor arguments. 

340 """ 

341 self.rules = [] # type: List[Rule] 

342 if rules: 

343 self.add_rules(rules) 

344 

345 def add_rules(self, rules: _RuleList) -> None: 

346 """Appends new rules to the router. 

347 

348 :arg rules: a list of Rule instances (or tuples of arguments, which are 

349 passed to Rule constructor). 

350 """ 

351 for rule in rules: 

352 if isinstance(rule, (tuple, list)): 

353 assert len(rule) in (2, 3, 4) 

354 if isinstance(rule[0], basestring_type): 

355 rule = Rule(PathMatches(rule[0]), *rule[1:]) 

356 else: 

357 rule = Rule(*rule) 

358 

359 self.rules.append(self.process_rule(rule)) 

360 

361 def process_rule(self, rule: "Rule") -> "Rule": 

362 """Override this method for additional preprocessing of each rule. 

363 

364 :arg Rule rule: a rule to be processed. 

365 :returns: the same or modified Rule instance. 

366 """ 

367 return rule 

368 

369 def find_handler( 

370 self, request: httputil.HTTPServerRequest, **kwargs: Any 

371 ) -> Optional[httputil.HTTPMessageDelegate]: 

372 for rule in self.rules: 

373 target_params = rule.matcher.match(request) 

374 if target_params is not None: 

375 if rule.target_kwargs: 

376 target_params["target_kwargs"] = rule.target_kwargs 

377 

378 delegate = self.get_target_delegate( 

379 rule.target, request, **target_params 

380 ) 

381 

382 if delegate is not None: 

383 return delegate 

384 

385 return None 

386 

387 def get_target_delegate( 

388 self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any 

389 ) -> Optional[httputil.HTTPMessageDelegate]: 

390 """Returns an instance of `~.httputil.HTTPMessageDelegate` for a 

391 Rule's target. This method is called by `~.find_handler` and can be 

392 extended to provide additional target types. 

393 

394 :arg target: a Rule's target. 

395 :arg httputil.HTTPServerRequest request: current request. 

396 :arg target_params: additional parameters that can be useful 

397 for `~.httputil.HTTPMessageDelegate` creation. 

398 """ 

399 if isinstance(target, Router): 

400 return target.find_handler(request, **target_params) 

401 

402 elif isinstance(target, httputil.HTTPServerConnectionDelegate): 

403 assert request.connection is not None 

404 return target.start_request(request.server_connection, request.connection) 

405 

406 elif callable(target): 

407 assert request.connection is not None 

408 return _CallableAdapter( 

409 partial(target, **target_params), request.connection 

410 ) 

411 

412 return None 

413 

414 

415class ReversibleRuleRouter(ReversibleRouter, RuleRouter): 

416 """A rule-based router that implements ``reverse_url`` method. 

417 

418 Each rule added to this router may have a ``name`` attribute that can be 

419 used to reconstruct an original uri. The actual reconstruction takes place 

420 in a rule's matcher (see `Matcher.reverse`). 

421 """ 

422 

423 def __init__(self, rules: Optional[_RuleList] = None) -> None: 

424 self.named_rules = {} # type: Dict[str, Any] 

425 super().__init__(rules) 

426 

427 def process_rule(self, rule: "Rule") -> "Rule": 

428 rule = super().process_rule(rule) 

429 

430 if rule.name: 

431 if rule.name in self.named_rules: 

432 app_log.warning( 

433 "Multiple handlers named %s; replacing previous value", rule.name 

434 ) 

435 self.named_rules[rule.name] = rule 

436 

437 return rule 

438 

439 def reverse_url(self, name: str, *args: Any) -> Optional[str]: 

440 if name in self.named_rules: 

441 return self.named_rules[name].matcher.reverse(*args) 

442 

443 for rule in self.rules: 

444 if isinstance(rule.target, ReversibleRouter): 

445 reversed_url = rule.target.reverse_url(name, *args) 

446 if reversed_url is not None: 

447 return reversed_url 

448 

449 return None 

450 

451 

452class Rule: 

453 """A routing rule.""" 

454 

455 def __init__( 

456 self, 

457 matcher: "Matcher", 

458 target: Any, 

459 target_kwargs: Optional[Dict[str, Any]] = None, 

460 name: Optional[str] = None, 

461 ) -> None: 

462 """Constructs a Rule instance. 

463 

464 :arg Matcher matcher: a `Matcher` instance used for determining 

465 whether the rule should be considered a match for a specific 

466 request. 

467 :arg target: a Rule's target (typically a ``RequestHandler`` or 

468 `~.httputil.HTTPServerConnectionDelegate` subclass or even a nested `Router`, 

469 depending on routing implementation). 

470 :arg dict target_kwargs: a dict of parameters that can be useful 

471 at the moment of target instantiation (for example, ``status_code`` 

472 for a ``RequestHandler`` subclass). They end up in 

473 ``target_params['target_kwargs']`` of `RuleRouter.get_target_delegate` 

474 method. 

475 :arg str name: the name of the rule that can be used to find it 

476 in `ReversibleRouter.reverse_url` implementation. 

477 """ 

478 if isinstance(target, str): 

479 # import the Module and instantiate the class 

480 # Must be a fully qualified name (module.ClassName) 

481 target = import_object(target) 

482 

483 self.matcher = matcher # type: Matcher 

484 self.target = target 

485 self.target_kwargs = target_kwargs if target_kwargs else {} 

486 self.name = name 

487 

488 def reverse(self, *args: Any) -> Optional[str]: 

489 return self.matcher.reverse(*args) 

490 

491 def __repr__(self) -> str: 

492 return "{}({!r}, {}, kwargs={!r}, name={!r})".format( 

493 self.__class__.__name__, 

494 self.matcher, 

495 self.target, 

496 self.target_kwargs, 

497 self.name, 

498 ) 

499 

500 

501class Matcher: 

502 """Represents a matcher for request features.""" 

503 

504 def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: 

505 """Matches current instance against the request. 

506 

507 :arg httputil.HTTPServerRequest request: current HTTP request 

508 :returns: a dict of parameters to be passed to the target handler 

509 (for example, ``handler_kwargs``, ``path_args``, ``path_kwargs`` 

510 can be passed for proper `~.web.RequestHandler` instantiation). 

511 An empty dict is a valid (and common) return value to indicate a match 

512 when the argument-passing features are not used. 

513 ``None`` must be returned to indicate that there is no match.""" 

514 raise NotImplementedError() 

515 

516 def reverse(self, *args: Any) -> Optional[str]: 

517 """Reconstructs full url from matcher instance and additional arguments.""" 

518 return None 

519 

520 

521class AnyMatches(Matcher): 

522 """Matches any request.""" 

523 

524 def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: 

525 return {} 

526 

527 

528class HostMatches(Matcher): 

529 """Matches requests from hosts specified by ``host_pattern`` regex.""" 

530 

531 def __init__(self, host_pattern: Union[str, Pattern]) -> None: 

532 if isinstance(host_pattern, basestring_type): 

533 if not host_pattern.endswith("$"): 

534 host_pattern += "$" 

535 self.host_pattern = re.compile(host_pattern) 

536 else: 

537 self.host_pattern = host_pattern 

538 

539 def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: 

540 if self.host_pattern.match(request.host_name): 

541 return {} 

542 

543 return None 

544 

545 

546class DefaultHostMatches(Matcher): 

547 """Matches requests from host that is equal to application's default_host. 

548 Always returns no match if ``X-Real-Ip`` header is present. 

549 """ 

550 

551 def __init__(self, application: Any, host_pattern: Pattern) -> None: 

552 self.application = application 

553 self.host_pattern = host_pattern 

554 

555 def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: 

556 # Look for default host if not behind load balancer (for debugging) 

557 if "X-Real-Ip" not in request.headers: 

558 if self.host_pattern.match(self.application.default_host): 

559 return {} 

560 return None 

561 

562 

563class PathMatches(Matcher): 

564 """Matches requests with paths specified by ``path_pattern`` regex.""" 

565 

566 def __init__(self, path_pattern: Union[str, Pattern]) -> None: 

567 if isinstance(path_pattern, basestring_type): 

568 if not path_pattern.endswith("$"): 

569 path_pattern += "$" 

570 self.regex = re.compile(path_pattern) 

571 else: 

572 self.regex = path_pattern 

573 

574 assert len(self.regex.groupindex) in (0, self.regex.groups), ( 

575 "groups in url regexes must either be all named or all " 

576 "positional: %r" % self.regex.pattern 

577 ) 

578 

579 self._path, self._group_count = self._find_groups() 

580 

581 def match(self, request: httputil.HTTPServerRequest) -> Optional[Dict[str, Any]]: 

582 match = self.regex.match(request.path) 

583 if match is None: 

584 return None 

585 if not self.regex.groups: 

586 return {} 

587 

588 path_args = [] # type: List[bytes] 

589 path_kwargs = {} # type: Dict[str, bytes] 

590 

591 # Pass matched groups to the handler. Since 

592 # match.groups() includes both named and 

593 # unnamed groups, we want to use either groups 

594 # or groupdict but not both. 

595 if self.regex.groupindex: 

596 path_kwargs = { 

597 str(k): _unquote_or_none(v) for (k, v) in match.groupdict().items() 

598 } 

599 else: 

600 path_args = [_unquote_or_none(s) for s in match.groups()] 

601 

602 return dict(path_args=path_args, path_kwargs=path_kwargs) 

603 

604 def reverse(self, *args: Any) -> Optional[str]: 

605 if self._path is None: 

606 raise ValueError("Cannot reverse url regex " + self.regex.pattern) 

607 assert len(args) == self._group_count, ( 

608 "required number of arguments " "not found" 

609 ) 

610 if not len(args): 

611 return self._path 

612 converted_args = [] 

613 for a in args: 

614 if not isinstance(a, (unicode_type, bytes)): 

615 a = str(a) 

616 converted_args.append(url_escape(utf8(a), plus=False)) 

617 return self._path % tuple(converted_args) 

618 

619 def _find_groups(self) -> Tuple[Optional[str], Optional[int]]: 

620 """Returns a tuple (reverse string, group count) for a url. 

621 

622 For example: Given the url pattern /([0-9]{4})/([a-z-]+)/, this method 

623 would return ('/%s/%s/', 2). 

624 """ 

625 pattern = self.regex.pattern 

626 if pattern.startswith("^"): 

627 pattern = pattern[1:] 

628 if pattern.endswith("$"): 

629 pattern = pattern[:-1] 

630 

631 if self.regex.groups != pattern.count("("): 

632 # The pattern is too complicated for our simplistic matching, 

633 # so we can't support reversing it. 

634 return None, None 

635 

636 pieces = [] 

637 for fragment in pattern.split("("): 

638 if ")" in fragment: 

639 paren_loc = fragment.index(")") 

640 if paren_loc >= 0: 

641 try: 

642 unescaped_fragment = re_unescape(fragment[paren_loc + 1 :]) 

643 except ValueError: 

644 # If we can't unescape part of it, we can't 

645 # reverse this url. 

646 return (None, None) 

647 pieces.append("%s" + unescaped_fragment) 

648 else: 

649 try: 

650 unescaped_fragment = re_unescape(fragment) 

651 except ValueError: 

652 # If we can't unescape part of it, we can't 

653 # reverse this url. 

654 return (None, None) 

655 pieces.append(unescaped_fragment) 

656 

657 return "".join(pieces), self.regex.groups 

658 

659 

660class URLSpec(Rule): 

661 """Specifies mappings between URLs and handlers. 

662 

663 .. versionchanged: 4.5 

664 `URLSpec` is now a subclass of a `Rule` with `PathMatches` matcher and is preserved for 

665 backwards compatibility. 

666 """ 

667 

668 def __init__( 

669 self, 

670 pattern: Union[str, Pattern], 

671 handler: Any, 

672 kwargs: Optional[Dict[str, Any]] = None, 

673 name: Optional[str] = None, 

674 ) -> None: 

675 """Parameters: 

676 

677 * ``pattern``: Regular expression to be matched. Any capturing 

678 groups in the regex will be passed in to the handler's 

679 get/post/etc methods as arguments (by keyword if named, by 

680 position if unnamed. Named and unnamed capturing groups 

681 may not be mixed in the same rule). 

682 

683 * ``handler``: `~.web.RequestHandler` subclass to be invoked. 

684 

685 * ``kwargs`` (optional): A dictionary of additional arguments 

686 to be passed to the handler's constructor. 

687 

688 * ``name`` (optional): A name for this handler. Used by 

689 `~.web.Application.reverse_url`. 

690 

691 """ 

692 matcher = PathMatches(pattern) 

693 super().__init__(matcher, handler, kwargs, name) 

694 

695 self.regex = matcher.regex 

696 self.handler_class = self.target 

697 self.kwargs = kwargs 

698 

699 def __repr__(self) -> str: 

700 return "{}({!r}, {}, kwargs={!r}, name={!r})".format( 

701 self.__class__.__name__, 

702 self.regex.pattern, 

703 self.handler_class, 

704 self.kwargs, 

705 self.name, 

706 ) 

707 

708 

709@overload 

710def _unquote_or_none(s: str) -> bytes: 

711 pass 

712 

713 

714@overload # noqa: F811 

715def _unquote_or_none(s: None) -> None: 

716 pass 

717 

718 

719def _unquote_or_none(s: Optional[str]) -> Optional[bytes]: # noqa: F811 

720 """None-safe wrapper around url_unescape to handle unmatched optional 

721 groups correctly. 

722 

723 Note that args are passed as bytes so the handler can decide what 

724 encoding to use. 

725 """ 

726 if s is None: 

727 return s 

728 return url_unescape(s, encoding=None, plus=False)