Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jinja2/utils.py: 33%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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
14import markupsafe
16if t.TYPE_CHECKING:
17 import typing_extensions as te
19F = t.TypeVar("F", bound=t.Callable[..., t.Any])
22class _MissingType:
23 def __repr__(self) -> str:
24 return "missing"
26 def __reduce__(self) -> str:
27 return "missing"
30missing: t.Any = _MissingType()
31"""Special singleton representing missing values for the runtime."""
33internal_code: t.MutableSet[CodeType] = set()
35concat = "".join
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.
42 Can be used on functions, filters, and tests.
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`.
48 .. versionadded:: 3.0.0
49 Replaces ``contextfunction`` and ``contextfilter``.
50 """
51 f.jinja_pass_arg = _PassArg.context # type: ignore
52 return f
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`.
60 Can be used on functions, filters, and tests.
62 If only ``EvalContext.environment`` is needed, use
63 :func:`pass_environment`.
65 .. versionadded:: 3.0.0
66 Replaces ``evalcontextfunction`` and ``evalcontextfilter``.
67 """
68 f.jinja_pass_arg = _PassArg.eval_context # type: ignore
69 return f
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.
76 Can be used on functions, filters, and tests.
78 .. versionadded:: 3.0.0
79 Replaces ``environmentfunction`` and ``environmentfilter``.
80 """
81 f.jinja_pass_arg = _PassArg.environment # type: ignore
82 return f
85class _PassArg(enum.Enum):
86 context = enum.auto()
87 eval_context = enum.auto()
88 environment = enum.auto()
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
95 return None
98def internalcode(f: F) -> F:
99 """Marks the function as internally used"""
100 internal_code.add(f.__code__)
101 return f
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::
111 def default(var, default=''):
112 if is_undefined(var):
113 return default
114 return var
115 """
116 from .runtime import Undefined
118 return isinstance(obj, Undefined)
121def consume(iterable: t.Iterable[t.Any]) -> None:
122 """Consumes an iterable without doing anything with it."""
123 for _ in iterable:
124 pass
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
136 get_spontaneous_environment.cache_clear()
137 _lexer_cache.clear()
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``).
146 If the `silent` is True the return value will be `None` if the import
147 fails.
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
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
171 return open(filename, mode)
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"
184 cls = type(obj)
186 if cls.__module__ == "builtins":
187 return f"{cls.__name__} object"
189 return f"{cls.__module__}.{cls.__name__} object"
192def pformat(obj: t.Any) -> str:
193 """Format an object using :func:`pprint.pformat`."""
194 from pprint import pformat
196 return pformat(obj)
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+$")
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.
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.
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``).
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.
257 .. versionchanged:: 3.0
258 The ``extra_schemes`` parameter was added.
260 .. versionchanged:: 3.0
261 Generate ``https://`` links for URLs without a scheme.
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:
270 def trim_url(x: str) -> str:
271 if len(x) > trim_url_limit:
272 return f"{x[:trim_url_limit]}..."
274 return x
276 else:
278 def trim_url(x: str) -> str:
279 return x
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 ""
285 for i, word in enumerate(words):
286 head, middle, tail = "", word, ""
287 match = re.match(r"^([(<]|<)+", middle)
289 if match:
290 head = match.group()
291 middle = middle[match.end() :]
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)
299 if match:
300 tail = match.group()
301 middle = middle[: match.start()]
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)
308 if start_count <= middle.count(end_char):
309 # Balanced, or lighter on the left
310 continue
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:]
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 )
330 elif middle.startswith("mailto:") and _email_re.match(middle[7:]):
331 middle = f'<a href="{middle}">{middle[7:]}</a>'
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>'
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>'
348 words[i] = f"{head}{middle}{tail}"
350 return "".join(words)
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
359 words = LOREM_IPSUM_WORDS.split()
360 result = []
362 for _ in range(n):
363 next_capitalized = True
364 last_comma = last_fullstop = 0
365 word = None
366 last = None
367 p = []
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)
391 # ensure that the paragraph ends with a dot.
392 p_str = " ".join(p)
394 if p_str.endswith(","):
395 p_str = p_str[:-1] + "."
396 elif not p_str.endswith("."):
397 p_str += "."
399 result.append(p_str)
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 )
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.
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)
420 obj = obj.encode(charset)
422 safe = b"" if for_qs else b"/"
423 rv = quote_from_bytes(obj, safe)
425 if for_qs:
426 rv = rv.replace("%20", "+")
428 return rv
431@abc.MutableMapping.register
432class LRUCache:
433 """A simple LRU Cache implementation."""
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.
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()
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
453 def __getstate__(self) -> t.Mapping[str, t.Any]:
454 return {
455 "capacity": self.capacity,
456 "_mapping": self._mapping,
457 "_queue": self._queue,
458 }
460 def __setstate__(self, d: t.Mapping[str, t.Any]) -> None:
461 self.__dict__.update(d)
462 self._postinit()
464 def __getnewargs__(self) -> t.Tuple[t.Any, ...]:
465 return (self.capacity,)
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
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
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
491 def clear(self) -> None:
492 """Clear the cache."""
493 with self._wlock:
494 self._mapping.clear()
495 self._queue.clear()
497 def __contains__(self, key: t.Any) -> bool:
498 """Check if a key exists in this cache."""
499 return key in self._mapping
501 def __len__(self) -> int:
502 """Return the current size of the cache."""
503 return len(self._mapping)
505 def __repr__(self) -> str:
506 return f"<{type(self).__name__} {self._mapping!r}>"
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.
512 Raise a `KeyError` if it does not exist.
513 """
514 with self._wlock:
515 rv = self._mapping[key]
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
526 self._append(key)
528 return rv
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()]
540 self._append(key)
541 self._mapping[key] = value
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]
550 try:
551 self._remove(key)
552 except ValueError:
553 pass
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
561 def values(self) -> t.Iterable[t.Any]:
562 """Return a list of all values."""
563 return [x[1] for x in self.items()]
565 def keys(self) -> t.Iterable[t.Any]:
566 """Return a list of all keys ordered by most recent usage."""
567 return list(self)
569 def __iter__(self) -> t.Iterator[t.Any]:
570 return reversed(tuple(self._queue))
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))
578 __copy__ = copy
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.
591 If you want to enable it for all templates created from strings or
592 for all templates with `.html` and `.xml` extensions::
594 from jinja2 import Environment, select_autoescape
595 env = Environment(autoescape=select_autoescape(
596 enabled_extensions=('html', 'xml'),
597 default_for_string=True,
598 ))
600 Example configuration to turn it on at all times except if the template
601 ends with `.txt`::
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 ))
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`.
617 For security reasons this function operates case insensitive.
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)
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
634 return autoescape
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`.
644 This is available in templates as the ``|tojson`` filter.
646 The following characters are escaped: ``<``, ``>``, ``&``, ``'``.
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.
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"]``.
660 .. versionchanged:: 3.0
661 The ``dumper`` parameter is renamed to ``dumps``.
663 .. versionadded:: 2.9
664 """
665 if dumps is None:
666 dumps = json.dumps
668 return markupsafe.Markup(
669 dumps(obj, **kwargs)
670 .replace("<", "\\u003c")
671 .replace(">", "\\u003e")
672 .replace("&", "\\u0026")
673 .replace("'", "\\u0027")
674 )
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.
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.
685 .. code-block:: html+jinja
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>
697 :param items: Each positional argument will be yielded in the order
698 given for each cycle.
700 .. versionadded:: 2.1
701 """
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
709 def reset(self) -> None:
710 """Resets the current item to the first item."""
711 self.pos = 0
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]
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
728 __next__ = next
731class Joiner:
732 """A joining helper for templates."""
734 def __init__(self, sep: str = ", ") -> None:
735 self.sep = sep
736 self.used = False
738 def __call__(self) -> str:
739 if not self.used:
740 self.used = True
741 return ""
742 return self.sep
745class Namespace:
746 """A namespace object that can hold arbitrary attributes. It may be
747 initialized from a dictionary or with keyword arguments."""
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)
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
762 def __setitem__(self, name: str, value: t.Any) -> None:
763 self.__attrs[name] = value
765 def __repr__(self) -> str:
766 return f"<Namespace {self.__attrs!r}>"