Загрузить файлы в «venv/Lib/site-packages/jinja2»
This commit is contained in:
BIN
venv/Lib/site-packages/jinja2/py.typed
Normal file
BIN
venv/Lib/site-packages/jinja2/py.typed
Normal file
Binary file not shown.
1062
venv/Lib/site-packages/jinja2/runtime.py
Normal file
1062
venv/Lib/site-packages/jinja2/runtime.py
Normal file
File diff suppressed because it is too large
Load Diff
436
venv/Lib/site-packages/jinja2/sandbox.py
Normal file
436
venv/Lib/site-packages/jinja2/sandbox.py
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
"""A sandbox layer that ensures unsafe operations cannot be performed.
|
||||||
|
Useful when the template itself comes from an untrusted source.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import operator
|
||||||
|
import types
|
||||||
|
import typing as t
|
||||||
|
from _string import formatter_field_name_split # type: ignore
|
||||||
|
from collections import abc
|
||||||
|
from collections import deque
|
||||||
|
from functools import update_wrapper
|
||||||
|
from string import Formatter
|
||||||
|
|
||||||
|
from markupsafe import EscapeFormatter
|
||||||
|
from markupsafe import Markup
|
||||||
|
|
||||||
|
from .environment import Environment
|
||||||
|
from .exceptions import SecurityError
|
||||||
|
from .runtime import Context
|
||||||
|
from .runtime import Undefined
|
||||||
|
|
||||||
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||||
|
|
||||||
|
#: maximum number of items a range may produce
|
||||||
|
MAX_RANGE = 100000
|
||||||
|
|
||||||
|
#: Unsafe function attributes.
|
||||||
|
UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set()
|
||||||
|
|
||||||
|
#: Unsafe method attributes. Function attributes are unsafe for methods too.
|
||||||
|
UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set()
|
||||||
|
|
||||||
|
#: unsafe generator attributes.
|
||||||
|
UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"}
|
||||||
|
|
||||||
|
#: unsafe attributes on coroutines
|
||||||
|
UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"}
|
||||||
|
|
||||||
|
#: unsafe attributes on async generators
|
||||||
|
UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"}
|
||||||
|
|
||||||
|
_mutable_spec: t.Tuple[t.Tuple[t.Type[t.Any], t.FrozenSet[str]], ...] = (
|
||||||
|
(
|
||||||
|
abc.MutableSet,
|
||||||
|
frozenset(
|
||||||
|
[
|
||||||
|
"add",
|
||||||
|
"clear",
|
||||||
|
"difference_update",
|
||||||
|
"discard",
|
||||||
|
"pop",
|
||||||
|
"remove",
|
||||||
|
"symmetric_difference_update",
|
||||||
|
"update",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
abc.MutableMapping,
|
||||||
|
frozenset(["clear", "pop", "popitem", "setdefault", "update"]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
abc.MutableSequence,
|
||||||
|
frozenset(
|
||||||
|
["append", "clear", "pop", "reverse", "insert", "sort", "extend", "remove"]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
deque,
|
||||||
|
frozenset(
|
||||||
|
[
|
||||||
|
"append",
|
||||||
|
"appendleft",
|
||||||
|
"clear",
|
||||||
|
"extend",
|
||||||
|
"extendleft",
|
||||||
|
"pop",
|
||||||
|
"popleft",
|
||||||
|
"remove",
|
||||||
|
"rotate",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def safe_range(*args: int) -> range:
|
||||||
|
"""A range that can't generate ranges with a length of more than
|
||||||
|
MAX_RANGE items.
|
||||||
|
"""
|
||||||
|
rng = range(*args)
|
||||||
|
|
||||||
|
if len(rng) > MAX_RANGE:
|
||||||
|
raise OverflowError(
|
||||||
|
"Range too big. The sandbox blocks ranges larger than"
|
||||||
|
f" MAX_RANGE ({MAX_RANGE})."
|
||||||
|
)
|
||||||
|
|
||||||
|
return rng
|
||||||
|
|
||||||
|
|
||||||
|
def unsafe(f: F) -> F:
|
||||||
|
"""Marks a function or method as unsafe.
|
||||||
|
|
||||||
|
.. code-block: python
|
||||||
|
|
||||||
|
@unsafe
|
||||||
|
def delete(self):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
f.unsafe_callable = True # type: ignore
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def is_internal_attribute(obj: t.Any, attr: str) -> bool:
|
||||||
|
"""Test if the attribute given is an internal python attribute. For
|
||||||
|
example this function returns `True` for the `func_code` attribute of
|
||||||
|
python objects. This is useful if the environment method
|
||||||
|
:meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
|
||||||
|
|
||||||
|
>>> from jinja2.sandbox import is_internal_attribute
|
||||||
|
>>> is_internal_attribute(str, "mro")
|
||||||
|
True
|
||||||
|
>>> is_internal_attribute(str, "upper")
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
if isinstance(obj, types.FunctionType):
|
||||||
|
if attr in UNSAFE_FUNCTION_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
elif isinstance(obj, types.MethodType):
|
||||||
|
if attr in UNSAFE_FUNCTION_ATTRIBUTES or attr in UNSAFE_METHOD_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
elif isinstance(obj, type):
|
||||||
|
if attr == "mro":
|
||||||
|
return True
|
||||||
|
elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)):
|
||||||
|
return True
|
||||||
|
elif isinstance(obj, types.GeneratorType):
|
||||||
|
if attr in UNSAFE_GENERATOR_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
elif hasattr(types, "CoroutineType") and isinstance(obj, types.CoroutineType):
|
||||||
|
if attr in UNSAFE_COROUTINE_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
elif hasattr(types, "AsyncGeneratorType") and isinstance(
|
||||||
|
obj, types.AsyncGeneratorType
|
||||||
|
):
|
||||||
|
if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
return attr.startswith("__")
|
||||||
|
|
||||||
|
|
||||||
|
def modifies_known_mutable(obj: t.Any, attr: str) -> bool:
|
||||||
|
"""This function checks if an attribute on a builtin mutable object
|
||||||
|
(list, dict, set or deque) or the corresponding ABCs would modify it
|
||||||
|
if called.
|
||||||
|
|
||||||
|
>>> modifies_known_mutable({}, "clear")
|
||||||
|
True
|
||||||
|
>>> modifies_known_mutable({}, "keys")
|
||||||
|
False
|
||||||
|
>>> modifies_known_mutable([], "append")
|
||||||
|
True
|
||||||
|
>>> modifies_known_mutable([], "index")
|
||||||
|
False
|
||||||
|
|
||||||
|
If called with an unsupported object, ``False`` is returned.
|
||||||
|
|
||||||
|
>>> modifies_known_mutable("foo", "upper")
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
for typespec, unsafe in _mutable_spec:
|
||||||
|
if isinstance(obj, typespec):
|
||||||
|
return attr in unsafe
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class SandboxedEnvironment(Environment):
|
||||||
|
"""The sandboxed environment. It works like the regular environment but
|
||||||
|
tells the compiler to generate sandboxed code. Additionally subclasses of
|
||||||
|
this environment may override the methods that tell the runtime what
|
||||||
|
attributes or functions are safe to access.
|
||||||
|
|
||||||
|
If the template tries to access insecure code a :exc:`SecurityError` is
|
||||||
|
raised. However also other exceptions may occur during the rendering so
|
||||||
|
the caller has to ensure that all exceptions are caught.
|
||||||
|
"""
|
||||||
|
|
||||||
|
sandboxed = True
|
||||||
|
|
||||||
|
#: default callback table for the binary operators. A copy of this is
|
||||||
|
#: available on each instance of a sandboxed environment as
|
||||||
|
#: :attr:`binop_table`
|
||||||
|
default_binop_table: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
||||||
|
"+": operator.add,
|
||||||
|
"-": operator.sub,
|
||||||
|
"*": operator.mul,
|
||||||
|
"/": operator.truediv,
|
||||||
|
"//": operator.floordiv,
|
||||||
|
"**": operator.pow,
|
||||||
|
"%": operator.mod,
|
||||||
|
}
|
||||||
|
|
||||||
|
#: default callback table for the unary operators. A copy of this is
|
||||||
|
#: available on each instance of a sandboxed environment as
|
||||||
|
#: :attr:`unop_table`
|
||||||
|
default_unop_table: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
|
||||||
|
"+": operator.pos,
|
||||||
|
"-": operator.neg,
|
||||||
|
}
|
||||||
|
|
||||||
|
#: a set of binary operators that should be intercepted. Each operator
|
||||||
|
#: that is added to this set (empty by default) is delegated to the
|
||||||
|
#: :meth:`call_binop` method that will perform the operator. The default
|
||||||
|
#: operator callback is specified by :attr:`binop_table`.
|
||||||
|
#:
|
||||||
|
#: The following binary operators are interceptable:
|
||||||
|
#: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
|
||||||
|
#:
|
||||||
|
#: The default operation form the operator table corresponds to the
|
||||||
|
#: builtin function. Intercepted calls are always slower than the native
|
||||||
|
#: operator call, so make sure only to intercept the ones you are
|
||||||
|
#: interested in.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 2.6
|
||||||
|
intercepted_binops: t.FrozenSet[str] = frozenset()
|
||||||
|
|
||||||
|
#: a set of unary operators that should be intercepted. Each operator
|
||||||
|
#: that is added to this set (empty by default) is delegated to the
|
||||||
|
#: :meth:`call_unop` method that will perform the operator. The default
|
||||||
|
#: operator callback is specified by :attr:`unop_table`.
|
||||||
|
#:
|
||||||
|
#: The following unary operators are interceptable: ``+``, ``-``
|
||||||
|
#:
|
||||||
|
#: The default operation form the operator table corresponds to the
|
||||||
|
#: builtin function. Intercepted calls are always slower than the native
|
||||||
|
#: operator call, so make sure only to intercept the ones you are
|
||||||
|
#: interested in.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 2.6
|
||||||
|
intercepted_unops: t.FrozenSet[str] = frozenset()
|
||||||
|
|
||||||
|
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.globals["range"] = safe_range
|
||||||
|
self.binop_table = self.default_binop_table.copy()
|
||||||
|
self.unop_table = self.default_unop_table.copy()
|
||||||
|
|
||||||
|
def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
|
||||||
|
"""The sandboxed environment will call this method to check if the
|
||||||
|
attribute of an object is safe to access. Per default all attributes
|
||||||
|
starting with an underscore are considered private as well as the
|
||||||
|
special attributes of internal python objects as returned by the
|
||||||
|
:func:`is_internal_attribute` function.
|
||||||
|
"""
|
||||||
|
return not (attr.startswith("_") or is_internal_attribute(obj, attr))
|
||||||
|
|
||||||
|
def is_safe_callable(self, obj: t.Any) -> bool:
|
||||||
|
"""Check if an object is safely callable. By default callables
|
||||||
|
are considered safe unless decorated with :func:`unsafe`.
|
||||||
|
|
||||||
|
This also recognizes the Django convention of setting
|
||||||
|
``func.alters_data = True``.
|
||||||
|
"""
|
||||||
|
return not (
|
||||||
|
getattr(obj, "unsafe_callable", False) or getattr(obj, "alters_data", False)
|
||||||
|
)
|
||||||
|
|
||||||
|
def call_binop(
|
||||||
|
self, context: Context, operator: str, left: t.Any, right: t.Any
|
||||||
|
) -> t.Any:
|
||||||
|
"""For intercepted binary operator calls (:meth:`intercepted_binops`)
|
||||||
|
this function is executed instead of the builtin operator. This can
|
||||||
|
be used to fine tune the behavior of certain operators.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
"""
|
||||||
|
return self.binop_table[operator](left, right)
|
||||||
|
|
||||||
|
def call_unop(self, context: Context, operator: str, arg: t.Any) -> t.Any:
|
||||||
|
"""For intercepted unary operator calls (:meth:`intercepted_unops`)
|
||||||
|
this function is executed instead of the builtin operator. This can
|
||||||
|
be used to fine tune the behavior of certain operators.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
"""
|
||||||
|
return self.unop_table[operator](arg)
|
||||||
|
|
||||||
|
def getitem(
|
||||||
|
self, obj: t.Any, argument: t.Union[str, t.Any]
|
||||||
|
) -> t.Union[t.Any, Undefined]:
|
||||||
|
"""Subscribe an object from sandboxed code."""
|
||||||
|
try:
|
||||||
|
return obj[argument]
|
||||||
|
except (TypeError, LookupError):
|
||||||
|
if isinstance(argument, str):
|
||||||
|
try:
|
||||||
|
attr = str(argument)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
value = getattr(obj, attr)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
fmt = self.wrap_str_format(value)
|
||||||
|
if fmt is not None:
|
||||||
|
return fmt
|
||||||
|
if self.is_safe_attribute(obj, argument, value):
|
||||||
|
return value
|
||||||
|
return self.unsafe_undefined(obj, argument)
|
||||||
|
return self.undefined(obj=obj, name=argument)
|
||||||
|
|
||||||
|
def getattr(self, obj: t.Any, attribute: str) -> t.Union[t.Any, Undefined]:
|
||||||
|
"""Subscribe an object from sandboxed code and prefer the
|
||||||
|
attribute. The attribute passed *must* be a bytestring.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
value = getattr(obj, attribute)
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
return obj[attribute]
|
||||||
|
except (TypeError, LookupError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
fmt = self.wrap_str_format(value)
|
||||||
|
if fmt is not None:
|
||||||
|
return fmt
|
||||||
|
if self.is_safe_attribute(obj, attribute, value):
|
||||||
|
return value
|
||||||
|
return self.unsafe_undefined(obj, attribute)
|
||||||
|
return self.undefined(obj=obj, name=attribute)
|
||||||
|
|
||||||
|
def unsafe_undefined(self, obj: t.Any, attribute: str) -> Undefined:
|
||||||
|
"""Return an undefined object for unsafe attributes."""
|
||||||
|
return self.undefined(
|
||||||
|
f"access to attribute {attribute!r} of"
|
||||||
|
f" {type(obj).__name__!r} object is unsafe.",
|
||||||
|
name=attribute,
|
||||||
|
obj=obj,
|
||||||
|
exc=SecurityError,
|
||||||
|
)
|
||||||
|
|
||||||
|
def wrap_str_format(self, value: t.Any) -> t.Optional[t.Callable[..., str]]:
|
||||||
|
"""If the given value is a ``str.format`` or ``str.format_map`` method,
|
||||||
|
return a new function than handles sandboxing. This is done at access
|
||||||
|
rather than in :meth:`call`, so that calls made without ``call`` are
|
||||||
|
also sandboxed.
|
||||||
|
"""
|
||||||
|
if not isinstance(
|
||||||
|
value, (types.MethodType, types.BuiltinMethodType)
|
||||||
|
) or value.__name__ not in ("format", "format_map"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
f_self: t.Any = value.__self__
|
||||||
|
|
||||||
|
if not isinstance(f_self, str):
|
||||||
|
return None
|
||||||
|
|
||||||
|
str_type: t.Type[str] = type(f_self)
|
||||||
|
is_format_map = value.__name__ == "format_map"
|
||||||
|
formatter: SandboxedFormatter
|
||||||
|
|
||||||
|
if isinstance(f_self, Markup):
|
||||||
|
formatter = SandboxedEscapeFormatter(self, escape=f_self.escape)
|
||||||
|
else:
|
||||||
|
formatter = SandboxedFormatter(self)
|
||||||
|
|
||||||
|
vformat = formatter.vformat
|
||||||
|
|
||||||
|
def wrapper(*args: t.Any, **kwargs: t.Any) -> str:
|
||||||
|
if is_format_map:
|
||||||
|
if kwargs:
|
||||||
|
raise TypeError("format_map() takes no keyword arguments")
|
||||||
|
|
||||||
|
if len(args) != 1:
|
||||||
|
raise TypeError(
|
||||||
|
f"format_map() takes exactly one argument ({len(args)} given)"
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs = args[0]
|
||||||
|
args = ()
|
||||||
|
|
||||||
|
return str_type(vformat(f_self, args, kwargs))
|
||||||
|
|
||||||
|
return update_wrapper(wrapper, value)
|
||||||
|
|
||||||
|
def call(
|
||||||
|
__self, # noqa: B902
|
||||||
|
__context: Context,
|
||||||
|
__obj: t.Any,
|
||||||
|
*args: t.Any,
|
||||||
|
**kwargs: t.Any,
|
||||||
|
) -> t.Any:
|
||||||
|
"""Call an object from sandboxed code."""
|
||||||
|
|
||||||
|
# the double prefixes are to avoid double keyword argument
|
||||||
|
# errors when proxying the call.
|
||||||
|
if not __self.is_safe_callable(__obj):
|
||||||
|
raise SecurityError(f"{__obj!r} is not safely callable")
|
||||||
|
return __context.call(__obj, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ImmutableSandboxedEnvironment(SandboxedEnvironment):
|
||||||
|
"""Works exactly like the regular `SandboxedEnvironment` but does not
|
||||||
|
permit modifications on the builtin mutable objects `list`, `set`, and
|
||||||
|
`dict` by using the :func:`modifies_known_mutable` function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
|
||||||
|
if not super().is_safe_attribute(obj, attr, value):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return not modifies_known_mutable(obj, attr)
|
||||||
|
|
||||||
|
|
||||||
|
class SandboxedFormatter(Formatter):
|
||||||
|
def __init__(self, env: Environment, **kwargs: t.Any) -> None:
|
||||||
|
self._env = env
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
def get_field(
|
||||||
|
self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any]
|
||||||
|
) -> t.Tuple[t.Any, str]:
|
||||||
|
first, rest = formatter_field_name_split(field_name)
|
||||||
|
obj = self.get_value(first, args, kwargs)
|
||||||
|
for is_attr, i in rest:
|
||||||
|
if is_attr:
|
||||||
|
obj = self._env.getattr(obj, i)
|
||||||
|
else:
|
||||||
|
obj = self._env.getitem(obj, i)
|
||||||
|
return obj, first
|
||||||
|
|
||||||
|
|
||||||
|
class SandboxedEscapeFormatter(SandboxedFormatter, EscapeFormatter):
|
||||||
|
pass
|
||||||
256
venv/Lib/site-packages/jinja2/tests.py
Normal file
256
venv/Lib/site-packages/jinja2/tests.py
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
"""Built-in template tests used with the ``is`` operator."""
|
||||||
|
|
||||||
|
import operator
|
||||||
|
import typing as t
|
||||||
|
from collections import abc
|
||||||
|
from numbers import Number
|
||||||
|
|
||||||
|
from .runtime import Undefined
|
||||||
|
from .utils import pass_environment
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
from .environment import Environment
|
||||||
|
|
||||||
|
|
||||||
|
def test_odd(value: int) -> bool:
|
||||||
|
"""Return true if the variable is odd."""
|
||||||
|
return value % 2 == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_even(value: int) -> bool:
|
||||||
|
"""Return true if the variable is even."""
|
||||||
|
return value % 2 == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_divisibleby(value: int, num: int) -> bool:
|
||||||
|
"""Check if a variable is divisible by a number."""
|
||||||
|
return value % num == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_defined(value: t.Any) -> bool:
|
||||||
|
"""Return true if the variable is defined:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{% if variable is defined %}
|
||||||
|
value of variable: {{ variable }}
|
||||||
|
{% else %}
|
||||||
|
variable is not defined
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
See the :func:`default` filter for a simple way to set undefined
|
||||||
|
variables.
|
||||||
|
"""
|
||||||
|
return not isinstance(value, Undefined)
|
||||||
|
|
||||||
|
|
||||||
|
def test_undefined(value: t.Any) -> bool:
|
||||||
|
"""Like :func:`defined` but the other way round."""
|
||||||
|
return isinstance(value, Undefined)
|
||||||
|
|
||||||
|
|
||||||
|
@pass_environment
|
||||||
|
def test_filter(env: "Environment", value: str) -> bool:
|
||||||
|
"""Check if a filter exists by name. Useful if a filter may be
|
||||||
|
optionally available.
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{% if 'markdown' is filter %}
|
||||||
|
{{ value | markdown }}
|
||||||
|
{% else %}
|
||||||
|
{{ value }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
"""
|
||||||
|
return value in env.filters
|
||||||
|
|
||||||
|
|
||||||
|
@pass_environment
|
||||||
|
def test_test(env: "Environment", value: str) -> bool:
|
||||||
|
"""Check if a test exists by name. Useful if a test may be
|
||||||
|
optionally available.
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{% if 'loud' is test %}
|
||||||
|
{% if value is loud %}
|
||||||
|
{{ value|upper }}
|
||||||
|
{% else %}
|
||||||
|
{{ value|lower }}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{{ value }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
"""
|
||||||
|
return value in env.tests
|
||||||
|
|
||||||
|
|
||||||
|
def test_none(value: t.Any) -> bool:
|
||||||
|
"""Return true if the variable is none."""
|
||||||
|
return value is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_boolean(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is a boolean value.
|
||||||
|
|
||||||
|
.. versionadded:: 2.11
|
||||||
|
"""
|
||||||
|
return value is True or value is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_false(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is False.
|
||||||
|
|
||||||
|
.. versionadded:: 2.11
|
||||||
|
"""
|
||||||
|
return value is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_true(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is True.
|
||||||
|
|
||||||
|
.. versionadded:: 2.11
|
||||||
|
"""
|
||||||
|
return value is True
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: The existing 'number' test matches booleans and floats
|
||||||
|
def test_integer(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is an integer.
|
||||||
|
|
||||||
|
.. versionadded:: 2.11
|
||||||
|
"""
|
||||||
|
return isinstance(value, int) and value is not True and value is not False
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: The existing 'number' test matches booleans and integers
|
||||||
|
def test_float(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is a float.
|
||||||
|
|
||||||
|
.. versionadded:: 2.11
|
||||||
|
"""
|
||||||
|
return isinstance(value, float)
|
||||||
|
|
||||||
|
|
||||||
|
def test_lower(value: str) -> bool:
|
||||||
|
"""Return true if the variable is lowercased."""
|
||||||
|
return str(value).islower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_upper(value: str) -> bool:
|
||||||
|
"""Return true if the variable is uppercased."""
|
||||||
|
return str(value).isupper()
|
||||||
|
|
||||||
|
|
||||||
|
def test_string(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is a string."""
|
||||||
|
return isinstance(value, str)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mapping(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is a mapping (dict etc.).
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
"""
|
||||||
|
return isinstance(value, abc.Mapping)
|
||||||
|
|
||||||
|
|
||||||
|
def test_number(value: t.Any) -> bool:
|
||||||
|
"""Return true if the variable is a number."""
|
||||||
|
return isinstance(value, Number)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sequence(value: t.Any) -> bool:
|
||||||
|
"""Return true if the variable is a sequence. Sequences are variables
|
||||||
|
that are iterable.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
len(value)
|
||||||
|
value.__getitem__ # noqa B018
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def test_sameas(value: t.Any, other: t.Any) -> bool:
|
||||||
|
"""Check if an object points to the same memory address than another
|
||||||
|
object:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{% if foo.attribute is sameas false %}
|
||||||
|
the foo attribute really is the `False` singleton
|
||||||
|
{% endif %}
|
||||||
|
"""
|
||||||
|
return value is other
|
||||||
|
|
||||||
|
|
||||||
|
def test_iterable(value: t.Any) -> bool:
|
||||||
|
"""Check if it's possible to iterate over an object."""
|
||||||
|
try:
|
||||||
|
iter(value)
|
||||||
|
except TypeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def test_escaped(value: t.Any) -> bool:
|
||||||
|
"""Check if the value is escaped."""
|
||||||
|
return hasattr(value, "__html__")
|
||||||
|
|
||||||
|
|
||||||
|
def test_in(value: t.Any, seq: t.Container[t.Any]) -> bool:
|
||||||
|
"""Check if value is in seq.
|
||||||
|
|
||||||
|
.. versionadded:: 2.10
|
||||||
|
"""
|
||||||
|
return value in seq
|
||||||
|
|
||||||
|
|
||||||
|
TESTS = {
|
||||||
|
"odd": test_odd,
|
||||||
|
"even": test_even,
|
||||||
|
"divisibleby": test_divisibleby,
|
||||||
|
"defined": test_defined,
|
||||||
|
"undefined": test_undefined,
|
||||||
|
"filter": test_filter,
|
||||||
|
"test": test_test,
|
||||||
|
"none": test_none,
|
||||||
|
"boolean": test_boolean,
|
||||||
|
"false": test_false,
|
||||||
|
"true": test_true,
|
||||||
|
"integer": test_integer,
|
||||||
|
"float": test_float,
|
||||||
|
"lower": test_lower,
|
||||||
|
"upper": test_upper,
|
||||||
|
"string": test_string,
|
||||||
|
"mapping": test_mapping,
|
||||||
|
"number": test_number,
|
||||||
|
"sequence": test_sequence,
|
||||||
|
"iterable": test_iterable,
|
||||||
|
"callable": callable,
|
||||||
|
"sameas": test_sameas,
|
||||||
|
"escaped": test_escaped,
|
||||||
|
"in": test_in,
|
||||||
|
"==": operator.eq,
|
||||||
|
"eq": operator.eq,
|
||||||
|
"equalto": operator.eq,
|
||||||
|
"!=": operator.ne,
|
||||||
|
"ne": operator.ne,
|
||||||
|
">": operator.gt,
|
||||||
|
"gt": operator.gt,
|
||||||
|
"greaterthan": operator.gt,
|
||||||
|
"ge": operator.ge,
|
||||||
|
">=": operator.ge,
|
||||||
|
"<": operator.lt,
|
||||||
|
"lt": operator.lt,
|
||||||
|
"lessthan": operator.lt,
|
||||||
|
"<=": operator.le,
|
||||||
|
"le": operator.le,
|
||||||
|
}
|
||||||
766
venv/Lib/site-packages/jinja2/utils.py
Normal file
766
venv/Lib/site-packages/jinja2/utils.py
Normal file
@@ -0,0 +1,766 @@
|
|||||||
|
import enum
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import typing as t
|
||||||
|
from collections import abc
|
||||||
|
from collections import deque
|
||||||
|
from random import choice
|
||||||
|
from random import randrange
|
||||||
|
from threading import Lock
|
||||||
|
from types import CodeType
|
||||||
|
from urllib.parse import quote_from_bytes
|
||||||
|
|
||||||
|
import markupsafe
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
|
|
||||||
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||||
|
|
||||||
|
|
||||||
|
class _MissingType:
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "missing"
|
||||||
|
|
||||||
|
def __reduce__(self) -> str:
|
||||||
|
return "missing"
|
||||||
|
|
||||||
|
|
||||||
|
missing: t.Any = _MissingType()
|
||||||
|
"""Special singleton representing missing values for the runtime."""
|
||||||
|
|
||||||
|
internal_code: t.MutableSet[CodeType] = set()
|
||||||
|
|
||||||
|
concat = "".join
|
||||||
|
|
||||||
|
|
||||||
|
def pass_context(f: F) -> F:
|
||||||
|
"""Pass the :class:`~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
|
||||||
|
:func:`pass_eval_context`. If only ``Context.environment`` is
|
||||||
|
needed, use :func:`pass_environment`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0.0
|
||||||
|
Replaces ``contextfunction`` and ``contextfilter``.
|
||||||
|
"""
|
||||||
|
f.jinja_pass_arg = _PassArg.context # type: ignore
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def pass_eval_context(f: F) -> F:
|
||||||
|
"""Pass the :class:`~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
|
||||||
|
:func:`pass_environment`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0.0
|
||||||
|
Replaces ``evalcontextfunction`` and ``evalcontextfilter``.
|
||||||
|
"""
|
||||||
|
f.jinja_pass_arg = _PassArg.eval_context # type: ignore
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def pass_environment(f: F) -> F:
|
||||||
|
"""Pass the :class:`~jinja2.Environment` as the first argument to
|
||||||
|
the decorated function when called while rendering a template.
|
||||||
|
|
||||||
|
Can be used on functions, filters, and tests.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0.0
|
||||||
|
Replaces ``environmentfunction`` and ``environmentfilter``.
|
||||||
|
"""
|
||||||
|
f.jinja_pass_arg = _PassArg.environment # type: ignore
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
class _PassArg(enum.Enum):
|
||||||
|
context = enum.auto()
|
||||||
|
eval_context = enum.auto()
|
||||||
|
environment = enum.auto()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_obj(cls, obj: F) -> t.Optional["_PassArg"]:
|
||||||
|
if hasattr(obj, "jinja_pass_arg"):
|
||||||
|
return obj.jinja_pass_arg # type: ignore
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def internalcode(f: F) -> F:
|
||||||
|
"""Marks the function as internally used"""
|
||||||
|
internal_code.add(f.__code__)
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def is_undefined(obj: t.Any) -> bool:
|
||||||
|
"""Check if the object passed is undefined. This does nothing more than
|
||||||
|
performing an instance check against :class:`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
|
||||||
|
"""
|
||||||
|
from .runtime import Undefined
|
||||||
|
|
||||||
|
return isinstance(obj, Undefined)
|
||||||
|
|
||||||
|
|
||||||
|
def consume(iterable: t.Iterable[t.Any]) -> None:
|
||||||
|
"""Consumes an iterable without doing anything with it."""
|
||||||
|
for _ in iterable:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def clear_caches() -> None:
|
||||||
|
"""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.
|
||||||
|
"""
|
||||||
|
from .environment import get_spontaneous_environment
|
||||||
|
from .lexer import _lexer_cache
|
||||||
|
|
||||||
|
get_spontaneous_environment.cache_clear()
|
||||||
|
_lexer_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
|
def import_string(import_name: str, silent: bool = False) -> t.Any:
|
||||||
|
"""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.
|
||||||
|
|
||||||
|
:return: imported object
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if ":" in import_name:
|
||||||
|
module, obj = import_name.split(":", 1)
|
||||||
|
elif "." in import_name:
|
||||||
|
module, _, obj = import_name.rpartition(".")
|
||||||
|
else:
|
||||||
|
return __import__(import_name)
|
||||||
|
return getattr(__import__(module, None, None, [obj]), obj)
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
if not silent:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO[t.Any]]:
|
||||||
|
"""Returns a file descriptor for the filename if that file exists,
|
||||||
|
otherwise ``None``.
|
||||||
|
"""
|
||||||
|
if not os.path.isfile(filename):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return open(filename, mode)
|
||||||
|
|
||||||
|
|
||||||
|
def object_type_repr(obj: t.Any) -> str:
|
||||||
|
"""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`).
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return "None"
|
||||||
|
elif obj is Ellipsis:
|
||||||
|
return "Ellipsis"
|
||||||
|
|
||||||
|
cls = type(obj)
|
||||||
|
|
||||||
|
if cls.__module__ == "builtins":
|
||||||
|
return f"{cls.__name__} object"
|
||||||
|
|
||||||
|
return f"{cls.__module__}.{cls.__name__} object"
|
||||||
|
|
||||||
|
|
||||||
|
def pformat(obj: t.Any) -> str:
|
||||||
|
"""Format an object using :func:`pprint.pformat`."""
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
return pformat(obj)
|
||||||
|
|
||||||
|
|
||||||
|
_http_re = re.compile(
|
||||||
|
r"""
|
||||||
|
^
|
||||||
|
(
|
||||||
|
(https?://|www\.) # scheme or www
|
||||||
|
(([\w%-]+\.)+)? # subdomain
|
||||||
|
(
|
||||||
|
[a-z]{2,63} # basic tld
|
||||||
|
|
|
||||||
|
xn--[\w%]{2,59} # idna tld
|
||||||
|
)
|
||||||
|
|
|
||||||
|
([\w%-]{2,63}\.)+ # basic domain
|
||||||
|
(com|net|int|edu|gov|org|info|mil) # basic tld
|
||||||
|
|
|
||||||
|
(https?://) # scheme
|
||||||
|
(
|
||||||
|
(([\d]{1,3})(\.[\d]{1,3}){3}) # IPv4
|
||||||
|
|
|
||||||
|
(\[([\da-f]{0,4}:){2}([\da-f]{0,4}:?){1,6}]) # IPv6
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(?::[\d]{1,5})? # port
|
||||||
|
(?:[/?#]\S*)? # path, query, and fragment
|
||||||
|
$
|
||||||
|
""",
|
||||||
|
re.IGNORECASE | re.VERBOSE,
|
||||||
|
)
|
||||||
|
_email_re = re.compile(r"^\S+@\w[\w.-]*\.\w+$")
|
||||||
|
|
||||||
|
|
||||||
|
def urlize(
|
||||||
|
text: str,
|
||||||
|
trim_url_limit: t.Optional[int] = None,
|
||||||
|
rel: t.Optional[str] = None,
|
||||||
|
target: t.Optional[str] = None,
|
||||||
|
extra_schemes: t.Optional[t.Iterable[str]] = None,
|
||||||
|
) -> str:
|
||||||
|
"""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``).
|
||||||
|
|
||||||
|
:param text: Original text containing URLs to link.
|
||||||
|
:param trim_url_limit: Shorten displayed URL values to this length.
|
||||||
|
:param target: Add the ``target`` attribute to links.
|
||||||
|
:param rel: Add the ``rel`` attribute to links.
|
||||||
|
:param extra_schemes: Recognize URLs that start with these schemes
|
||||||
|
in addition to the default behavior.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The ``extra_schemes`` parameter was added.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
Generate ``https://`` links for URLs without a scheme.
|
||||||
|
|
||||||
|
.. versionchanged:: 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.
|
||||||
|
"""
|
||||||
|
if trim_url_limit is not None:
|
||||||
|
|
||||||
|
def trim_url(x: str) -> str:
|
||||||
|
if len(x) > trim_url_limit:
|
||||||
|
return f"{x[:trim_url_limit]}..."
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def trim_url(x: str) -> str:
|
||||||
|
return x
|
||||||
|
|
||||||
|
words = re.split(r"(\s+)", str(markupsafe.escape(text)))
|
||||||
|
rel_attr = f' rel="{markupsafe.escape(rel)}"' if rel else ""
|
||||||
|
target_attr = f' target="{markupsafe.escape(target)}"' if target else ""
|
||||||
|
|
||||||
|
for i, word in enumerate(words):
|
||||||
|
head, middle, tail = "", word, ""
|
||||||
|
match = re.match(r"^([(<]|<)+", middle)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
head = match.group()
|
||||||
|
middle = middle[match.end() :]
|
||||||
|
|
||||||
|
# Unlike lead, which is anchored to the start of the string,
|
||||||
|
# need to check that the string ends with any of the characters
|
||||||
|
# before trying to match all of them, to avoid backtracking.
|
||||||
|
if middle.endswith((")", ">", ".", ",", "\n", ">")):
|
||||||
|
match = re.search(r"([)>.,\n]|>)+$", middle)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
tail = match.group()
|
||||||
|
middle = middle[: match.start()]
|
||||||
|
|
||||||
|
# Prefer balancing parentheses in URLs instead of ignoring a
|
||||||
|
# trailing character.
|
||||||
|
for start_char, end_char in ("(", ")"), ("<", ">"), ("<", ">"):
|
||||||
|
start_count = middle.count(start_char)
|
||||||
|
|
||||||
|
if start_count <= middle.count(end_char):
|
||||||
|
# Balanced, or lighter on the left
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Move as many as possible from the tail to balance
|
||||||
|
for _ in range(min(start_count, tail.count(end_char))):
|
||||||
|
end_index = tail.index(end_char) + len(end_char)
|
||||||
|
# Move anything in the tail before the end char too
|
||||||
|
middle += tail[:end_index]
|
||||||
|
tail = tail[end_index:]
|
||||||
|
|
||||||
|
if _http_re.match(middle):
|
||||||
|
if middle.startswith("https://") or middle.startswith("http://"):
|
||||||
|
middle = (
|
||||||
|
f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
middle = (
|
||||||
|
f'<a href="https://{middle}"{rel_attr}{target_attr}>'
|
||||||
|
f"{trim_url(middle)}</a>"
|
||||||
|
)
|
||||||
|
|
||||||
|
elif middle.startswith("mailto:") and _email_re.match(middle[7:]):
|
||||||
|
middle = f'<a href="{middle}">{middle[7:]}</a>'
|
||||||
|
|
||||||
|
elif (
|
||||||
|
"@" in middle
|
||||||
|
and not middle.startswith("www.")
|
||||||
|
# ignore values like `@a@b`
|
||||||
|
and not middle.startswith("@")
|
||||||
|
and ":" not in middle
|
||||||
|
and _email_re.match(middle)
|
||||||
|
):
|
||||||
|
middle = f'<a href="mailto:{middle}">{middle}</a>'
|
||||||
|
|
||||||
|
elif extra_schemes is not None:
|
||||||
|
for scheme in extra_schemes:
|
||||||
|
if middle != scheme and middle.startswith(scheme):
|
||||||
|
middle = f'<a href="{middle}"{rel_attr}{target_attr}>{middle}</a>'
|
||||||
|
|
||||||
|
words[i] = f"{head}{middle}{tail}"
|
||||||
|
|
||||||
|
return "".join(words)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_lorem_ipsum(
|
||||||
|
n: int = 5, html: bool = True, min: int = 20, max: int = 100
|
||||||
|
) -> str:
|
||||||
|
"""Generate some lorem ipsum for the template."""
|
||||||
|
from .constants import LOREM_IPSUM_WORDS
|
||||||
|
|
||||||
|
words = LOREM_IPSUM_WORDS.split()
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for _ in range(n):
|
||||||
|
next_capitalized = True
|
||||||
|
last_comma = last_fullstop = 0
|
||||||
|
word = None
|
||||||
|
last = None
|
||||||
|
p = []
|
||||||
|
|
||||||
|
# each paragraph contains out of 20 to 100 words.
|
||||||
|
for idx, _ in enumerate(range(randrange(min, max))):
|
||||||
|
while True:
|
||||||
|
word = choice(words)
|
||||||
|
if word != last:
|
||||||
|
last = word
|
||||||
|
break
|
||||||
|
if next_capitalized:
|
||||||
|
word = word.capitalize()
|
||||||
|
next_capitalized = False
|
||||||
|
# add commas
|
||||||
|
if idx - randrange(3, 8) > last_comma:
|
||||||
|
last_comma = idx
|
||||||
|
last_fullstop += 2
|
||||||
|
word += ","
|
||||||
|
# add end of sentences
|
||||||
|
if idx - randrange(10, 20) > last_fullstop:
|
||||||
|
last_comma = last_fullstop = idx
|
||||||
|
word += "."
|
||||||
|
next_capitalized = True
|
||||||
|
p.append(word)
|
||||||
|
|
||||||
|
# ensure that the paragraph ends with a dot.
|
||||||
|
p_str = " ".join(p)
|
||||||
|
|
||||||
|
if p_str.endswith(","):
|
||||||
|
p_str = p_str[:-1] + "."
|
||||||
|
elif not p_str.endswith("."):
|
||||||
|
p_str += "."
|
||||||
|
|
||||||
|
result.append(p_str)
|
||||||
|
|
||||||
|
if not html:
|
||||||
|
return "\n\n".join(result)
|
||||||
|
return markupsafe.Markup(
|
||||||
|
"\n".join(f"<p>{markupsafe.escape(x)}</p>" for x in result)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str:
|
||||||
|
"""Quote a string for use in a URL using the given charset.
|
||||||
|
|
||||||
|
:param obj: String or bytes to quote. Other types are converted to
|
||||||
|
string then encoded to bytes using the given charset.
|
||||||
|
:param charset: Encode text to bytes using this charset.
|
||||||
|
:param for_qs: Quote "/" and use "+" for spaces.
|
||||||
|
"""
|
||||||
|
if not isinstance(obj, bytes):
|
||||||
|
if not isinstance(obj, str):
|
||||||
|
obj = str(obj)
|
||||||
|
|
||||||
|
obj = obj.encode(charset)
|
||||||
|
|
||||||
|
safe = b"" if for_qs else b"/"
|
||||||
|
rv = quote_from_bytes(obj, safe)
|
||||||
|
|
||||||
|
if for_qs:
|
||||||
|
rv = rv.replace("%20", "+")
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
@abc.MutableMapping.register
|
||||||
|
class LRUCache:
|
||||||
|
"""A simple LRU Cache implementation."""
|
||||||
|
|
||||||
|
# this is fast for small capacities (something below 1000) but doesn't
|
||||||
|
# scale. But as long as it's only used as storage for templates this
|
||||||
|
# won't do any harm.
|
||||||
|
|
||||||
|
def __init__(self, capacity: int) -> None:
|
||||||
|
self.capacity = capacity
|
||||||
|
self._mapping: t.Dict[t.Any, t.Any] = {}
|
||||||
|
self._queue: te.Deque[t.Any] = deque()
|
||||||
|
self._postinit()
|
||||||
|
|
||||||
|
def _postinit(self) -> None:
|
||||||
|
# alias all queue methods for faster lookup
|
||||||
|
self._popleft = self._queue.popleft
|
||||||
|
self._pop = self._queue.pop
|
||||||
|
self._remove = self._queue.remove
|
||||||
|
self._wlock = Lock()
|
||||||
|
self._append = self._queue.append
|
||||||
|
|
||||||
|
def __getstate__(self) -> t.Mapping[str, t.Any]:
|
||||||
|
return {
|
||||||
|
"capacity": self.capacity,
|
||||||
|
"_mapping": self._mapping,
|
||||||
|
"_queue": self._queue,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __setstate__(self, d: t.Mapping[str, t.Any]) -> None:
|
||||||
|
self.__dict__.update(d)
|
||||||
|
self._postinit()
|
||||||
|
|
||||||
|
def __getnewargs__(self) -> t.Tuple[t.Any, ...]:
|
||||||
|
return (self.capacity,)
|
||||||
|
|
||||||
|
def copy(self) -> "te.Self":
|
||||||
|
"""Return a shallow copy of the instance."""
|
||||||
|
rv = self.__class__(self.capacity)
|
||||||
|
rv._mapping.update(self._mapping)
|
||||||
|
rv._queue.extend(self._queue)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def get(self, key: t.Any, default: t.Any = None) -> t.Any:
|
||||||
|
"""Return an item from the cache dict or `default`"""
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any:
|
||||||
|
"""Set `default` if the key is not in the cache otherwise
|
||||||
|
leave unchanged. Return the value of this key.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
self[key] = default
|
||||||
|
return default
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""Clear the cache."""
|
||||||
|
with self._wlock:
|
||||||
|
self._mapping.clear()
|
||||||
|
self._queue.clear()
|
||||||
|
|
||||||
|
def __contains__(self, key: t.Any) -> bool:
|
||||||
|
"""Check if a key exists in this cache."""
|
||||||
|
return key in self._mapping
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
"""Return the current size of the cache."""
|
||||||
|
return len(self._mapping)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<{type(self).__name__} {self._mapping!r}>"
|
||||||
|
|
||||||
|
def __getitem__(self, key: t.Any) -> t.Any:
|
||||||
|
"""Get an item from the cache. Moves the item up so that it has the
|
||||||
|
highest priority then.
|
||||||
|
|
||||||
|
Raise a `KeyError` if it does not exist.
|
||||||
|
"""
|
||||||
|
with self._wlock:
|
||||||
|
rv = self._mapping[key]
|
||||||
|
|
||||||
|
if self._queue[-1] != key:
|
||||||
|
try:
|
||||||
|
self._remove(key)
|
||||||
|
except ValueError:
|
||||||
|
# if something removed the key from the container
|
||||||
|
# when we read, ignore the ValueError that we would
|
||||||
|
# get otherwise.
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._append(key)
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def __setitem__(self, key: t.Any, value: t.Any) -> None:
|
||||||
|
"""Sets the value for an item. Moves the item up so that it
|
||||||
|
has the highest priority then.
|
||||||
|
"""
|
||||||
|
with self._wlock:
|
||||||
|
if key in self._mapping:
|
||||||
|
self._remove(key)
|
||||||
|
elif len(self._mapping) == self.capacity:
|
||||||
|
del self._mapping[self._popleft()]
|
||||||
|
|
||||||
|
self._append(key)
|
||||||
|
self._mapping[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: t.Any) -> None:
|
||||||
|
"""Remove an item from the cache dict.
|
||||||
|
Raise a `KeyError` if it does not exist.
|
||||||
|
"""
|
||||||
|
with self._wlock:
|
||||||
|
del self._mapping[key]
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._remove(key)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]:
|
||||||
|
"""Return a list of items."""
|
||||||
|
result = [(key, self._mapping[key]) for key in list(self._queue)]
|
||||||
|
result.reverse()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def values(self) -> t.Iterable[t.Any]:
|
||||||
|
"""Return a list of all values."""
|
||||||
|
return [x[1] for x in self.items()]
|
||||||
|
|
||||||
|
def keys(self) -> t.Iterable[t.Any]:
|
||||||
|
"""Return a list of all keys ordered by most recent usage."""
|
||||||
|
return list(self)
|
||||||
|
|
||||||
|
def __iter__(self) -> t.Iterator[t.Any]:
|
||||||
|
return reversed(tuple(self._queue))
|
||||||
|
|
||||||
|
def __reversed__(self) -> t.Iterator[t.Any]:
|
||||||
|
"""Iterate over the keys in the cache dict, oldest items
|
||||||
|
coming first.
|
||||||
|
"""
|
||||||
|
return iter(tuple(self._queue))
|
||||||
|
|
||||||
|
__copy__ = copy
|
||||||
|
|
||||||
|
|
||||||
|
def select_autoescape(
|
||||||
|
enabled_extensions: t.Collection[str] = ("html", "htm", "xml"),
|
||||||
|
disabled_extensions: t.Collection[str] = (),
|
||||||
|
default_for_string: bool = True,
|
||||||
|
default: bool = False,
|
||||||
|
) -> t.Callable[[t.Optional[str]], bool]:
|
||||||
|
"""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.
|
||||||
|
|
||||||
|
.. versionadded:: 2.9
|
||||||
|
"""
|
||||||
|
enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions)
|
||||||
|
disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions)
|
||||||
|
|
||||||
|
def autoescape(template_name: t.Optional[str]) -> bool:
|
||||||
|
if template_name is None:
|
||||||
|
return default_for_string
|
||||||
|
template_name = template_name.lower()
|
||||||
|
if template_name.endswith(enabled_patterns):
|
||||||
|
return True
|
||||||
|
if template_name.endswith(disabled_patterns):
|
||||||
|
return False
|
||||||
|
return default
|
||||||
|
|
||||||
|
return autoescape
|
||||||
|
|
||||||
|
|
||||||
|
def htmlsafe_json_dumps(
|
||||||
|
obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any
|
||||||
|
) -> markupsafe.Markup:
|
||||||
|
"""Serialize an object to a string of JSON with :func:`json.dumps`,
|
||||||
|
then replace HTML-unsafe characters with Unicode escapes and mark
|
||||||
|
the result safe with :class:`~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.
|
||||||
|
|
||||||
|
:param obj: The object to serialize to JSON.
|
||||||
|
:param dumps: The ``dumps`` function to use. Defaults to
|
||||||
|
``env.policies["json.dumps_function"]``, which defaults to
|
||||||
|
:func:`json.dumps`.
|
||||||
|
:param kwargs: Extra arguments to pass to ``dumps``. Merged onto
|
||||||
|
``env.policies["json.dumps_kwargs"]``.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The ``dumper`` parameter is renamed to ``dumps``.
|
||||||
|
|
||||||
|
.. versionadded:: 2.9
|
||||||
|
"""
|
||||||
|
if dumps is None:
|
||||||
|
dumps = json.dumps
|
||||||
|
|
||||||
|
return markupsafe.Markup(
|
||||||
|
dumps(obj, **kwargs)
|
||||||
|
.replace("<", "\\u003c")
|
||||||
|
.replace(">", "\\u003e")
|
||||||
|
.replace("&", "\\u0026")
|
||||||
|
.replace("'", "\\u0027")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Cycler:
|
||||||
|
"""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.
|
||||||
|
|
||||||
|
.. code-block:: html+jinja
|
||||||
|
|
||||||
|
{% 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>
|
||||||
|
|
||||||
|
:param items: Each positional argument will be yielded in the order
|
||||||
|
given for each cycle.
|
||||||
|
|
||||||
|
.. versionadded:: 2.1
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *items: t.Any) -> None:
|
||||||
|
if not items:
|
||||||
|
raise RuntimeError("at least one item has to be provided")
|
||||||
|
self.items = items
|
||||||
|
self.pos = 0
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
"""Resets the current item to the first item."""
|
||||||
|
self.pos = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current(self) -> t.Any:
|
||||||
|
"""Return the current item. Equivalent to the item that will be
|
||||||
|
returned next time :meth:`next` is called.
|
||||||
|
"""
|
||||||
|
return self.items[self.pos]
|
||||||
|
|
||||||
|
def next(self) -> t.Any:
|
||||||
|
"""Return the current item, then advance :attr:`current` to the
|
||||||
|
next item.
|
||||||
|
"""
|
||||||
|
rv = self.current
|
||||||
|
self.pos = (self.pos + 1) % len(self.items)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
__next__ = next
|
||||||
|
|
||||||
|
|
||||||
|
class Joiner:
|
||||||
|
"""A joining helper for templates."""
|
||||||
|
|
||||||
|
def __init__(self, sep: str = ", ") -> None:
|
||||||
|
self.sep = sep
|
||||||
|
self.used = False
|
||||||
|
|
||||||
|
def __call__(self) -> str:
|
||||||
|
if not self.used:
|
||||||
|
self.used = True
|
||||||
|
return ""
|
||||||
|
return self.sep
|
||||||
|
|
||||||
|
|
||||||
|
class Namespace:
|
||||||
|
"""A namespace object that can hold arbitrary attributes. It may be
|
||||||
|
initialized from a dictionary or with keyword arguments."""
|
||||||
|
|
||||||
|
def __init__(*args: t.Any, **kwargs: t.Any) -> None: # noqa: B902
|
||||||
|
self, args = args[0], args[1:]
|
||||||
|
self.__attrs = dict(*args, **kwargs)
|
||||||
|
|
||||||
|
def __getattribute__(self, name: str) -> t.Any:
|
||||||
|
# __class__ is needed for the awaitable check in async mode
|
||||||
|
if name in {"_Namespace__attrs", "__class__"}:
|
||||||
|
return object.__getattribute__(self, name)
|
||||||
|
try:
|
||||||
|
return self.__attrs[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(name) from None
|
||||||
|
|
||||||
|
def __setitem__(self, name: str, value: t.Any) -> None:
|
||||||
|
self.__attrs[name] = value
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<Namespace {self.__attrs!r}>"
|
||||||
Reference in New Issue
Block a user