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

218 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-01 06:54 +0000

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 Any, Union, Optional, Awaitable, List, Dict, Pattern, Tuple, overload 

188 

189 

190class Router(httputil.HTTPServerConnectionDelegate): 

191 """Abstract router interface.""" 

192 

193 def find_handler( 

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

195 ) -> Optional[httputil.HTTPMessageDelegate]: 

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

197 that can serve the request. 

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

199 

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

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

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

203 process the request. 

204 """ 

205 raise NotImplementedError() 

206 

207 def start_request( 

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

209 ) -> httputil.HTTPMessageDelegate: 

210 return _RoutingDelegate(self, server_conn, request_conn) 

211 

212 

213class ReversibleRouter(Router): 

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

215 and support reversing them to original urls. 

216 """ 

217 

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

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

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

221 

222 :arg str name: route name. 

223 :arg args: url parameters. 

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

225 """ 

226 raise NotImplementedError() 

227 

228 

229class _RoutingDelegate(httputil.HTTPMessageDelegate): 

230 def __init__( 

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

232 ) -> None: 

233 self.server_conn = server_conn 

234 self.request_conn = request_conn 

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

236 self.router = router # type: Router 

237 

238 def headers_received( 

239 self, 

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

241 headers: httputil.HTTPHeaders, 

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

243 assert isinstance(start_line, httputil.RequestStartLine) 

244 request = httputil.HTTPServerRequest( 

245 connection=self.request_conn, 

246 server_connection=self.server_conn, 

247 start_line=start_line, 

248 headers=headers, 

249 ) 

250 

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

252 if self.delegate is None: 

253 app_log.debug( 

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

255 start_line.method, 

256 start_line.path, 

257 ) 

258 self.delegate = _DefaultMessageDelegate(self.request_conn) 

259 

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

261 

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

263 assert self.delegate is not None 

264 return self.delegate.data_received(chunk) 

265 

266 def finish(self) -> None: 

267 assert self.delegate is not None 

268 self.delegate.finish() 

269 

270 def on_connection_close(self) -> None: 

271 assert self.delegate is not None 

272 self.delegate.on_connection_close() 

273 

274 

275class _DefaultMessageDelegate(httputil.HTTPMessageDelegate): 

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

277 self.connection = connection 

278 

279 def finish(self) -> None: 

280 self.connection.write_headers( 

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

282 httputil.HTTPHeaders(), 

283 ) 

284 self.connection.finish() 

285 

286 

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

288# arguments to be passed to the Rule constructor. 

289_RuleList = List[ 

290 Union[ 

291 "Rule", 

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

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

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

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

296 ] 

297] 

298 

299 

300class RuleRouter(Router): 

301 """Rule-based router implementation.""" 

302 

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

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

305 

306 RuleRouter([ 

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

308 # ... more rules 

309 ]) 

310 

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

312 

313 RuleRouter([ 

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

315 ]) 

316 

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

318 

319 RuleRouter([ 

320 ("/handler", Target), 

321 ]) 

322 

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

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

325 accepting a request argument. 

326 

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

328 constructor arguments. 

329 """ 

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

331 if rules: 

332 self.add_rules(rules) 

333 

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

335 """Appends new rules to the router. 

336 

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

338 passed to Rule constructor). 

339 """ 

340 for rule in rules: 

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

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

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

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

345 else: 

346 rule = Rule(*rule) 

347 

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

349 

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

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

352 

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

354 :returns: the same or modified Rule instance. 

355 """ 

356 return rule 

357 

358 def find_handler( 

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

360 ) -> Optional[httputil.HTTPMessageDelegate]: 

361 for rule in self.rules: 

362 target_params = rule.matcher.match(request) 

363 if target_params is not None: 

364 if rule.target_kwargs: 

365 target_params["target_kwargs"] = rule.target_kwargs 

366 

367 delegate = self.get_target_delegate( 

368 rule.target, request, **target_params 

369 ) 

370 

371 if delegate is not None: 

372 return delegate 

373 

374 return None 

375 

376 def get_target_delegate( 

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

378 ) -> Optional[httputil.HTTPMessageDelegate]: 

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

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

381 extended to provide additional target types. 

382 

383 :arg target: a Rule's target. 

384 :arg httputil.HTTPServerRequest request: current request. 

385 :arg target_params: additional parameters that can be useful 

386 for `~.httputil.HTTPMessageDelegate` creation. 

387 """ 

388 if isinstance(target, Router): 

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

390 

391 elif isinstance(target, httputil.HTTPServerConnectionDelegate): 

392 assert request.connection is not None 

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

394 

395 elif callable(target): 

396 assert request.connection is not None 

397 return _CallableAdapter( 

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

399 ) 

400 

401 return None 

402 

403 

404class ReversibleRuleRouter(ReversibleRouter, RuleRouter): 

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

406 

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

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

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

410 """ 

411 

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

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

414 super().__init__(rules) 

415 

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

417 rule = super().process_rule(rule) 

418 

419 if rule.name: 

420 if rule.name in self.named_rules: 

421 app_log.warning( 

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

423 ) 

424 self.named_rules[rule.name] = rule 

425 

426 return rule 

427 

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

429 if name in self.named_rules: 

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

431 

432 for rule in self.rules: 

433 if isinstance(rule.target, ReversibleRouter): 

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

435 if reversed_url is not None: 

436 return reversed_url 

437 

438 return None 

439 

440 

441class Rule(object): 

442 """A routing rule.""" 

443 

444 def __init__( 

445 self, 

446 matcher: "Matcher", 

447 target: Any, 

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

449 name: Optional[str] = None, 

450 ) -> None: 

451 """Constructs a Rule instance. 

452 

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

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

455 request. 

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

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

458 depending on routing implementation). 

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

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

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

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

463 method. 

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

465 in `ReversibleRouter.reverse_url` implementation. 

466 """ 

467 if isinstance(target, str): 

468 # import the Module and instantiate the class 

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

470 target = import_object(target) 

471 

472 self.matcher = matcher # type: Matcher 

473 self.target = target 

474 self.target_kwargs = target_kwargs if target_kwargs else {} 

475 self.name = name 

476 

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

478 return self.matcher.reverse(*args) 

479 

480 def __repr__(self) -> str: 

481 return "%s(%r, %s, kwargs=%r, name=%r)" % ( 

482 self.__class__.__name__, 

483 self.matcher, 

484 self.target, 

485 self.target_kwargs, 

486 self.name, 

487 ) 

488 

489 

490class Matcher(object): 

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

492 

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

494 """Matches current instance against the request. 

495 

496 :arg httputil.HTTPServerRequest request: current HTTP request 

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

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

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

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

501 when the argument-passing features are not used. 

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

503 raise NotImplementedError() 

504 

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

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

507 return None 

508 

509 

510class AnyMatches(Matcher): 

511 """Matches any request.""" 

512 

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

514 return {} 

515 

516 

517class HostMatches(Matcher): 

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

519 

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

521 if isinstance(host_pattern, basestring_type): 

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

523 host_pattern += "$" 

524 self.host_pattern = re.compile(host_pattern) 

525 else: 

526 self.host_pattern = host_pattern 

527 

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

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

530 return {} 

531 

532 return None 

533 

534 

535class DefaultHostMatches(Matcher): 

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

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

538 """ 

539 

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

541 self.application = application 

542 self.host_pattern = host_pattern 

543 

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

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

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

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

548 return {} 

549 return None 

550 

551 

552class PathMatches(Matcher): 

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

554 

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

556 if isinstance(path_pattern, basestring_type): 

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

558 path_pattern += "$" 

559 self.regex = re.compile(path_pattern) 

560 else: 

561 self.regex = path_pattern 

562 

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

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

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

566 ) 

567 

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

569 

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

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

572 if match is None: 

573 return None 

574 if not self.regex.groups: 

575 return {} 

576 

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

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

579 

580 # Pass matched groups to the handler. Since 

581 # match.groups() includes both named and 

582 # unnamed groups, we want to use either groups 

583 # or groupdict but not both. 

584 if self.regex.groupindex: 

585 path_kwargs = dict( 

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

587 ) 

588 else: 

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

590 

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

592 

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

594 if self._path is None: 

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

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

597 "required number of arguments " "not found" 

598 ) 

599 if not len(args): 

600 return self._path 

601 converted_args = [] 

602 for a in args: 

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

604 a = str(a) 

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

606 return self._path % tuple(converted_args) 

607 

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

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

610 

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

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

613 """ 

614 pattern = self.regex.pattern 

615 if pattern.startswith("^"): 

616 pattern = pattern[1:] 

617 if pattern.endswith("$"): 

618 pattern = pattern[:-1] 

619 

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

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

622 # so we can't support reversing it. 

623 return None, None 

624 

625 pieces = [] 

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

627 if ")" in fragment: 

628 paren_loc = fragment.index(")") 

629 if paren_loc >= 0: 

630 try: 

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

632 except ValueError: 

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

634 # reverse this url. 

635 return (None, None) 

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

637 else: 

638 try: 

639 unescaped_fragment = re_unescape(fragment) 

640 except ValueError: 

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

642 # reverse this url. 

643 return (None, None) 

644 pieces.append(unescaped_fragment) 

645 

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

647 

648 

649class URLSpec(Rule): 

650 """Specifies mappings between URLs and handlers. 

651 

652 .. versionchanged: 4.5 

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

654 backwards compatibility. 

655 """ 

656 

657 def __init__( 

658 self, 

659 pattern: Union[str, Pattern], 

660 handler: Any, 

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

662 name: Optional[str] = None, 

663 ) -> None: 

664 """Parameters: 

665 

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

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

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

669 position if unnamed. Named and unnamed capturing groups 

670 may not be mixed in the same rule). 

671 

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

673 

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

675 to be passed to the handler's constructor. 

676 

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

678 `~.web.Application.reverse_url`. 

679 

680 """ 

681 matcher = PathMatches(pattern) 

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

683 

684 self.regex = matcher.regex 

685 self.handler_class = self.target 

686 self.kwargs = kwargs 

687 

688 def __repr__(self) -> str: 

689 return "%s(%r, %s, kwargs=%r, name=%r)" % ( 

690 self.__class__.__name__, 

691 self.regex.pattern, 

692 self.handler_class, 

693 self.kwargs, 

694 self.name, 

695 ) 

696 

697 

698@overload 

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

700 pass 

701 

702 

703@overload # noqa: F811 

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

705 pass 

706 

707 

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

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

710 groups correctly. 

711 

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

713 encoding to use. 

714 """ 

715 if s is None: 

716 return s 

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