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

371 statements  

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

1import ast 

2import re 

3import typing as t 

4from dataclasses import dataclass 

5from string import Template 

6from types import CodeType 

7 

8from .._internal import _to_bytes 

9from ..urls import url_encode 

10from ..urls import url_quote 

11from .converters import ValidationError 

12 

13if t.TYPE_CHECKING: 

14 from .converters import BaseConverter 

15 from .map import Map 

16 

17 

18class Weighting(t.NamedTuple): 

19 number_static_weights: int 

20 static_weights: t.List[t.Tuple[int, int]] 

21 number_argument_weights: int 

22 argument_weights: t.List[int] 

23 

24 

25@dataclass 

26class RulePart: 

27 """A part of a rule. 

28 

29 Rules can be represented by parts as delimited by `/` with 

30 instances of this class representing those parts. The *content* is 

31 either the raw content if *static* or a regex string to match 

32 against. The *weight* can be used to order parts when matching. 

33 

34 """ 

35 

36 content: str 

37 final: bool 

38 static: bool 

39 suffixed: bool 

40 weight: Weighting 

41 

42 

43_part_re = re.compile( 

44 r""" 

45 (?: 

46 (?P<slash>\/) # a slash 

47 | 

48 (?P<static>[^<\/]+) # static rule data 

49 | 

50 (?: 

51 < 

52 (?: 

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

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

55 \: # variable delimiter 

56 )? 

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

58 > 

59 ) 

60 ) 

61 """, 

62 re.VERBOSE, 

63) 

64 

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

66_converter_args_re = re.compile( 

67 r""" 

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

69 (?P<value> 

70 True|False| 

71 \d+.\d+| 

72 \d+.| 

73 \d+| 

74 [\w\d_.]+| 

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

76 )\s*, 

77 """, 

78 re.VERBOSE, 

79) 

80 

81 

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

83 

84 

85def _find(value: str, target: str, pos: int) -> int: 

86 """Find the *target* in *value* after *pos*. 

87 

88 Returns the *value* length if *target* isn't found. 

89 """ 

90 try: 

91 return value.index(target, pos) 

92 except ValueError: 

93 return len(value) 

94 

95 

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

97 if value in _PYTHON_CONSTANTS: 

98 return _PYTHON_CONSTANTS[value] 

99 for convert in int, float: 

100 try: 

101 return convert(value) # type: ignore 

102 except ValueError: 

103 pass 

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

105 value = value[1:-1] 

106 return str(value) 

107 

108 

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

110 argstr += "," 

111 args = [] 

112 kwargs = {} 

113 

114 for item in _converter_args_re.finditer(argstr): 

115 value = item.group("stringval") 

116 if value is None: 

117 value = item.group("value") 

118 value = _pythonize(value) 

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

120 args.append(value) 

121 else: 

122 name = item.group("name") 

123 kwargs[name] = value 

124 

125 return tuple(args), kwargs 

126 

127 

128class RuleFactory: 

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

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

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

132 """ 

133 

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

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

136 an iterable of rules.""" 

137 raise NotImplementedError() 

138 

139 

140class Subdomain(RuleFactory): 

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

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

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

144 

145 url_map = Map([ 

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

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

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

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

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

151 ]) 

152 ]) 

153 

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

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

156 for the current request. 

157 """ 

158 

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

160 self.subdomain = subdomain 

161 self.rules = rules 

162 

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

164 for rulefactory in self.rules: 

165 for rule in rulefactory.get_rules(map): 

166 rule = rule.empty() 

167 rule.subdomain = self.subdomain 

168 yield rule 

169 

170 

171class Submount(RuleFactory): 

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

173 

174 url_map = Map([ 

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

176 Submount('/blog', [ 

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

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

179 ]) 

180 ]) 

181 

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

183 """ 

184 

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

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

187 self.rules = rules 

188 

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

190 for rulefactory in self.rules: 

191 for rule in rulefactory.get_rules(map): 

192 rule = rule.empty() 

193 rule.rule = self.path + rule.rule 

194 yield rule 

195 

196 

197class EndpointPrefix(RuleFactory): 

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

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

200 

201 url_map = Map([ 

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

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

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

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

206 ])]) 

207 ]) 

208 """ 

209 

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

211 self.prefix = prefix 

212 self.rules = rules 

213 

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

215 for rulefactory in self.rules: 

216 for rule in rulefactory.get_rules(map): 

217 rule = rule.empty() 

218 rule.endpoint = self.prefix + rule.endpoint 

219 yield rule 

220 

221 

222class RuleTemplate: 

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

224 the endpoint, rule, defaults or subdomain sections. 

225 

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

227 

228 from werkzeug.routing import Map, Rule, RuleTemplate 

229 

230 resource = RuleTemplate([ 

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

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

233 ]) 

234 

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

236 

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

238 replace the placeholders in all the string parameters. 

239 """ 

240 

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

242 self.rules = list(rules) 

243 

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

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

246 

247 

248class RuleTemplateFactory(RuleFactory): 

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

250 `RuleTemplate` internally. 

251 

252 :internal: 

253 """ 

254 

255 def __init__( 

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

257 ) -> None: 

258 self.rules = rules 

259 self.context = context 

260 

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

262 for rulefactory in self.rules: 

263 for rule in rulefactory.get_rules(map): 

264 new_defaults = subdomain = None 

265 if rule.defaults: 

266 new_defaults = {} 

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

268 if isinstance(value, str): 

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

270 new_defaults[key] = value 

271 if rule.subdomain is not None: 

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

273 new_endpoint = rule.endpoint 

274 if isinstance(new_endpoint, str): 

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

276 yield Rule( 

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

278 new_defaults, 

279 subdomain, 

280 rule.methods, 

281 rule.build_only, 

282 new_endpoint, 

283 rule.strict_slashes, 

284 ) 

285 

286 

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

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

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

290 if isinstance(tree, ast.Expr): 

291 tree = tree.value # type: ignore 

292 for node in ast.walk(tree): 

293 if isinstance(node, ast.Name): 

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

295 return tree 

296 

297 

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

299_IF_KWARGS_URL_ENCODE_CODE = """\ 

300if kwargs: 

301 params = self._encode_query_vars(kwargs) 

302 q = "?" if params else "" 

303else: 

304 q = params = "" 

305""" 

306_IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE) 

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

308 

309 

310class Rule(RuleFactory): 

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

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

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

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

315 

316 `string` 

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

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

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

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

321 

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

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

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

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

326 

327 The converters are defined on the `Map`. 

328 

329 `endpoint` 

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

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

332 because the endpoint is used for URL generation. 

333 

334 `defaults` 

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

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

337 

338 url_map = Map([ 

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

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

341 ]) 

342 

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

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

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

346 generation. 

347 

348 `subdomain` 

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

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

351 not bound to a subdomain this feature is disabled. 

352 

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

354 and all subdomains are forwarded to your application:: 

355 

356 url_map = Map([ 

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

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

359 ]) 

360 

361 `methods` 

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

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

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

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

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

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

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

369 

370 `strict_slashes` 

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

372 not specified the `Map` setting is used. 

373 

374 `merge_slashes` 

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

376 

377 `build_only` 

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

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

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

381 

382 `redirect_to` 

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

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

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

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

387 rule syntax:: 

388 

389 def foo_with_slug(adapter, id): 

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

391 # course has nothing to do with werkzeug. 

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

393 

394 url_map = Map([ 

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

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

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

398 ]) 

399 

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

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

402 

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

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

405 really mean root of that domain. 

406 

407 `alias` 

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

409 endpoint and arguments. 

410 

411 `host` 

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

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

414 that the subdomain feature is disabled. 

415 

416 `websocket` 

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

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

419 requests. 

420 

421 .. versionchanged:: 2.1 

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

423 servers, are considered when routing instead of terminating the 

424 match early. 

425 

426 .. versionadded:: 1.0 

427 Added ``websocket``. 

428 

429 .. versionadded:: 1.0 

430 Added ``merge_slashes``. 

431 

432 .. versionadded:: 0.7 

433 Added ``alias`` and ``host``. 

434 

435 .. versionchanged:: 0.6.1 

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

437 """ 

438 

439 def __init__( 

440 self, 

441 string: str, 

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

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

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

445 build_only: bool = False, 

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

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

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

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

450 alias: bool = False, 

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

452 websocket: bool = False, 

453 ) -> None: 

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

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

456 self.rule = string 

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

458 self.is_branch = string.endswith("/") 

459 

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

461 self.strict_slashes = strict_slashes 

462 self.merge_slashes = merge_slashes 

463 self.subdomain = subdomain 

464 self.host = host 

465 self.defaults = defaults 

466 self.build_only = build_only 

467 self.alias = alias 

468 self.websocket = websocket 

469 

470 if methods is not None: 

471 if isinstance(methods, str): 

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

473 

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

475 

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

477 methods.add("HEAD") 

478 

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

480 raise ValueError( 

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

482 ) 

483 

484 self.methods = methods 

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

486 self.redirect_to = redirect_to 

487 

488 if defaults: 

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

490 else: 

491 self.arguments = set() 

492 

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

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

495 self._parts: t.List[RulePart] = [] 

496 

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

498 """ 

499 Return an unbound copy of this rule. 

500 

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

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

503 provided to the new copy. 

504 """ 

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

506 

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

508 """ 

509 Provides kwargs for instantiating empty copy with empty() 

510 

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

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

513 has custom keyword arguments that are needed at instantiation. 

514 

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

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

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

518 """ 

519 defaults = None 

520 if self.defaults: 

521 defaults = dict(self.defaults) 

522 return dict( 

523 defaults=defaults, 

524 subdomain=self.subdomain, 

525 methods=self.methods, 

526 build_only=self.build_only, 

527 endpoint=self.endpoint, 

528 strict_slashes=self.strict_slashes, 

529 redirect_to=self.redirect_to, 

530 alias=self.alias, 

531 host=self.host, 

532 ) 

533 

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

535 yield self 

536 

537 def refresh(self) -> None: 

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

539 rule in place. 

540 

541 :internal: 

542 """ 

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

544 

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

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

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

548 

549 :internal: 

550 """ 

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

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

553 self.map = map 

554 if self.strict_slashes is None: 

555 self.strict_slashes = map.strict_slashes 

556 if self.merge_slashes is None: 

557 self.merge_slashes = map.merge_slashes 

558 if self.subdomain is None: 

559 self.subdomain = map.default_subdomain 

560 self.compile() 

561 

562 def get_converter( 

563 self, 

564 variable_name: str, 

565 converter_name: str, 

566 args: t.Tuple, 

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

568 ) -> "BaseConverter": 

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

570 

571 .. versionadded:: 0.9 

572 """ 

573 if converter_name not in self.map.converters: 

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

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

576 

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

578 return url_encode( 

579 query_vars, 

580 charset=self.map.charset, 

581 sort=self.map.sort_parameters, 

582 key=self.map.sort_key, 

583 ) 

584 

585 def _parse_rule(self, rule: str) -> t.Iterable[RulePart]: 

586 content = "" 

587 static = True 

588 argument_weights = [] 

589 static_weights: t.List[t.Tuple[int, int]] = [] 

590 final = False 

591 

592 pos = 0 

593 while pos < len(rule): 

594 match = _part_re.match(rule, pos) 

595 if match is None: 

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

597 

598 data = match.groupdict() 

599 if data["static"] is not None: 

600 static_weights.append((len(static_weights), -len(data["static"]))) 

601 self._trace.append((False, data["static"])) 

602 content += data["static"] if static else re.escape(data["static"]) 

603 

604 if data["variable"] is not None: 

605 if static: 

606 # Switching content to represent regex, hence the need to escape 

607 content = re.escape(content) 

608 static = False 

609 c_args, c_kwargs = parse_converter_args(data["arguments"] or "") 

610 convobj = self.get_converter( 

611 data["variable"], data["converter"] or "default", c_args, c_kwargs 

612 ) 

613 self._converters[data["variable"]] = convobj 

614 self.arguments.add(data["variable"]) 

615 if not convobj.part_isolating: 

616 final = True 

617 content += f"({convobj.regex})" 

618 argument_weights.append(convobj.weight) 

619 self._trace.append((True, data["variable"])) 

620 

621 if data["slash"] is not None: 

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

623 if final: 

624 content += "/" 

625 else: 

626 if not static: 

627 content += r"\Z" 

628 weight = Weighting( 

629 -len(static_weights), 

630 static_weights, 

631 -len(argument_weights), 

632 argument_weights, 

633 ) 

634 yield RulePart( 

635 content=content, 

636 final=final, 

637 static=static, 

638 suffixed=False, 

639 weight=weight, 

640 ) 

641 content = "" 

642 static = True 

643 argument_weights = [] 

644 static_weights = [] 

645 final = False 

646 

647 pos = match.end() 

648 

649 suffixed = False 

650 if final and content[-1] == "/": 

651 # If a converter is part_isolating=False (matches slashes) and ends with a 

652 # slash, augment the regex to support slash redirects. 

653 suffixed = True 

654 content = content[:-1] + "(?<!/)(/?)" 

655 if not static: 

656 content += r"\Z" 

657 weight = Weighting( 

658 -len(static_weights), 

659 static_weights, 

660 -len(argument_weights), 

661 argument_weights, 

662 ) 

663 yield RulePart( 

664 content=content, 

665 final=final, 

666 static=static, 

667 suffixed=suffixed, 

668 weight=weight, 

669 ) 

670 if suffixed: 

671 yield RulePart( 

672 content="", final=False, static=True, suffixed=False, weight=weight 

673 ) 

674 

675 def compile(self) -> None: 

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

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

678 

679 if self.map.host_matching: 

680 domain_rule = self.host or "" 

681 else: 

682 domain_rule = self.subdomain or "" 

683 self._parts = [] 

684 self._trace = [] 

685 self._converters = {} 

686 if domain_rule == "": 

687 self._parts = [ 

688 RulePart( 

689 content="", 

690 final=False, 

691 static=True, 

692 suffixed=False, 

693 weight=Weighting(0, [], 0, []), 

694 ) 

695 ] 

696 else: 

697 self._parts.extend(self._parse_rule(domain_rule)) 

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

699 rule = self.rule 

700 if self.merge_slashes: 

701 rule = re.sub("/{2,}?", "/", self.rule) 

702 self._parts.extend(self._parse_rule(rule)) 

703 

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

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

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

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

708 

709 @staticmethod 

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

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

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

713 exec(code, globs, locs) 

714 return locs[name] # type: ignore 

715 

716 def _compile_builder( 

717 self, append_unknown: bool = True 

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

719 defaults = self.defaults or {} 

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

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

722 

723 opl = dom_ops 

724 for is_dynamic, data in self._trace: 

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

726 opl = url_ops 

727 continue 

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

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

730 # resolve it to a constant ahead of time 

731 if is_dynamic and data in defaults: 

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

733 opl.append((False, data)) 

734 elif not is_dynamic: 

735 opl.append( 

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

737 ) 

738 else: 

739 opl.append((True, data)) 

740 

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

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

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

744 return ret 

745 

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

747 parts = [ 

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

749 for is_dynamic, elem in ops 

750 ] 

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

752 # constant fold 

753 ret = [parts[0]] 

754 for p in parts[1:]: 

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

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

757 else: 

758 ret.append(p) 

759 return ret 

760 

761 dom_parts = _parts(dom_ops) 

762 url_parts = _parts(url_ops) 

763 if not append_unknown: 

764 body = [] 

765 else: 

766 body = [_IF_KWARGS_URL_ENCODE_AST] 

767 url_parts.extend(_URL_ENCODE_AST_NAMES) 

768 

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

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

771 return parts[0] 

772 return ast.JoinedStr(parts) 

773 

774 body.append( 

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

776 ) 

777 

778 pargs = [ 

779 elem 

780 for is_dynamic, elem in dom_ops + url_ops 

781 if is_dynamic and elem not in defaults 

782 ] 

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

784 

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

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

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

788 for arg in pargs + kargs: 

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

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

791 for _ in kargs: 

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

793 func_ast.body = body 

794 

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

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

797 module = ast.parse("") 

798 module.body = [func_ast] 

799 

800 # mark everything as on line 1, offset 0 

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

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

803 for node in ast.walk(module): 

804 if "lineno" in node._attributes: 

805 node.lineno = 1 

806 if "end_lineno" in node._attributes: 

807 node.end_lineno = node.lineno # type: ignore[attr-defined] 

808 if "col_offset" in node._attributes: 

809 node.col_offset = 0 

810 if "end_col_offset" in node._attributes: 

811 node.end_col_offset = node.col_offset # type: ignore[attr-defined] 

812 

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

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

815 

816 def build( 

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

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

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

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

821 

822 :internal: 

823 """ 

824 try: 

825 if append_unknown: 

826 return self._build_unknown(**values) 

827 else: 

828 return self._build(**values) 

829 except ValidationError: 

830 return None 

831 

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

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

834 

835 :internal: 

836 """ 

837 return bool( 

838 not self.build_only 

839 and self.defaults 

840 and self.endpoint == rule.endpoint 

841 and self != rule 

842 and self.arguments == rule.arguments 

843 ) 

844 

845 def suitable_for( 

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

847 ) -> bool: 

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

849 

850 :internal: 

851 """ 

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

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

854 if ( 

855 method is not None 

856 and self.methods is not None 

857 and method not in self.methods 

858 ): 

859 return False 

860 

861 defaults = self.defaults or () 

862 

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

864 # the value dictionary otherwise it's not suitable 

865 for key in self.arguments: 

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

867 return False 

868 

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

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

871 if defaults: 

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

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

874 return False 

875 

876 return True 

877 

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

879 """The build compare key for sorting. 

880 

881 :internal: 

882 """ 

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

884 

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

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

887 

888 __hash__ = None # type: ignore 

889 

890 def __str__(self) -> str: 

891 return self.rule 

892 

893 def __repr__(self) -> str: 

894 if self.map is None: 

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

896 parts = [] 

897 for is_dynamic, data in self._trace: 

898 if is_dynamic: 

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

900 else: 

901 parts.append(data) 

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

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

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