jinja2.utils
1import enum 2import json 3import os 4import re 5import typing as t 6from collections import abc 7from collections import deque 8from random import choice 9from random import randrange 10from threading import Lock 11from types import CodeType 12from urllib.parse import quote_from_bytes 13 14import markupsafe 15 16if t.TYPE_CHECKING: 17 import typing_extensions as te 18 19F = t.TypeVar("F", bound=t.Callable[..., t.Any]) 20 21 22class _MissingType: 23 def __repr__(self) -> str: 24 return "missing" 25 26 def __reduce__(self) -> str: 27 return "missing" 28 29 30missing: t.Any = _MissingType() 31"""Special singleton representing missing values for the runtime.""" 32 33internal_code: t.MutableSet[CodeType] = set() 34 35concat = "".join 36 37 38def pass_context(f: F) -> F: 39 """Pass the :class:`~jinja2.runtime.Context` as the first argument 40 to the decorated function when called while rendering a template. 41 42 Can be used on functions, filters, and tests. 43 44 If only ``Context.eval_context`` is needed, use 45 :func:`pass_eval_context`. If only ``Context.environment`` is 46 needed, use :func:`pass_environment`. 47 48 .. versionadded:: 3.0.0 49 Replaces ``contextfunction`` and ``contextfilter``. 50 """ 51 f.jinja_pass_arg = _PassArg.context # type: ignore 52 return f 53 54 55def pass_eval_context(f: F) -> F: 56 """Pass the :class:`~jinja2.nodes.EvalContext` as the first argument 57 to the decorated function when called while rendering a template. 58 See :ref:`eval-context`. 59 60 Can be used on functions, filters, and tests. 61 62 If only ``EvalContext.environment`` is needed, use 63 :func:`pass_environment`. 64 65 .. versionadded:: 3.0.0 66 Replaces ``evalcontextfunction`` and ``evalcontextfilter``. 67 """ 68 f.jinja_pass_arg = _PassArg.eval_context # type: ignore 69 return f 70 71 72def pass_environment(f: F) -> F: 73 """Pass the :class:`~jinja2.Environment` as the first argument to 74 the decorated function when called while rendering a template. 75 76 Can be used on functions, filters, and tests. 77 78 .. versionadded:: 3.0.0 79 Replaces ``environmentfunction`` and ``environmentfilter``. 80 """ 81 f.jinja_pass_arg = _PassArg.environment # type: ignore 82 return f 83 84 85class _PassArg(enum.Enum): 86 context = enum.auto() 87 eval_context = enum.auto() 88 environment = enum.auto() 89 90 @classmethod 91 def from_obj(cls, obj: F) -> t.Optional["_PassArg"]: 92 if hasattr(obj, "jinja_pass_arg"): 93 return obj.jinja_pass_arg # type: ignore 94 95 return None 96 97 98def internalcode(f: F) -> F: 99 """Marks the function as internally used""" 100 internal_code.add(f.__code__) 101 return f 102 103 104def is_undefined(obj: t.Any) -> bool: 105 """Check if the object passed is undefined. This does nothing more than 106 performing an instance check against :class:`Undefined` but looks nicer. 107 This can be used for custom filters or tests that want to react to 108 undefined variables. For example a custom default filter can look like 109 this:: 110 111 def default(var, default=''): 112 if is_undefined(var): 113 return default 114 return var 115 """ 116 from .runtime import Undefined 117 118 return isinstance(obj, Undefined) 119 120 121def consume(iterable: t.Iterable[t.Any]) -> None: 122 """Consumes an iterable without doing anything with it.""" 123 for _ in iterable: 124 pass 125 126 127def clear_caches() -> None: 128 """Jinja keeps internal caches for environments and lexers. These are 129 used so that Jinja doesn't have to recreate environments and lexers all 130 the time. Normally you don't have to care about that but if you are 131 measuring memory consumption you may want to clean the caches. 132 """ 133 from .environment import get_spontaneous_environment 134 from .lexer import _lexer_cache 135 136 get_spontaneous_environment.cache_clear() 137 _lexer_cache.clear() 138 139 140def import_string(import_name: str, silent: bool = False) -> t.Any: 141 """Imports an object based on a string. This is useful if you want to 142 use import paths as endpoints or something similar. An import path can 143 be specified either in dotted notation (``xml.sax.saxutils.escape``) 144 or with a colon as object delimiter (``xml.sax.saxutils:escape``). 145 146 If the `silent` is True the return value will be `None` if the import 147 fails. 148 149 :return: imported object 150 """ 151 try: 152 if ":" in import_name: 153 module, obj = import_name.split(":", 1) 154 elif "." in import_name: 155 module, _, obj = import_name.rpartition(".") 156 else: 157 return __import__(import_name) 158 return getattr(__import__(module, None, None, [obj]), obj) 159 except (ImportError, AttributeError): 160 if not silent: 161 raise 162 163 164def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO[t.Any]]: 165 """Returns a file descriptor for the filename if that file exists, 166 otherwise ``None``. 167 """ 168 if not os.path.isfile(filename): 169 return None 170 171 return open(filename, mode) 172 173 174def object_type_repr(obj: t.Any) -> str: 175 """Returns the name of the object's type. For some recognized 176 singletons the name of the object is returned instead. (For 177 example for `None` and `Ellipsis`). 178 """ 179 if obj is None: 180 return "None" 181 elif obj is Ellipsis: 182 return "Ellipsis" 183 184 cls = type(obj) 185 186 if cls.__module__ == "builtins": 187 return f"{cls.__name__} object" 188 189 return f"{cls.__module__}.{cls.__name__} object" 190 191 192def pformat(obj: t.Any) -> str: 193 """Format an object using :func:`pprint.pformat`.""" 194 from pprint import pformat 195 196 return pformat(obj) 197 198 199_http_re = re.compile( 200 r""" 201 ^ 202 ( 203 (https?://|www\.) # scheme or www 204 (([\w%-]+\.)+)? # subdomain 205 ( 206 [a-z]{2,63} # basic tld 207 | 208 xn--[\w%]{2,59} # idna tld 209 ) 210 | 211 ([\w%-]{2,63}\.)+ # basic domain 212 (com|net|int|edu|gov|org|info|mil) # basic tld 213 | 214 (https?://) # scheme 215 ( 216 (([\d]{1,3})(\.[\d]{1,3}){3}) # IPv4 217 | 218 (\[([\da-f]{0,4}:){2}([\da-f]{0,4}:?){1,6}]) # IPv6 219 ) 220 ) 221 (?::[\d]{1,5})? # port 222 (?:[/?#]\S*)? # path, query, and fragment 223 $ 224 """, 225 re.IGNORECASE | re.VERBOSE, 226) 227_email_re = re.compile(r"^\S+@\w[\w.-]*\.\w+$") 228 229 230def urlize( 231 text: str, 232 trim_url_limit: t.Optional[int] = None, 233 rel: t.Optional[str] = None, 234 target: t.Optional[str] = None, 235 extra_schemes: t.Optional[t.Iterable[str]] = None, 236) -> str: 237 """Convert URLs in text into clickable links. 238 239 This may not recognize links in some situations. Usually, a more 240 comprehensive formatter, such as a Markdown library, is a better 241 choice. 242 243 Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email 244 addresses. Links with trailing punctuation (periods, commas, closing 245 parentheses) and leading punctuation (opening parentheses) are 246 recognized excluding the punctuation. Email addresses that include 247 header fields are not recognized (for example, 248 ``mailto:address@example.com?cc=copy@example.com``). 249 250 :param text: Original text containing URLs to link. 251 :param trim_url_limit: Shorten displayed URL values to this length. 252 :param target: Add the ``target`` attribute to links. 253 :param rel: Add the ``rel`` attribute to links. 254 :param extra_schemes: Recognize URLs that start with these schemes 255 in addition to the default behavior. 256 257 .. versionchanged:: 3.0 258 The ``extra_schemes`` parameter was added. 259 260 .. versionchanged:: 3.0 261 Generate ``https://`` links for URLs without a scheme. 262 263 .. versionchanged:: 3.0 264 The parsing rules were updated. Recognize email addresses with 265 or without the ``mailto:`` scheme. Validate IP addresses. Ignore 266 parentheses and brackets in more cases. 267 """ 268 if trim_url_limit is not None: 269 270 def trim_url(x: str) -> str: 271 if len(x) > trim_url_limit: 272 return f"{x[:trim_url_limit]}..." 273 274 return x 275 276 else: 277 278 def trim_url(x: str) -> str: 279 return x 280 281 words = re.split(r"(\s+)", str(markupsafe.escape(text))) 282 rel_attr = f' rel="{markupsafe.escape(rel)}"' if rel else "" 283 target_attr = f' target="{markupsafe.escape(target)}"' if target else "" 284 285 for i, word in enumerate(words): 286 head, middle, tail = "", word, "" 287 match = re.match(r"^([(<]|<)+", middle) 288 289 if match: 290 head = match.group() 291 middle = middle[match.end() :] 292 293 # Unlike lead, which is anchored to the start of the string, 294 # need to check that the string ends with any of the characters 295 # before trying to match all of them, to avoid backtracking. 296 if middle.endswith((")", ">", ".", ",", "\n", ">")): 297 match = re.search(r"([)>.,\n]|>)+$", middle) 298 299 if match: 300 tail = match.group() 301 middle = middle[: match.start()] 302 303 # Prefer balancing parentheses in URLs instead of ignoring a 304 # trailing character. 305 for start_char, end_char in ("(", ")"), ("<", ">"), ("<", ">"): 306 start_count = middle.count(start_char) 307 308 if start_count <= middle.count(end_char): 309 # Balanced, or lighter on the left 310 continue 311 312 # Move as many as possible from the tail to balance 313 for _ in range(min(start_count, tail.count(end_char))): 314 end_index = tail.index(end_char) + len(end_char) 315 # Move anything in the tail before the end char too 316 middle += tail[:end_index] 317 tail = tail[end_index:] 318 319 if _http_re.match(middle): 320 if middle.startswith("https://") or middle.startswith("http://"): 321 middle = ( 322 f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>' 323 ) 324 else: 325 middle = ( 326 f'<a href="https://{middle}"{rel_attr}{target_attr}>' 327 f"{trim_url(middle)}</a>" 328 ) 329 330 elif middle.startswith("mailto:") and _email_re.match(middle[7:]): 331 middle = f'<a href="{middle}">{middle[7:]}</a>' 332 333 elif ( 334 "@" in middle 335 and not middle.startswith("www.") 336 # ignore values like `@a@b` 337 and not middle.startswith("@") 338 and ":" not in middle 339 and _email_re.match(middle) 340 ): 341 middle = f'<a href="mailto:{middle}">{middle}</a>' 342 343 elif extra_schemes is not None: 344 for scheme in extra_schemes: 345 if middle != scheme and middle.startswith(scheme): 346 middle = f'<a href="{middle}"{rel_attr}{target_attr}>{middle}</a>' 347 348 words[i] = f"{head}{middle}{tail}" 349 350 return "".join(words) 351 352 353def generate_lorem_ipsum( 354 n: int = 5, html: bool = True, min: int = 20, max: int = 100 355) -> str: 356 """Generate some lorem ipsum for the template.""" 357 from .constants import LOREM_IPSUM_WORDS 358 359 words = LOREM_IPSUM_WORDS.split() 360 result = [] 361 362 for _ in range(n): 363 next_capitalized = True 364 last_comma = last_fullstop = 0 365 word = None 366 last = None 367 p = [] 368 369 # each paragraph contains out of 20 to 100 words. 370 for idx, _ in enumerate(range(randrange(min, max))): 371 while True: 372 word = choice(words) 373 if word != last: 374 last = word 375 break 376 if next_capitalized: 377 word = word.capitalize() 378 next_capitalized = False 379 # add commas 380 if idx - randrange(3, 8) > last_comma: 381 last_comma = idx 382 last_fullstop += 2 383 word += "," 384 # add end of sentences 385 if idx - randrange(10, 20) > last_fullstop: 386 last_comma = last_fullstop = idx 387 word += "." 388 next_capitalized = True 389 p.append(word) 390 391 # ensure that the paragraph ends with a dot. 392 p_str = " ".join(p) 393 394 if p_str.endswith(","): 395 p_str = p_str[:-1] + "." 396 elif not p_str.endswith("."): 397 p_str += "." 398 399 result.append(p_str) 400 401 if not html: 402 return "\n\n".join(result) 403 return markupsafe.Markup( 404 "\n".join(f"<p>{markupsafe.escape(x)}</p>" for x in result) 405 ) 406 407 408def url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str: 409 """Quote a string for use in a URL using the given charset. 410 411 :param obj: String or bytes to quote. Other types are converted to 412 string then encoded to bytes using the given charset. 413 :param charset: Encode text to bytes using this charset. 414 :param for_qs: Quote "/" and use "+" for spaces. 415 """ 416 if not isinstance(obj, bytes): 417 if not isinstance(obj, str): 418 obj = str(obj) 419 420 obj = obj.encode(charset) 421 422 safe = b"" if for_qs else b"/" 423 rv = quote_from_bytes(obj, safe) 424 425 if for_qs: 426 rv = rv.replace("%20", "+") 427 428 return rv 429 430 431@abc.MutableMapping.register 432class LRUCache: 433 """A simple LRU Cache implementation.""" 434 435 # this is fast for small capacities (something below 1000) but doesn't 436 # scale. But as long as it's only used as storage for templates this 437 # won't do any harm. 438 439 def __init__(self, capacity: int) -> None: 440 self.capacity = capacity 441 self._mapping: t.Dict[t.Any, t.Any] = {} 442 self._queue: te.Deque[t.Any] = deque() 443 self._postinit() 444 445 def _postinit(self) -> None: 446 # alias all queue methods for faster lookup 447 self._popleft = self._queue.popleft 448 self._pop = self._queue.pop 449 self._remove = self._queue.remove 450 self._wlock = Lock() 451 self._append = self._queue.append 452 453 def __getstate__(self) -> t.Mapping[str, t.Any]: 454 return { 455 "capacity": self.capacity, 456 "_mapping": self._mapping, 457 "_queue": self._queue, 458 } 459 460 def __setstate__(self, d: t.Mapping[str, t.Any]) -> None: 461 self.__dict__.update(d) 462 self._postinit() 463 464 def __getnewargs__(self) -> t.Tuple[t.Any, ...]: 465 return (self.capacity,) 466 467 def copy(self) -> "te.Self": 468 """Return a shallow copy of the instance.""" 469 rv = self.__class__(self.capacity) 470 rv._mapping.update(self._mapping) 471 rv._queue.extend(self._queue) 472 return rv 473 474 def get(self, key: t.Any, default: t.Any = None) -> t.Any: 475 """Return an item from the cache dict or `default`""" 476 try: 477 return self[key] 478 except KeyError: 479 return default 480 481 def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any: 482 """Set `default` if the key is not in the cache otherwise 483 leave unchanged. Return the value of this key. 484 """ 485 try: 486 return self[key] 487 except KeyError: 488 self[key] = default 489 return default 490 491 def clear(self) -> None: 492 """Clear the cache.""" 493 with self._wlock: 494 self._mapping.clear() 495 self._queue.clear() 496 497 def __contains__(self, key: t.Any) -> bool: 498 """Check if a key exists in this cache.""" 499 return key in self._mapping 500 501 def __len__(self) -> int: 502 """Return the current size of the cache.""" 503 return len(self._mapping) 504 505 def __repr__(self) -> str: 506 return f"<{type(self).__name__} {self._mapping!r}>" 507 508 def __getitem__(self, key: t.Any) -> t.Any: 509 """Get an item from the cache. Moves the item up so that it has the 510 highest priority then. 511 512 Raise a `KeyError` if it does not exist. 513 """ 514 with self._wlock: 515 rv = self._mapping[key] 516 517 if self._queue[-1] != key: 518 try: 519 self._remove(key) 520 except ValueError: 521 # if something removed the key from the container 522 # when we read, ignore the ValueError that we would 523 # get otherwise. 524 pass 525 526 self._append(key) 527 528 return rv 529 530 def __setitem__(self, key: t.Any, value: t.Any) -> None: 531 """Sets the value for an item. Moves the item up so that it 532 has the highest priority then. 533 """ 534 with self._wlock: 535 if key in self._mapping: 536 self._remove(key) 537 elif len(self._mapping) == self.capacity: 538 del self._mapping[self._popleft()] 539 540 self._append(key) 541 self._mapping[key] = value 542 543 def __delitem__(self, key: t.Any) -> None: 544 """Remove an item from the cache dict. 545 Raise a `KeyError` if it does not exist. 546 """ 547 with self._wlock: 548 del self._mapping[key] 549 550 try: 551 self._remove(key) 552 except ValueError: 553 pass 554 555 def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]: 556 """Return a list of items.""" 557 result = [(key, self._mapping[key]) for key in list(self._queue)] 558 result.reverse() 559 return result 560 561 def values(self) -> t.Iterable[t.Any]: 562 """Return a list of all values.""" 563 return [x[1] for x in self.items()] 564 565 def keys(self) -> t.Iterable[t.Any]: 566 """Return a list of all keys ordered by most recent usage.""" 567 return list(self) 568 569 def __iter__(self) -> t.Iterator[t.Any]: 570 return reversed(tuple(self._queue)) 571 572 def __reversed__(self) -> t.Iterator[t.Any]: 573 """Iterate over the keys in the cache dict, oldest items 574 coming first. 575 """ 576 return iter(tuple(self._queue)) 577 578 __copy__ = copy 579 580 581def select_autoescape( 582 enabled_extensions: t.Collection[str] = ("html", "htm", "xml"), 583 disabled_extensions: t.Collection[str] = (), 584 default_for_string: bool = True, 585 default: bool = False, 586) -> t.Callable[[t.Optional[str]], bool]: 587 """Intelligently sets the initial value of autoescaping based on the 588 filename of the template. This is the recommended way to configure 589 autoescaping if you do not want to write a custom function yourself. 590 591 If you want to enable it for all templates created from strings or 592 for all templates with `.html` and `.xml` extensions:: 593 594 from jinja2 import Environment, select_autoescape 595 env = Environment(autoescape=select_autoescape( 596 enabled_extensions=('html', 'xml'), 597 default_for_string=True, 598 )) 599 600 Example configuration to turn it on at all times except if the template 601 ends with `.txt`:: 602 603 from jinja2 import Environment, select_autoescape 604 env = Environment(autoescape=select_autoescape( 605 disabled_extensions=('txt',), 606 default_for_string=True, 607 default=True, 608 )) 609 610 The `enabled_extensions` is an iterable of all the extensions that 611 autoescaping should be enabled for. Likewise `disabled_extensions` is 612 a list of all templates it should be disabled for. If a template is 613 loaded from a string then the default from `default_for_string` is used. 614 If nothing matches then the initial value of autoescaping is set to the 615 value of `default`. 616 617 For security reasons this function operates case insensitive. 618 619 .. versionadded:: 2.9 620 """ 621 enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions) 622 disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions) 623 624 def autoescape(template_name: t.Optional[str]) -> bool: 625 if template_name is None: 626 return default_for_string 627 template_name = template_name.lower() 628 if template_name.endswith(enabled_patterns): 629 return True 630 if template_name.endswith(disabled_patterns): 631 return False 632 return default 633 634 return autoescape 635 636 637def htmlsafe_json_dumps( 638 obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any 639) -> markupsafe.Markup: 640 """Serialize an object to a string of JSON with :func:`json.dumps`, 641 then replace HTML-unsafe characters with Unicode escapes and mark 642 the result safe with :class:`~markupsafe.Markup`. 643 644 This is available in templates as the ``|tojson`` filter. 645 646 The following characters are escaped: ``<``, ``>``, ``&``, ``'``. 647 648 The returned string is safe to render in HTML documents and 649 ``<script>`` tags. The exception is in HTML attributes that are 650 double quoted; either use single quotes or the ``|forceescape`` 651 filter. 652 653 :param obj: The object to serialize to JSON. 654 :param dumps: The ``dumps`` function to use. Defaults to 655 ``env.policies["json.dumps_function"]``, which defaults to 656 :func:`json.dumps`. 657 :param kwargs: Extra arguments to pass to ``dumps``. Merged onto 658 ``env.policies["json.dumps_kwargs"]``. 659 660 .. versionchanged:: 3.0 661 The ``dumper`` parameter is renamed to ``dumps``. 662 663 .. versionadded:: 2.9 664 """ 665 if dumps is None: 666 dumps = json.dumps 667 668 return markupsafe.Markup( 669 dumps(obj, **kwargs) 670 .replace("<", "\\u003c") 671 .replace(">", "\\u003e") 672 .replace("&", "\\u0026") 673 .replace("'", "\\u0027") 674 ) 675 676 677class Cycler: 678 """Cycle through values by yield them one at a time, then restarting 679 once the end is reached. Available as ``cycler`` in templates. 680 681 Similar to ``loop.cycle``, but can be used outside loops or across 682 multiple loops. For example, render a list of folders and files in a 683 list, alternating giving them "odd" and "even" classes. 684 685 .. code-block:: html+jinja 686 687 {% set row_class = cycler("odd", "even") %} 688 <ul class="browser"> 689 {% for folder in folders %} 690 <li class="folder {{ row_class.next() }}">{{ folder }} 691 {% endfor %} 692 {% for file in files %} 693 <li class="file {{ row_class.next() }}">{{ file }} 694 {% endfor %} 695 </ul> 696 697 :param items: Each positional argument will be yielded in the order 698 given for each cycle. 699 700 .. versionadded:: 2.1 701 """ 702 703 def __init__(self, *items: t.Any) -> None: 704 if not items: 705 raise RuntimeError("at least one item has to be provided") 706 self.items = items 707 self.pos = 0 708 709 def reset(self) -> None: 710 """Resets the current item to the first item.""" 711 self.pos = 0 712 713 @property 714 def current(self) -> t.Any: 715 """Return the current item. Equivalent to the item that will be 716 returned next time :meth:`next` is called. 717 """ 718 return self.items[self.pos] 719 720 def next(self) -> t.Any: 721 """Return the current item, then advance :attr:`current` to the 722 next item. 723 """ 724 rv = self.current 725 self.pos = (self.pos + 1) % len(self.items) 726 return rv 727 728 __next__ = next 729 730 731class Joiner: 732 """A joining helper for templates.""" 733 734 def __init__(self, sep: str = ", ") -> None: 735 self.sep = sep 736 self.used = False 737 738 def __call__(self) -> str: 739 if not self.used: 740 self.used = True 741 return "" 742 return self.sep 743 744 745class Namespace: 746 """A namespace object that can hold arbitrary attributes. It may be 747 initialized from a dictionary or with keyword arguments.""" 748 749 def __init__(*args: t.Any, **kwargs: t.Any) -> None: # noqa: B902 750 self, args = args[0], args[1:] 751 self.__attrs = dict(*args, **kwargs) 752 753 def __getattribute__(self, name: str) -> t.Any: 754 # __class__ is needed for the awaitable check in async mode 755 if name in {"_Namespace__attrs", "__class__"}: 756 return object.__getattribute__(self, name) 757 try: 758 return self.__attrs[name] 759 except KeyError: 760 raise AttributeError(name) from None 761 762 def __setitem__(self, name: str, value: t.Any) -> None: 763 self.__attrs[name] = value 764 765 def __repr__(self) -> str: 766 return f"<Namespace {self.__attrs!r}>"
Special singleton representing missing values for the runtime.
Concatenate any number of strings.
The string whose method is called is inserted in between each given string. The result is returned as a new string.
Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'
39def pass_context(f: F) -> F: 40 """Pass the :class:`~jinja2.runtime.Context` as the first argument 41 to the decorated function when called while rendering a template. 42 43 Can be used on functions, filters, and tests. 44 45 If only ``Context.eval_context`` is needed, use 46 :func:`pass_eval_context`. If only ``Context.environment`` is 47 needed, use :func:`pass_environment`. 48 49 .. versionadded:: 3.0.0 50 Replaces ``contextfunction`` and ``contextfilter``. 51 """ 52 f.jinja_pass_arg = _PassArg.context # type: ignore 53 return f
Pass the ~jinja2.runtime.Context
as the first argument
to the decorated function when called while rendering a template.
Can be used on functions, filters, and tests.
If only Context.eval_context
is needed, use
pass_eval_context()
. If only Context.environment
is
needed, use pass_environment()
.
New in version 3.0.0:
Replaces contextfunction
and contextfilter
.
56def pass_eval_context(f: F) -> F: 57 """Pass the :class:`~jinja2.nodes.EvalContext` as the first argument 58 to the decorated function when called while rendering a template. 59 See :ref:`eval-context`. 60 61 Can be used on functions, filters, and tests. 62 63 If only ``EvalContext.environment`` is needed, use 64 :func:`pass_environment`. 65 66 .. versionadded:: 3.0.0 67 Replaces ``evalcontextfunction`` and ``evalcontextfilter``. 68 """ 69 f.jinja_pass_arg = _PassArg.eval_context # type: ignore 70 return f
Pass the ~jinja2.nodes.EvalContext
as the first argument
to the decorated function when called while rendering a template.
See :ref:eval-context
.
Can be used on functions, filters, and tests.
If only EvalContext.environment
is needed, use
pass_environment()
.
New in version 3.0.0:
Replaces evalcontextfunction
and evalcontextfilter
.
73def pass_environment(f: F) -> F: 74 """Pass the :class:`~jinja2.Environment` as the first argument to 75 the decorated function when called while rendering a template. 76 77 Can be used on functions, filters, and tests. 78 79 .. versionadded:: 3.0.0 80 Replaces ``environmentfunction`` and ``environmentfilter``. 81 """ 82 f.jinja_pass_arg = _PassArg.environment # type: ignore 83 return f
Pass the ~jinja2.Environment
as the first argument to
the decorated function when called while rendering a template.
Can be used on functions, filters, and tests.
New in version 3.0.0:
Replaces environmentfunction
and environmentfilter
.
99def internalcode(f: F) -> F: 100 """Marks the function as internally used""" 101 internal_code.add(f.__code__) 102 return f
Marks the function as internally used
105def is_undefined(obj: t.Any) -> bool: 106 """Check if the object passed is undefined. This does nothing more than 107 performing an instance check against :class:`Undefined` but looks nicer. 108 This can be used for custom filters or tests that want to react to 109 undefined variables. For example a custom default filter can look like 110 this:: 111 112 def default(var, default=''): 113 if is_undefined(var): 114 return default 115 return var 116 """ 117 from .runtime import Undefined 118 119 return isinstance(obj, Undefined)
Check if the object passed is undefined. This does nothing more than
performing an instance check against Undefined
but looks nicer.
This can be used for custom filters or tests that want to react to
undefined variables. For example a custom default filter can look like
this::
def default(var, default=''):
if is_undefined(var):
return default
return var
122def consume(iterable: t.Iterable[t.Any]) -> None: 123 """Consumes an iterable without doing anything with it.""" 124 for _ in iterable: 125 pass
Consumes an iterable without doing anything with it.
128def clear_caches() -> None: 129 """Jinja keeps internal caches for environments and lexers. These are 130 used so that Jinja doesn't have to recreate environments and lexers all 131 the time. Normally you don't have to care about that but if you are 132 measuring memory consumption you may want to clean the caches. 133 """ 134 from .environment import get_spontaneous_environment 135 from .lexer import _lexer_cache 136 137 get_spontaneous_environment.cache_clear() 138 _lexer_cache.clear()
Jinja keeps internal caches for environments and lexers. These are used so that Jinja doesn't have to recreate environments and lexers all the time. Normally you don't have to care about that but if you are measuring memory consumption you may want to clean the caches.
141def import_string(import_name: str, silent: bool = False) -> t.Any: 142 """Imports an object based on a string. This is useful if you want to 143 use import paths as endpoints or something similar. An import path can 144 be specified either in dotted notation (``xml.sax.saxutils.escape``) 145 or with a colon as object delimiter (``xml.sax.saxutils:escape``). 146 147 If the `silent` is True the return value will be `None` if the import 148 fails. 149 150 :return: imported object 151 """ 152 try: 153 if ":" in import_name: 154 module, obj = import_name.split(":", 1) 155 elif "." in import_name: 156 module, _, obj = import_name.rpartition(".") 157 else: 158 return __import__(import_name) 159 return getattr(__import__(module, None, None, [obj]), obj) 160 except (ImportError, AttributeError): 161 if not silent: 162 raise
Imports an object based on a string. This is useful if you want to
use import paths as endpoints or something similar. An import path can
be specified either in dotted notation (xml.sax.saxutils.escape
)
or with a colon as object delimiter (xml.sax.saxutils:escape
).
If the silent
is True the return value will be None
if the import
fails.
Returns
imported object
165def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO[t.Any]]: 166 """Returns a file descriptor for the filename if that file exists, 167 otherwise ``None``. 168 """ 169 if not os.path.isfile(filename): 170 return None 171 172 return open(filename, mode)
Returns a file descriptor for the filename if that file exists,
otherwise None
.
175def object_type_repr(obj: t.Any) -> str: 176 """Returns the name of the object's type. For some recognized 177 singletons the name of the object is returned instead. (For 178 example for `None` and `Ellipsis`). 179 """ 180 if obj is None: 181 return "None" 182 elif obj is Ellipsis: 183 return "Ellipsis" 184 185 cls = type(obj) 186 187 if cls.__module__ == "builtins": 188 return f"{cls.__name__} object" 189 190 return f"{cls.__module__}.{cls.__name__} object"
Returns the name of the object's type. For some recognized
singletons the name of the object is returned instead. (For
example for None
and Ellipsis
).
193def pformat(obj: t.Any) -> str: 194 """Format an object using :func:`pprint.pformat`.""" 195 from pprint import pformat 196 197 return pformat(obj)
Format an object using pprint.pformat()
.
231def urlize( 232 text: str, 233 trim_url_limit: t.Optional[int] = None, 234 rel: t.Optional[str] = None, 235 target: t.Optional[str] = None, 236 extra_schemes: t.Optional[t.Iterable[str]] = None, 237) -> str: 238 """Convert URLs in text into clickable links. 239 240 This may not recognize links in some situations. Usually, a more 241 comprehensive formatter, such as a Markdown library, is a better 242 choice. 243 244 Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email 245 addresses. Links with trailing punctuation (periods, commas, closing 246 parentheses) and leading punctuation (opening parentheses) are 247 recognized excluding the punctuation. Email addresses that include 248 header fields are not recognized (for example, 249 ``mailto:address@example.com?cc=copy@example.com``). 250 251 :param text: Original text containing URLs to link. 252 :param trim_url_limit: Shorten displayed URL values to this length. 253 :param target: Add the ``target`` attribute to links. 254 :param rel: Add the ``rel`` attribute to links. 255 :param extra_schemes: Recognize URLs that start with these schemes 256 in addition to the default behavior. 257 258 .. versionchanged:: 3.0 259 The ``extra_schemes`` parameter was added. 260 261 .. versionchanged:: 3.0 262 Generate ``https://`` links for URLs without a scheme. 263 264 .. versionchanged:: 3.0 265 The parsing rules were updated. Recognize email addresses with 266 or without the ``mailto:`` scheme. Validate IP addresses. Ignore 267 parentheses and brackets in more cases. 268 """ 269 if trim_url_limit is not None: 270 271 def trim_url(x: str) -> str: 272 if len(x) > trim_url_limit: 273 return f"{x[:trim_url_limit]}..." 274 275 return x 276 277 else: 278 279 def trim_url(x: str) -> str: 280 return x 281 282 words = re.split(r"(\s+)", str(markupsafe.escape(text))) 283 rel_attr = f' rel="{markupsafe.escape(rel)}"' if rel else "" 284 target_attr = f' target="{markupsafe.escape(target)}"' if target else "" 285 286 for i, word in enumerate(words): 287 head, middle, tail = "", word, "" 288 match = re.match(r"^([(<]|<)+", middle) 289 290 if match: 291 head = match.group() 292 middle = middle[match.end() :] 293 294 # Unlike lead, which is anchored to the start of the string, 295 # need to check that the string ends with any of the characters 296 # before trying to match all of them, to avoid backtracking. 297 if middle.endswith((")", ">", ".", ",", "\n", ">")): 298 match = re.search(r"([)>.,\n]|>)+$", middle) 299 300 if match: 301 tail = match.group() 302 middle = middle[: match.start()] 303 304 # Prefer balancing parentheses in URLs instead of ignoring a 305 # trailing character. 306 for start_char, end_char in ("(", ")"), ("<", ">"), ("<", ">"): 307 start_count = middle.count(start_char) 308 309 if start_count <= middle.count(end_char): 310 # Balanced, or lighter on the left 311 continue 312 313 # Move as many as possible from the tail to balance 314 for _ in range(min(start_count, tail.count(end_char))): 315 end_index = tail.index(end_char) + len(end_char) 316 # Move anything in the tail before the end char too 317 middle += tail[:end_index] 318 tail = tail[end_index:] 319 320 if _http_re.match(middle): 321 if middle.startswith("https://") or middle.startswith("http://"): 322 middle = ( 323 f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>' 324 ) 325 else: 326 middle = ( 327 f'<a href="https://{middle}"{rel_attr}{target_attr}>' 328 f"{trim_url(middle)}</a>" 329 ) 330 331 elif middle.startswith("mailto:") and _email_re.match(middle[7:]): 332 middle = f'<a href="{middle}">{middle[7:]}</a>' 333 334 elif ( 335 "@" in middle 336 and not middle.startswith("www.") 337 # ignore values like `@a@b` 338 and not middle.startswith("@") 339 and ":" not in middle 340 and _email_re.match(middle) 341 ): 342 middle = f'<a href="mailto:{middle}">{middle}</a>' 343 344 elif extra_schemes is not None: 345 for scheme in extra_schemes: 346 if middle != scheme and middle.startswith(scheme): 347 middle = f'<a href="{middle}"{rel_attr}{target_attr}>{middle}</a>' 348 349 words[i] = f"{head}{middle}{tail}" 350 351 return "".join(words)
Convert URLs in text into clickable links.
This may not recognize links in some situations. Usually, a more comprehensive formatter, such as a Markdown library, is a better choice.
Works on http://
, https://
, www.
, mailto:
, and email
addresses. Links with trailing punctuation (periods, commas, closing
parentheses) and leading punctuation (opening parentheses) are
recognized excluding the punctuation. Email addresses that include
header fields are not recognized (for example,
mailto:address@example.com?cc=copy@example.com
).
Parameters
- text: Original text containing URLs to link.
- trim_url_limit: Shorten displayed URL values to this length.
- target: Add the
target
attribute to links. - rel: Add the
rel
attribute to links. - extra_schemes: Recognize URLs that start with these schemes in addition to the default behavior.
Changed in version 3.0:
The extra_schemes
parameter was added.
Changed in version 3.0:
Generate https://
links for URLs without a scheme.
Changed in version 3.0:
The parsing rules were updated. Recognize email addresses with
or without the mailto:
scheme. Validate IP addresses. Ignore
parentheses and brackets in more cases.
354def generate_lorem_ipsum( 355 n: int = 5, html: bool = True, min: int = 20, max: int = 100 356) -> str: 357 """Generate some lorem ipsum for the template.""" 358 from .constants import LOREM_IPSUM_WORDS 359 360 words = LOREM_IPSUM_WORDS.split() 361 result = [] 362 363 for _ in range(n): 364 next_capitalized = True 365 last_comma = last_fullstop = 0 366 word = None 367 last = None 368 p = [] 369 370 # each paragraph contains out of 20 to 100 words. 371 for idx, _ in enumerate(range(randrange(min, max))): 372 while True: 373 word = choice(words) 374 if word != last: 375 last = word 376 break 377 if next_capitalized: 378 word = word.capitalize() 379 next_capitalized = False 380 # add commas 381 if idx - randrange(3, 8) > last_comma: 382 last_comma = idx 383 last_fullstop += 2 384 word += "," 385 # add end of sentences 386 if idx - randrange(10, 20) > last_fullstop: 387 last_comma = last_fullstop = idx 388 word += "." 389 next_capitalized = True 390 p.append(word) 391 392 # ensure that the paragraph ends with a dot. 393 p_str = " ".join(p) 394 395 if p_str.endswith(","): 396 p_str = p_str[:-1] + "." 397 elif not p_str.endswith("."): 398 p_str += "." 399 400 result.append(p_str) 401 402 if not html: 403 return "\n\n".join(result) 404 return markupsafe.Markup( 405 "\n".join(f"<p>{markupsafe.escape(x)}</p>" for x in result) 406 )
Generate some lorem ipsum for the template.
409def url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str: 410 """Quote a string for use in a URL using the given charset. 411 412 :param obj: String or bytes to quote. Other types are converted to 413 string then encoded to bytes using the given charset. 414 :param charset: Encode text to bytes using this charset. 415 :param for_qs: Quote "/" and use "+" for spaces. 416 """ 417 if not isinstance(obj, bytes): 418 if not isinstance(obj, str): 419 obj = str(obj) 420 421 obj = obj.encode(charset) 422 423 safe = b"" if for_qs else b"/" 424 rv = quote_from_bytes(obj, safe) 425 426 if for_qs: 427 rv = rv.replace("%20", "+") 428 429 return rv
Quote a string for use in a URL using the given charset.
Parameters
- obj: String or bytes to quote. Other types are converted to string then encoded to bytes using the given charset.
- charset: Encode text to bytes using this charset.
- for_qs: Quote "/" and use "+" for spaces.
432@abc.MutableMapping.register 433class LRUCache: 434 """A simple LRU Cache implementation.""" 435 436 # this is fast for small capacities (something below 1000) but doesn't 437 # scale. But as long as it's only used as storage for templates this 438 # won't do any harm. 439 440 def __init__(self, capacity: int) -> None: 441 self.capacity = capacity 442 self._mapping: t.Dict[t.Any, t.Any] = {} 443 self._queue: te.Deque[t.Any] = deque() 444 self._postinit() 445 446 def _postinit(self) -> None: 447 # alias all queue methods for faster lookup 448 self._popleft = self._queue.popleft 449 self._pop = self._queue.pop 450 self._remove = self._queue.remove 451 self._wlock = Lock() 452 self._append = self._queue.append 453 454 def __getstate__(self) -> t.Mapping[str, t.Any]: 455 return { 456 "capacity": self.capacity, 457 "_mapping": self._mapping, 458 "_queue": self._queue, 459 } 460 461 def __setstate__(self, d: t.Mapping[str, t.Any]) -> None: 462 self.__dict__.update(d) 463 self._postinit() 464 465 def __getnewargs__(self) -> t.Tuple[t.Any, ...]: 466 return (self.capacity,) 467 468 def copy(self) -> "te.Self": 469 """Return a shallow copy of the instance.""" 470 rv = self.__class__(self.capacity) 471 rv._mapping.update(self._mapping) 472 rv._queue.extend(self._queue) 473 return rv 474 475 def get(self, key: t.Any, default: t.Any = None) -> t.Any: 476 """Return an item from the cache dict or `default`""" 477 try: 478 return self[key] 479 except KeyError: 480 return default 481 482 def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any: 483 """Set `default` if the key is not in the cache otherwise 484 leave unchanged. Return the value of this key. 485 """ 486 try: 487 return self[key] 488 except KeyError: 489 self[key] = default 490 return default 491 492 def clear(self) -> None: 493 """Clear the cache.""" 494 with self._wlock: 495 self._mapping.clear() 496 self._queue.clear() 497 498 def __contains__(self, key: t.Any) -> bool: 499 """Check if a key exists in this cache.""" 500 return key in self._mapping 501 502 def __len__(self) -> int: 503 """Return the current size of the cache.""" 504 return len(self._mapping) 505 506 def __repr__(self) -> str: 507 return f"<{type(self).__name__} {self._mapping!r}>" 508 509 def __getitem__(self, key: t.Any) -> t.Any: 510 """Get an item from the cache. Moves the item up so that it has the 511 highest priority then. 512 513 Raise a `KeyError` if it does not exist. 514 """ 515 with self._wlock: 516 rv = self._mapping[key] 517 518 if self._queue[-1] != key: 519 try: 520 self._remove(key) 521 except ValueError: 522 # if something removed the key from the container 523 # when we read, ignore the ValueError that we would 524 # get otherwise. 525 pass 526 527 self._append(key) 528 529 return rv 530 531 def __setitem__(self, key: t.Any, value: t.Any) -> None: 532 """Sets the value for an item. Moves the item up so that it 533 has the highest priority then. 534 """ 535 with self._wlock: 536 if key in self._mapping: 537 self._remove(key) 538 elif len(self._mapping) == self.capacity: 539 del self._mapping[self._popleft()] 540 541 self._append(key) 542 self._mapping[key] = value 543 544 def __delitem__(self, key: t.Any) -> None: 545 """Remove an item from the cache dict. 546 Raise a `KeyError` if it does not exist. 547 """ 548 with self._wlock: 549 del self._mapping[key] 550 551 try: 552 self._remove(key) 553 except ValueError: 554 pass 555 556 def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]: 557 """Return a list of items.""" 558 result = [(key, self._mapping[key]) for key in list(self._queue)] 559 result.reverse() 560 return result 561 562 def values(self) -> t.Iterable[t.Any]: 563 """Return a list of all values.""" 564 return [x[1] for x in self.items()] 565 566 def keys(self) -> t.Iterable[t.Any]: 567 """Return a list of all keys ordered by most recent usage.""" 568 return list(self) 569 570 def __iter__(self) -> t.Iterator[t.Any]: 571 return reversed(tuple(self._queue)) 572 573 def __reversed__(self) -> t.Iterator[t.Any]: 574 """Iterate over the keys in the cache dict, oldest items 575 coming first. 576 """ 577 return iter(tuple(self._queue)) 578 579 __copy__ = copy
A simple LRU Cache implementation.
468 def copy(self) -> "te.Self": 469 """Return a shallow copy of the instance.""" 470 rv = self.__class__(self.capacity) 471 rv._mapping.update(self._mapping) 472 rv._queue.extend(self._queue) 473 return rv
Return a shallow copy of the instance.
475 def get(self, key: t.Any, default: t.Any = None) -> t.Any: 476 """Return an item from the cache dict or `default`""" 477 try: 478 return self[key] 479 except KeyError: 480 return default
Return an item from the cache dict or default
482 def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any: 483 """Set `default` if the key is not in the cache otherwise 484 leave unchanged. Return the value of this key. 485 """ 486 try: 487 return self[key] 488 except KeyError: 489 self[key] = default 490 return default
Set default
if the key is not in the cache otherwise
leave unchanged. Return the value of this key.
492 def clear(self) -> None: 493 """Clear the cache.""" 494 with self._wlock: 495 self._mapping.clear() 496 self._queue.clear()
Clear the cache.
556 def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]: 557 """Return a list of items.""" 558 result = [(key, self._mapping[key]) for key in list(self._queue)] 559 result.reverse() 560 return result
Return a list of items.
582def select_autoescape( 583 enabled_extensions: t.Collection[str] = ("html", "htm", "xml"), 584 disabled_extensions: t.Collection[str] = (), 585 default_for_string: bool = True, 586 default: bool = False, 587) -> t.Callable[[t.Optional[str]], bool]: 588 """Intelligently sets the initial value of autoescaping based on the 589 filename of the template. This is the recommended way to configure 590 autoescaping if you do not want to write a custom function yourself. 591 592 If you want to enable it for all templates created from strings or 593 for all templates with `.html` and `.xml` extensions:: 594 595 from jinja2 import Environment, select_autoescape 596 env = Environment(autoescape=select_autoescape( 597 enabled_extensions=('html', 'xml'), 598 default_for_string=True, 599 )) 600 601 Example configuration to turn it on at all times except if the template 602 ends with `.txt`:: 603 604 from jinja2 import Environment, select_autoescape 605 env = Environment(autoescape=select_autoescape( 606 disabled_extensions=('txt',), 607 default_for_string=True, 608 default=True, 609 )) 610 611 The `enabled_extensions` is an iterable of all the extensions that 612 autoescaping should be enabled for. Likewise `disabled_extensions` is 613 a list of all templates it should be disabled for. If a template is 614 loaded from a string then the default from `default_for_string` is used. 615 If nothing matches then the initial value of autoescaping is set to the 616 value of `default`. 617 618 For security reasons this function operates case insensitive. 619 620 .. versionadded:: 2.9 621 """ 622 enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions) 623 disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions) 624 625 def autoescape(template_name: t.Optional[str]) -> bool: 626 if template_name is None: 627 return default_for_string 628 template_name = template_name.lower() 629 if template_name.endswith(enabled_patterns): 630 return True 631 if template_name.endswith(disabled_patterns): 632 return False 633 return default 634 635 return autoescape
Intelligently sets the initial value of autoescaping based on the filename of the template. This is the recommended way to configure autoescaping if you do not want to write a custom function yourself.
If you want to enable it for all templates created from strings or
for all templates with .html
and .xml
extensions::
from jinja2 import Environment, select_autoescape
env = Environment(autoescape=select_autoescape(
enabled_extensions=('html', 'xml'),
default_for_string=True,
))
Example configuration to turn it on at all times except if the template
ends with .txt
::
from jinja2 import Environment, select_autoescape
env = Environment(autoescape=select_autoescape(
disabled_extensions=('txt',),
default_for_string=True,
default=True,
))
The enabled_extensions
is an iterable of all the extensions that
autoescaping should be enabled for. Likewise disabled_extensions
is
a list of all templates it should be disabled for. If a template is
loaded from a string then the default from default_for_string
is used.
If nothing matches then the initial value of autoescaping is set to the
value of default
.
For security reasons this function operates case insensitive.
New in version 2.9.
638def htmlsafe_json_dumps( 639 obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any 640) -> markupsafe.Markup: 641 """Serialize an object to a string of JSON with :func:`json.dumps`, 642 then replace HTML-unsafe characters with Unicode escapes and mark 643 the result safe with :class:`~markupsafe.Markup`. 644 645 This is available in templates as the ``|tojson`` filter. 646 647 The following characters are escaped: ``<``, ``>``, ``&``, ``'``. 648 649 The returned string is safe to render in HTML documents and 650 ``<script>`` tags. The exception is in HTML attributes that are 651 double quoted; either use single quotes or the ``|forceescape`` 652 filter. 653 654 :param obj: The object to serialize to JSON. 655 :param dumps: The ``dumps`` function to use. Defaults to 656 ``env.policies["json.dumps_function"]``, which defaults to 657 :func:`json.dumps`. 658 :param kwargs: Extra arguments to pass to ``dumps``. Merged onto 659 ``env.policies["json.dumps_kwargs"]``. 660 661 .. versionchanged:: 3.0 662 The ``dumper`` parameter is renamed to ``dumps``. 663 664 .. versionadded:: 2.9 665 """ 666 if dumps is None: 667 dumps = json.dumps 668 669 return markupsafe.Markup( 670 dumps(obj, **kwargs) 671 .replace("<", "\\u003c") 672 .replace(">", "\\u003e") 673 .replace("&", "\\u0026") 674 .replace("'", "\\u0027") 675 )
Serialize an object to a string of JSON with json.dumps()
,
then replace HTML-unsafe characters with Unicode escapes and mark
the result safe with ~markupsafe.Markup
.
This is available in templates as the |tojson
filter.
The following characters are escaped: <
, >
, &
, '
.
The returned string is safe to render in HTML documents and
<script>
tags. The exception is in HTML attributes that are
double quoted; either use single quotes or the |forceescape
filter.
Parameters
- obj: The object to serialize to JSON.
- dumps: The
dumps
function to use. Defaults toenv.policies["json.dumps_function"]
, which defaults tojson.dumps()
. - kwargs: Extra arguments to pass to
dumps
. Merged ontoenv.policies["json.dumps_kwargs"]
.
Changed in version 3.0:
The dumper
parameter is renamed to dumps
.
New in version 2.9.
678class Cycler: 679 """Cycle through values by yield them one at a time, then restarting 680 once the end is reached. Available as ``cycler`` in templates. 681 682 Similar to ``loop.cycle``, but can be used outside loops or across 683 multiple loops. For example, render a list of folders and files in a 684 list, alternating giving them "odd" and "even" classes. 685 686 .. code-block:: html+jinja 687 688 {% set row_class = cycler("odd", "even") %} 689 <ul class="browser"> 690 {% for folder in folders %} 691 <li class="folder {{ row_class.next() }}">{{ folder }} 692 {% endfor %} 693 {% for file in files %} 694 <li class="file {{ row_class.next() }}">{{ file }} 695 {% endfor %} 696 </ul> 697 698 :param items: Each positional argument will be yielded in the order 699 given for each cycle. 700 701 .. versionadded:: 2.1 702 """ 703 704 def __init__(self, *items: t.Any) -> None: 705 if not items: 706 raise RuntimeError("at least one item has to be provided") 707 self.items = items 708 self.pos = 0 709 710 def reset(self) -> None: 711 """Resets the current item to the first item.""" 712 self.pos = 0 713 714 @property 715 def current(self) -> t.Any: 716 """Return the current item. Equivalent to the item that will be 717 returned next time :meth:`next` is called. 718 """ 719 return self.items[self.pos] 720 721 def next(self) -> t.Any: 722 """Return the current item, then advance :attr:`current` to the 723 next item. 724 """ 725 rv = self.current 726 self.pos = (self.pos + 1) % len(self.items) 727 return rv 728 729 __next__ = next
Cycle through values by yield them one at a time, then restarting
once the end is reached. Available as cycler
in templates.
Similar to loop.cycle
, but can be used outside loops or across
multiple loops. For example, render a list of folders and files in a
list, alternating giving them "odd" and "even" classes.
{% set row_class = cycler("odd", "even") %}
<ul class="browser">
{% for folder in folders %}
<li class="folder {{ row_class.next() }}">{{ folder }}
{% endfor %}
{% for file in files %}
<li class="file {{ row_class.next() }}">{{ file }}
{% endfor %}
</ul>
Parameters
- items: Each positional argument will be yielded in the order given for each cycle.
New in version 2.1.
714 @property 715 def current(self) -> t.Any: 716 """Return the current item. Equivalent to the item that will be 717 returned next time :meth:`next` is called. 718 """ 719 return self.items[self.pos]
Return the current item. Equivalent to the item that will be
returned next time next()
is called.
732class Joiner: 733 """A joining helper for templates.""" 734 735 def __init__(self, sep: str = ", ") -> None: 736 self.sep = sep 737 self.used = False 738 739 def __call__(self) -> str: 740 if not self.used: 741 self.used = True 742 return "" 743 return self.sep
A joining helper for templates.
746class Namespace: 747 """A namespace object that can hold arbitrary attributes. It may be 748 initialized from a dictionary or with keyword arguments.""" 749 750 def __init__(*args: t.Any, **kwargs: t.Any) -> None: # noqa: B902 751 self, args = args[0], args[1:] 752 self.__attrs = dict(*args, **kwargs) 753 754 def __getattribute__(self, name: str) -> t.Any: 755 # __class__ is needed for the awaitable check in async mode 756 if name in {"_Namespace__attrs", "__class__"}: 757 return object.__getattribute__(self, name) 758 try: 759 return self.__attrs[name] 760 except KeyError: 761 raise AttributeError(name) from None 762 763 def __setitem__(self, name: str, value: t.Any) -> None: 764 self.__attrs[name] = value 765 766 def __repr__(self) -> str: 767 return f"<Namespace {self.__attrs!r}>"
A namespace object that can hold arbitrary attributes. It may be initialized from a dictionary or with keyword arguments.