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