remove _compat module

This commit is contained in:
David Lord 2020-01-27 22:05:00 -08:00
parent b0015c72d5
commit 148a19138c
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
29 changed files with 264 additions and 766 deletions

View File

@ -1,132 +0,0 @@
# -*- coding: utf-8 -*-
# flake8: noqa
import marshal
import sys
PY2 = sys.version_info[0] == 2
PYPY = hasattr(sys, "pypy_translation_info")
_identity = lambda x: x
if not PY2:
unichr = chr
range_type = range
text_type = str
string_types = (str,)
integer_types = (int,)
iterkeys = lambda d: iter(d.keys())
itervalues = lambda d: iter(d.values())
iteritems = lambda d: iter(d.items())
import pickle
from io import BytesIO, StringIO
NativeStringIO = StringIO
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
ifilter = filter
imap = map
izip = zip
intern = sys.intern
implements_iterator = _identity
implements_to_string = _identity
encode_filename = _identity
marshal_dump = marshal.dump
marshal_load = marshal.load
else:
unichr = unichr
text_type = unicode
range_type = xrange
string_types = (str, unicode)
integer_types = (int, long)
iterkeys = lambda d: d.iterkeys()
itervalues = lambda d: d.itervalues()
iteritems = lambda d: d.iteritems()
import cPickle as pickle
from cStringIO import StringIO as BytesIO, StringIO
NativeStringIO = BytesIO
exec("def reraise(tp, value, tb=None):\n raise tp, value, tb")
from itertools import imap, izip, ifilter
intern = intern
def implements_iterator(cls):
cls.next = cls.__next__
del cls.__next__
return cls
def implements_to_string(cls):
cls.__unicode__ = cls.__str__
cls.__str__ = lambda x: x.__unicode__().encode("utf-8")
return cls
def encode_filename(filename):
if isinstance(filename, unicode):
return filename.encode("utf-8")
return filename
def marshal_dump(code, f):
if isinstance(f, file):
marshal.dump(code, f)
else:
f.write(marshal.dumps(code))
def marshal_load(f):
if isinstance(f, file):
return marshal.load(f)
return marshal.loads(f.read())
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a
# dummy metaclass for one level of class instantiation that replaces
# itself with the actual metaclass.
class metaclass(type):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, "temporary_class", (), {})
try:
from urllib.parse import quote_from_bytes as url_quote
except ImportError:
from urllib import quote as url_quote
try:
from collections import abc
except ImportError:
import collections as abc
try:
from os import fspath
except ImportError:
try:
from pathlib import PurePath
except ImportError:
PurePath = None
def fspath(path):
if hasattr(path, "__fspath__"):
return path.__fspath__()
# Python 3.5 doesn't have __fspath__ yet, use str.
if PurePath is not None and isinstance(path, PurePath):
return str(path)
return path

View File

@ -8,19 +8,15 @@ are initialized on the first request.
"""
import errno
import fnmatch
import marshal
import os
import pickle
import stat
import sys
import tempfile
from hashlib import sha1
from os import listdir
from os import path
from io import BytesIO
from ._compat import BytesIO
from ._compat import marshal_dump
from ._compat import marshal_load
from ._compat import pickle
from ._compat import text_type
from .utils import open_if_exists
bc_version = 4
@ -67,7 +63,7 @@ class Bucket(object):
return
# if marshal_load fails then we need to reload
try:
self.code = marshal_load(f)
self.code = marshal.load(f)
except (EOFError, ValueError, TypeError):
self.reset()
return
@ -78,7 +74,7 @@ class Bucket(object):
raise TypeError("can't write empty bucket")
f.write(bc_magic)
pickle.dump(self.checksum, f, 2)
marshal_dump(self.code, f)
marshal.dump(self.code, f)
def bytecode_from_string(self, string):
"""Load bytecode from a string."""
@ -145,7 +141,7 @@ class BytecodeCache(object):
hash = sha1(name.encode("utf-8"))
if filename is not None:
filename = "|" + filename
if isinstance(filename, text_type):
if isinstance(filename, str):
filename = filename.encode("utf-8")
hash.update(filename)
return hash.hexdigest()
@ -241,7 +237,7 @@ class FileSystemBytecodeCache(BytecodeCache):
return actual_dir
def _get_cache_filename(self, bucket):
return path.join(self.directory, self.pattern % bucket.key)
return os.path.join(self.directory, self.pattern % bucket.key)
def load_bytecode(self, bucket):
f = open_if_exists(self._get_cache_filename(bucket), "rb")
@ -264,10 +260,10 @@ class FileSystemBytecodeCache(BytecodeCache):
# normally.
from os import remove
files = fnmatch.filter(listdir(self.directory), self.pattern % "*")
files = fnmatch.filter(os.listdir(self.directory), self.pattern % "*")
for filename in files:
try:
remove(path.join(self.directory, filename))
remove(os.path.join(self.directory, filename))
except OSError:
pass

View File

@ -2,6 +2,7 @@
"""Compiles nodes from the parser into Python code."""
from collections import namedtuple
from functools import update_wrapper
from io import StringIO
from itertools import chain
from keyword import iskeyword as is_python_keyword
@ -9,13 +10,6 @@ from markupsafe import escape
from markupsafe import Markup
from . import nodes
from ._compat import imap
from ._compat import iteritems
from ._compat import izip
from ._compat import NativeStringIO
from ._compat import range_type
from ._compat import string_types
from ._compat import text_type
from .exceptions import TemplateAssertionError
from .idtracking import Symbols
from .idtracking import VAR_LOAD_ALIAS
@ -38,30 +32,6 @@ operators = {
"notin": "not in",
}
# what method to iterate over items do we want to use for dict iteration
# in generated code? on 2.x let's go with iteritems, on 3.x with items
if hasattr(dict, "iteritems"):
dict_item_iter = "iteritems"
else:
dict_item_iter = "items"
code_features = ["division"]
# does this python version support generator stops? (PEP 0479)
try:
exec("from __future__ import generator_stop")
code_features.append("generator_stop")
except SyntaxError:
pass
# does this python version support yield from?
try:
exec("def f(): yield from x()")
except SyntaxError:
supports_yield_from = False
else:
supports_yield_from = True
def optimizeconst(f):
def new_func(self, node, frame, **kwargs):
@ -93,20 +63,16 @@ def has_safe_repr(value):
"""Does the node have a safe representation?"""
if value is None or value is NotImplemented or value is Ellipsis:
return True
if type(value) in (bool, int, float, complex, range_type, Markup) + string_types:
return True
if type(value) in (tuple, list, set, frozenset):
for item in value:
if not has_safe_repr(item):
return False
return True
elif type(value) is dict:
for key, value in iteritems(value):
if not has_safe_repr(key):
return False
if not has_safe_repr(value):
return False
if type(value) in {bool, int, float, complex, range, str, Markup}:
return True
if type(value) in {tuple, list, set, frozenset}:
return all(has_safe_repr(v) for v in value)
if type(value) is dict:
return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items())
return False
@ -249,7 +215,7 @@ class CodeGenerator(NodeVisitor):
self, environment, name, filename, stream=None, defer_init=False, optimized=True
):
if stream is None:
stream = NativeStringIO()
stream = StringIO()
self.environment = environment
self.name = name
self.filename = filename
@ -432,7 +398,7 @@ class CodeGenerator(NodeVisitor):
self.write(", ")
self.visit(kwarg, frame)
if extra_kwargs is not None:
for key, value in iteritems(extra_kwargs):
for key, value in extra_kwargs.items():
self.write(", %s=%s" % (key, value))
if node.dyn_args:
self.write(", *")
@ -448,7 +414,7 @@ class CodeGenerator(NodeVisitor):
self.visit(kwarg.value, frame)
self.write(", ")
if extra_kwargs is not None:
for key, value in iteritems(extra_kwargs):
for key, value in extra_kwargs.items():
self.write("%r: %s, " % (key, value))
if node.dyn_kwargs is not None:
self.write("}, **")
@ -477,7 +443,7 @@ class CodeGenerator(NodeVisitor):
def enter_frame(self, frame):
undefs = []
for target, (action, param) in iteritems(frame.symbols.loads):
for target, (action, param) in frame.symbols.loads.items():
if action == VAR_LOAD_PARAMETER:
pass
elif action == VAR_LOAD_RESOLVE:
@ -494,7 +460,7 @@ class CodeGenerator(NodeVisitor):
def leave_frame(self, frame, with_python_scope=False):
if not with_python_scope:
undefs = []
for target, _ in iteritems(frame.symbols.loads):
for target in frame.symbols.loads:
undefs.append(target)
if undefs:
self.writeline("%s = missing" % " = ".join(undefs))
@ -612,7 +578,7 @@ class CodeGenerator(NodeVisitor):
def dump_local_context(self, frame):
return "{%s}" % ", ".join(
"%r: %s" % (name, target)
for name, target in iteritems(frame.symbols.dump_stores())
for name, target in frame.symbols.dump_stores().items()
)
def write_commons(self):
@ -704,7 +670,7 @@ class CodeGenerator(NodeVisitor):
else:
self.writeline(
"context.exported_vars.update((%s))"
% ", ".join(imap(repr, public_names))
% ", ".join(map(repr, public_names))
)
# -- Statement Visitors
@ -715,7 +681,6 @@ class CodeGenerator(NodeVisitor):
from .runtime import exported
self.writeline("from __future__ import %s" % ", ".join(code_features))
self.writeline("from jinja2.runtime import " + ", ".join(exported))
if self.environment.is_async:
@ -781,7 +746,7 @@ class CodeGenerator(NodeVisitor):
self.indent()
self.writeline("if parent_template is not None:")
self.indent()
if supports_yield_from and not self.environment.is_async:
if not self.environment.is_async:
self.writeline("yield from parent_template.root_render_func(context)")
else:
self.writeline(
@ -795,7 +760,7 @@ class CodeGenerator(NodeVisitor):
self.outdent(1 + (not self.has_known_extends))
# at this point we now have the blocks collected and can visit them too.
for name, block in iteritems(self.blocks):
for name, block in self.blocks.items():
self.writeline(
"%s(context, missing=missing%s):"
% (self.func("block_" + name), envenv),
@ -851,11 +816,7 @@ class CodeGenerator(NodeVisitor):
else:
context = self.get_context_ref()
if (
supports_yield_from
and not self.environment.is_async
and frame.buffer is None
):
if not self.environment.is_async and frame.buffer is None:
self.writeline(
"yield from context.blocks[%r][0](%s)" % (node.name, context), node
)
@ -900,9 +861,7 @@ class CodeGenerator(NodeVisitor):
self.writeline("parent_template = environment.get_template(", node)
self.visit(node.template, frame)
self.write(", %r)" % self.name)
self.writeline(
"for name, parent_block in parent_template.blocks.%s():" % dict_item_iter
)
self.writeline("for name, parent_block in parent_template.blocks.items():")
self.indent()
self.writeline("context.blocks.setdefault(name, []).append(parent_block)")
self.outdent()
@ -924,7 +883,7 @@ class CodeGenerator(NodeVisitor):
func_name = "get_or_select_template"
if isinstance(node.template, nodes.Const):
if isinstance(node.template.value, string_types):
if isinstance(node.template.value, str):
func_name = "get_template"
elif isinstance(node.template.value, (tuple, list)):
func_name = "select_template"
@ -958,13 +917,8 @@ class CodeGenerator(NodeVisitor):
"._body_stream:"
)
else:
if supports_yield_from:
self.writeline("yield from template._get_default_module()._body_stream")
skip_event_yield = True
else:
self.writeline(
"for event in template._get_default_module()._body_stream:"
)
self.writeline("yield from template._get_default_module()._body_stream")
skip_event_yield = True
if not skip_event_yield:
self.indent()
@ -1071,7 +1025,7 @@ class CodeGenerator(NodeVisitor):
else:
self.writeline(
"context.exported_vars.difference_"
"update((%s))" % ", ".join(imap(repr, discarded_names))
"update((%s))" % ", ".join(map(repr, discarded_names))
)
def visit_For(self, node, frame):
@ -1262,7 +1216,7 @@ class CodeGenerator(NodeVisitor):
with_frame = frame.inner()
with_frame.symbols.analyze_node(node)
self.enter_frame(with_frame)
for target, expr in izip(node.targets, node.values):
for target, expr in zip(node.targets, node.values):
self.newline()
self.visit(target, with_frame)
self.write(" = ")
@ -1278,7 +1232,7 @@ class CodeGenerator(NodeVisitor):
#: The default finalize function if the environment isn't configured
#: with one. Or if the environment has one, this is called on that
#: function's output for constants.
_default_finalize = text_type
_default_finalize = str
_finalize = None
def _make_finalize(self):
@ -1344,7 +1298,7 @@ class CodeGenerator(NodeVisitor):
# Template data doesn't go through finalize.
if isinstance(node, nodes.TemplateData):
return text_type(const)
return str(const)
return finalize.const(const)
@ -1353,11 +1307,11 @@ class CodeGenerator(NodeVisitor):
``Output`` node.
"""
if frame.eval_ctx.volatile:
self.write("(escape if context.eval_ctx.autoescape else to_string)(")
self.write("(escape if context.eval_ctx.autoescape else str)(")
elif frame.eval_ctx.autoescape:
self.write("escape(")
else:
self.write("to_string(")
self.write("str(")
if finalize.src is not None:
self.write(finalize.src)
@ -1615,11 +1569,11 @@ class CodeGenerator(NodeVisitor):
@optimizeconst
def visit_Concat(self, node, frame):
if frame.eval_ctx.volatile:
func_name = "(context.eval_ctx.volatile and markup_join or unicode_join)"
func_name = "(context.eval_ctx.volatile and markup_join or str_join)"
elif frame.eval_ctx.autoescape:
func_name = "markup_join"
else:
func_name = "unicode_join"
func_name = "str_join"
self.write("%s((" % func_name)
for arg in node.nodes:
self.visit(arg, frame)

View File

@ -1,8 +1,8 @@
import platform
import sys
from types import CodeType
from . import TemplateSyntaxError
from ._compat import PYPY
from .utils import internal_code
from .utils import missing
@ -14,13 +14,11 @@ def rewrite_traceback_stack(source=None):
This must be called within an ``except`` block.
:param exc_info: A :meth:`sys.exc_info` tuple. If not provided,
the current ``exc_info`` is used.
:param source: For ``TemplateSyntaxError``, the original source if
known.
:return: A :meth:`sys.exc_info` tuple that can be re-raised.
:return: The original exception with the rewritten traceback.
"""
exc_type, exc_value, tb = sys.exc_info()
_, exc_value, tb = sys.exc_info()
if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated:
exc_value.translated = True
@ -70,7 +68,7 @@ def rewrite_traceback_stack(source=None):
for tb in reversed(stack):
tb_next = tb_set_next(tb, tb_next)
return exc_type, exc_value, tb_next
return exc_value.with_traceback(tb_next)
def fake_traceback(exc_value, tb, filename, lineno):
@ -215,7 +213,7 @@ if sys.version_info >= (3, 7):
return tb
elif PYPY:
elif platform.python_implementation() == "PyPy":
# PyPy might have special support, and won't work with ctypes.
try:
import tputil

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from ._compat import range_type
from .filters import FILTERS as DEFAULT_FILTERS # noqa: F401
from .tests import TESTS as DEFAULT_TESTS # noqa: F401
from .utils import Cycler
@ -24,7 +23,7 @@ KEEP_TRAILING_NEWLINE = False
# default filters, tests and namespace
DEFAULT_NAMESPACE = {
"range": range_type,
"range": range,
"dict": dict,
"lipsum": generate_lorem_ipsum,
"cycler": Cycler,

View File

@ -11,13 +11,6 @@ from functools import reduce
from markupsafe import Markup
from . import nodes
from ._compat import encode_filename
from ._compat import implements_iterator
from ._compat import implements_to_string
from ._compat import iteritems
from ._compat import reraise
from ._compat import string_types
from ._compat import text_type
from .compiler import CodeGenerator
from .compiler import generate
from .defaults import BLOCK_END_STRING
@ -102,7 +95,7 @@ def load_extensions(environment, extensions):
"""
result = {}
for extension in extensions:
if isinstance(extension, string_types):
if isinstance(extension, str):
extension = import_string(extension)
result[extension.identifier] = extension(environment)
return result
@ -376,7 +369,7 @@ class Environment(object):
yet. This is used by :ref:`extensions <writing-extensions>` to register
callbacks and configuration values without breaking inheritance.
"""
for key, value in iteritems(attributes):
for key, value in attributes.items():
if not hasattr(self, key):
setattr(self, key, value)
@ -421,7 +414,7 @@ class Environment(object):
rv.overlayed = True
rv.linked_to = self
for key, value in iteritems(args):
for key, value in args.items():
if value is not missing:
setattr(rv, key, value)
@ -431,7 +424,7 @@ class Environment(object):
rv.cache = copy_cache(self.cache)
rv.extensions = {}
for key, value in iteritems(self.extensions):
for key, value in self.extensions.items():
rv.extensions[key] = value.bind(rv)
if extensions is not missing:
rv.extensions.update(load_extensions(rv, extensions))
@ -449,7 +442,7 @@ class Environment(object):
try:
return obj[argument]
except (AttributeError, TypeError, LookupError):
if isinstance(argument, string_types):
if isinstance(argument, str):
try:
attr = str(argument)
except Exception:
@ -534,7 +527,7 @@ class Environment(object):
def _parse(self, source, name, filename):
"""Internal parsing function used by `parse` and `compile`."""
return Parser(self, source, name, encode_filename(filename)).parse()
return Parser(self, source, name, filename).parse()
def lex(self, source, name=None, filename=None):
"""Lex the given sourcecode and return a generator that yields
@ -546,7 +539,7 @@ class Environment(object):
of the extensions to be applied you have to filter source through
the :meth:`preprocess` method.
"""
source = text_type(source)
source = str(source)
try:
return self.lexer.tokeniter(source, name, filename)
except TemplateSyntaxError:
@ -560,7 +553,7 @@ class Environment(object):
return reduce(
lambda s, e: e.preprocess(s, name, filename),
self.iter_extensions(),
text_type(source),
str(source),
)
def _tokenize(self, source, name, filename=None, state=None):
@ -621,7 +614,7 @@ class Environment(object):
"""
source_hint = None
try:
if isinstance(source, string_types):
if isinstance(source, str):
source_hint = source
source = self._parse(source, name, filename)
source = self._generate(source, name, filename, defer_init=defer_init)
@ -629,8 +622,6 @@ class Environment(object):
return source
if filename is None:
filename = "<template>"
else:
filename = encode_filename(filename)
return self._compile(source, filename)
except TemplateSyntaxError:
self.handle_exception(source=source_hint)
@ -718,7 +709,7 @@ class Environment(object):
info.external_attr = 0o755 << 16
zip_file.writestr(info, data)
else:
if isinstance(data, text_type):
if isinstance(data, str):
data = data.encode("utf8")
with open(os.path.join(target, filename), "wb") as f:
@ -795,7 +786,7 @@ class Environment(object):
"""
from .debug import rewrite_traceback_stack
reraise(*rewrite_traceback_stack(source=source))
raise rewrite_traceback_stack(source=source)
def join_path(self, template, parent):
"""Join a template with the parent. By default all the lookups are
@ -892,7 +883,7 @@ class Environment(object):
.. versionadded:: 2.3
"""
if isinstance(template_name_or_list, (string_types, Undefined)):
if isinstance(template_name_or_list, (str, Undefined)):
return self.get_template(template_name_or_list, parent, globals)
elif isinstance(template_name_or_list, Template):
return template_name_or_list
@ -1185,7 +1176,6 @@ class Template(object):
return "<%s %s>" % (self.__class__.__name__, name)
@implements_to_string
class TemplateModule(object):
"""Represents an imported template. All the exported names of the
template are available as attributes on this object. Additionally
@ -1239,7 +1229,6 @@ class TemplateExpression(object):
return rv
@implements_iterator
class TemplateStream(object):
"""A template stream works pretty much like an ordinary python generator
but it can buffer multiple items to reduce the number of total iterations.
@ -1265,7 +1254,7 @@ class TemplateStream(object):
Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
"""
close = False
if isinstance(fp, string_types):
if isinstance(fp, str):
if encoding is None:
encoding = "utf-8"
fp = open(fp, "wb")

View File

@ -1,44 +1,15 @@
# -*- coding: utf-8 -*-
from ._compat import imap
from ._compat import implements_to_string
from ._compat import PY2
from ._compat import text_type
class TemplateError(Exception):
"""Baseclass for all template errors."""
if PY2:
def __init__(self, message=None):
super().__init__(message)
def __init__(self, message=None):
if message is not None:
message = text_type(message).encode("utf-8")
Exception.__init__(self, message)
@property
def message(self):
if self.args:
message = self.args[0]
if message is not None:
return message.decode("utf-8", "replace")
def __unicode__(self):
return self.message or u""
else:
def __init__(self, message=None):
Exception.__init__(self, message)
@property
def message(self):
if self.args:
message = self.args[0]
if message is not None:
return message
@property
def message(self):
if self.args:
return self.args[0]
@implements_to_string
class TemplateNotFound(IOError, LookupError, TemplateError):
"""Raised if a template does not exist.
@ -95,13 +66,12 @@ class TemplatesNotFound(TemplateNotFound):
parts.append(name)
message = u"none of the templates given were found: " + u", ".join(
imap(text_type, parts)
map(str, parts)
)
TemplateNotFound.__init__(self, names and names[-1] or None, message)
self.templates = list(names)
@implements_to_string
class TemplateSyntaxError(TemplateError):
"""Raised to tell the user that there is a problem with the template."""

View File

@ -7,9 +7,6 @@ from sys import version_info
from markupsafe import Markup
from . import nodes
from ._compat import iteritems
from ._compat import string_types
from ._compat import with_metaclass
from .defaults import BLOCK_END_STRING
from .defaults import BLOCK_START_STRING
from .defaults import COMMENT_END_STRING
@ -47,7 +44,7 @@ class ExtensionRegistry(type):
return rv
class Extension(with_metaclass(ExtensionRegistry, object)):
class Extension(metaclass=ExtensionRegistry):
"""Extensions can be used to add extra functionality to the Jinja template
system at the parser level. Custom extensions are bound to an environment
but may not store environment specific data on `self`. The reason for
@ -222,7 +219,7 @@ class InternationalizationExtension(Extension):
self.environment.globals.pop(key, None)
def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
if isinstance(source, string_types):
if isinstance(source, str):
source = self.environment.parse(source)
return extract_from_ast(source, gettext_functions)
@ -409,7 +406,7 @@ class InternationalizationExtension(Extension):
# enough to handle the variable expansion and autoescape
# handling itself
if self.environment.newstyle_gettext:
for key, value in iteritems(variables):
for key, value in variables.items():
# the function adds that later anyways in case num was
# called num, so just skip it.
if num_called_num and key == "num":
@ -554,7 +551,7 @@ def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS, babel_style=True
strings = []
for arg in node.args:
if isinstance(arg, nodes.Const) and isinstance(arg.value, string_types):
if isinstance(arg, nodes.Const) and isinstance(arg.value, str):
strings.append(arg.value)
else:
strings.append(None)

View File

@ -3,28 +3,24 @@
import math
import random
import re
from collections import abc
from collections import namedtuple
from itertools import chain
from itertools import groupby
from markupsafe import escape
from markupsafe import Markup
from markupsafe import soft_unicode
from markupsafe import soft_str
from ._compat import abc
from ._compat import imap
from ._compat import iteritems
from ._compat import string_types
from ._compat import text_type
from .exceptions import FilterArgumentError
from .runtime import Undefined
from .utils import htmlsafe_json_dumps
from .utils import pformat
from .utils import unicode_urlencode
from .utils import url_quote
from .utils import urlize
_word_re = re.compile(r"\w+", re.UNICODE)
_word_beginning_split_re = re.compile(r"([-\s\(\{\[\<]+)", re.UNICODE)
_word_re = re.compile(r"\w+")
_word_beginning_split_re = re.compile(r"([-\s({\[<]+)")
def contextfilter(f):
@ -57,7 +53,7 @@ def environmentfilter(f):
def ignore_case(value):
"""For use as a postprocessor for :func:`make_attrgetter`. Converts strings
to lowercase and returns other types as-is."""
return value.lower() if isinstance(value, string_types) else value
return value.lower() if isinstance(value, str) else value
def make_attrgetter(environment, attribute, postprocess=None, default=None):
@ -95,7 +91,7 @@ def make_multi_attrgetter(environment, attribute, postprocess=None):
Examples of attribute: "attr1,attr2", "attr1.inner1.0,attr2.inner2.0", etc.
"""
attribute_parts = (
attribute.split(",") if isinstance(attribute, string_types) else [attribute]
attribute.split(",") if isinstance(attribute, str) else [attribute]
)
attribute = [
_prepare_attribute_parts(attribute_part) for attribute_part in attribute_parts
@ -120,7 +116,7 @@ def make_multi_attrgetter(environment, attribute, postprocess=None):
def _prepare_attribute_parts(attr):
if attr is None:
return []
elif isinstance(attr, string_types):
elif isinstance(attr, str):
return [int(x) if x.isdigit() else x for x in attr.split(".")]
else:
return [attr]
@ -130,7 +126,7 @@ def do_forceescape(value):
"""Enforce HTML escaping. This will probably double escape variables."""
if hasattr(value, "__html__"):
value = value.__html__()
return escape(text_type(value))
return escape(str(value))
def do_urlencode(value):
@ -149,16 +145,16 @@ def do_urlencode(value):
.. versionadded:: 2.7
"""
if isinstance(value, string_types) or not isinstance(value, abc.Iterable):
return unicode_urlencode(value)
if isinstance(value, str) or not isinstance(value, abc.Iterable):
return url_quote(value)
if isinstance(value, dict):
items = iteritems(value)
items = value.items()
else:
items = iter(value)
return u"&".join(
"%s=%s" % (unicode_urlencode(k, for_qs=True), unicode_urlencode(v, for_qs=True))
"%s=%s" % (url_quote(k, for_qs=True), url_quote(v, for_qs=True))
for k, v in items
)
@ -182,7 +178,7 @@ def do_replace(eval_ctx, s, old, new, count=None):
if count is None:
count = -1
if not eval_ctx.autoescape:
return text_type(s).replace(text_type(old), text_type(new), count)
return str(s).replace(str(old), str(new), count)
if (
hasattr(old, "__html__")
or hasattr(new, "__html__")
@ -190,18 +186,18 @@ def do_replace(eval_ctx, s, old, new, count=None):
):
s = escape(s)
else:
s = soft_unicode(s)
return s.replace(soft_unicode(old), soft_unicode(new), count)
s = soft_str(s)
return s.replace(soft_str(old), soft_str(new), count)
def do_upper(s):
"""Convert a value to uppercase."""
return soft_unicode(s).upper()
return soft_str(s).upper()
def do_lower(s):
"""Convert a value to lowercase."""
return soft_unicode(s).lower()
return soft_str(s).lower()
@evalcontextfilter
@ -230,7 +226,7 @@ def do_xmlattr(_eval_ctx, d, autospace=True):
"""
rv = u" ".join(
u'%s="%s"' % (escape(key), escape(value))
for key, value in iteritems(d)
for key, value in d.items()
if value is not None and not isinstance(value, Undefined)
)
if autospace and rv:
@ -244,7 +240,7 @@ def do_capitalize(s):
"""Capitalize a value. The first character will be uppercase, all others
lowercase.
"""
return soft_unicode(s).capitalize()
return soft_str(s).capitalize()
def do_title(s):
@ -254,7 +250,7 @@ def do_title(s):
return "".join(
[
item[0].upper() + item[1:].lower()
for item in _word_beginning_split_re.split(soft_unicode(s))
for item in _word_beginning_split_re.split(soft_str(s))
if item
]
)
@ -471,11 +467,11 @@ def do_join(eval_ctx, value, d=u"", attribute=None):
The `attribute` parameter was added.
"""
if attribute is not None:
value = imap(make_attrgetter(eval_ctx.environment, attribute), value)
value = map(make_attrgetter(eval_ctx.environment, attribute), value)
# no automatic escaping? joining is a lot easier then
if not eval_ctx.autoescape:
return text_type(d).join(imap(text_type, value))
return str(d).join(map(str, value))
# if the delimiter doesn't have an html representation we check
# if any of the items has. If yes we do a coercion to Markup
@ -486,20 +482,20 @@ def do_join(eval_ctx, value, d=u"", attribute=None):
if hasattr(item, "__html__"):
do_escape = True
else:
value[idx] = text_type(item)
value[idx] = str(item)
if do_escape:
d = escape(d)
else:
d = text_type(d)
d = str(d)
return d.join(value)
# no html involved, to normal joining
return soft_unicode(d).join(imap(soft_unicode, value))
return soft_str(d).join(map(soft_str, value))
def do_center(value, width=80):
"""Centers the value in a field of a given width."""
return text_type(value).center(width)
return str(value).center(width)
@environmentfilter
@ -764,7 +760,7 @@ def do_int(value, default=0, base=10):
The base is ignored for decimal numbers and non-string values.
"""
try:
if isinstance(value, string_types):
if isinstance(value, str):
return int(value, base)
return int(value)
except (TypeError, ValueError):
@ -810,19 +806,19 @@ def do_format(value, *args, **kwargs):
raise FilterArgumentError(
"can't handle positional and keyword arguments at the same time"
)
return soft_unicode(value) % (kwargs or args)
return soft_str(value) % (kwargs or args)
def do_trim(value, chars=None):
"""Strip leading and trailing characters, by default whitespace."""
return soft_unicode(value).strip(chars)
return soft_str(value).strip(chars)
def do_striptags(value):
"""Strip SGML/XML tags and replace adjacent whitespace by one space."""
if hasattr(value, "__html__"):
value = value.__html__()
return Markup(text_type(value)).striptags()
return Markup(str(value)).striptags()
def do_slice(value, slices, fill_with=None):
@ -995,7 +991,7 @@ def do_sum(environment, iterable, attribute=None, start=0):
attributes. Also the `start` parameter was moved on to the right.
"""
if attribute is not None:
iterable = imap(make_attrgetter(environment, attribute), iterable)
iterable = map(make_attrgetter(environment, attribute), iterable)
return sum(iterable, start)
@ -1015,14 +1011,14 @@ def do_mark_safe(value):
def do_mark_unsafe(value):
"""Mark a value as unsafe. This is the reverse operation for :func:`safe`."""
return text_type(value)
return str(value)
def do_reverse(value):
"""Reverse the object or return an iterator that iterates over it the other
way round.
"""
if isinstance(value, string_types):
if isinstance(value, str):
return value[::-1]
try:
return reversed(value)
@ -1355,7 +1351,7 @@ FILTERS = {
"selectattr": do_selectattr,
"slice": do_slice,
"sort": do_sort,
"string": soft_unicode,
"string": soft_str,
"striptags": do_striptags,
"sum": do_sum,
"title": do_title,

View File

@ -1,4 +1,3 @@
from ._compat import iteritems
from .visitor import NodeVisitor
VAR_LOAD_PARAMETER = "param"
@ -114,7 +113,7 @@ class Symbols(object):
self.loads.update(sym.loads)
self.stores.update(sym.stores)
for name, branch_count in iteritems(stores):
for name, branch_count in stores.items():
if branch_count == len(branch_symbols):
continue
target = self.find_ref(name)
@ -141,7 +140,7 @@ class Symbols(object):
rv = set()
node = self
while node is not None:
for target, (instr, _) in iteritems(self.loads):
for target, (instr, _) in self.loads.items():
if instr == VAR_LOAD_PARAMETER:
rv.add(target)
node = node.parent

View File

@ -8,11 +8,8 @@ import re
from ast import literal_eval
from collections import deque
from operator import itemgetter
from sys import intern
from ._compat import implements_iterator
from ._compat import intern
from ._compat import iteritems
from ._compat import text_type
from .exceptions import TemplateSyntaxError
from .utils import LRUCache
@ -21,7 +18,7 @@ from .utils import LRUCache
_lexer_cache = LRUCache(50)
# static regular expressions
whitespace_re = re.compile(r"\s+", re.U)
whitespace_re = re.compile(r"\s+")
newline_re = re.compile(r"(\r\n|\r|\n)")
string_re = re.compile(
r"('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S
@ -136,7 +133,7 @@ operators = {
";": TOKEN_SEMICOLON,
}
reverse_operators = dict([(v, k) for k, v in iteritems(operators)])
reverse_operators = dict([(v, k) for k, v in operators.items()])
assert len(operators) == len(reverse_operators), "operators dropped"
operator_re = re.compile(
"(%s)" % "|".join(re.escape(x) for x in sorted(operators, key=lambda x: -len(x)))
@ -296,7 +293,6 @@ class Token(tuple):
return "Token(%r, %r, %r)" % (self.lineno, self.type, self.value)
@implements_iterator
class TokenStreamIterator(object):
"""The iterator for tokenstreams. Iterate over the stream
until the eof token is reached.
@ -317,7 +313,6 @@ class TokenStreamIterator(object):
return token
@implements_iterator
class TokenStream(object):
"""A token stream is an iterable that yields :class:`Token`\\s. The
parser however does not iterate over it but calls :meth:`next` to go
@ -665,7 +660,6 @@ class Lexer(object):
"""This method tokenizes the text and returns the tokens in a
generator. Use this method if you just want to tokenize a template.
"""
source = text_type(source)
lines = source.splitlines()
if self.keep_trailing_newline and source:
for newline in ("\r\n", "\r", "\n"):
@ -744,7 +738,7 @@ class Lexer(object):
# yield for the current token the first named
# group that matched
elif token == "#bygroup":
for key, value in iteritems(m.groupdict()):
for key, value in m.groupdict().items():
if value is not None:
yield lineno, key, value
lineno += value.count("\n")
@ -804,7 +798,7 @@ class Lexer(object):
stack.pop()
# resolve the new state by group checking
elif new_state == "#bygroup":
for key, value in iteritems(m.groupdict()):
for key, value in m.groupdict().items():
if value is not None:
stack.append(key)
break

View File

@ -6,15 +6,11 @@ import os
import pkgutil
import sys
import weakref
from collections import abc
from hashlib import sha1
from importlib import import_module
from os import path
from types import ModuleType
from ._compat import abc
from ._compat import fspath
from ._compat import iteritems
from ._compat import string_types
from .exceptions import TemplateNotFound
from .utils import internalcode
from .utils import open_if_exists
@ -27,9 +23,9 @@ def split_template_path(template):
pieces = []
for piece in template.split("/"):
if (
path.sep in piece
or (path.altsep and path.altsep in piece)
or piece == path.pardir
os.path.sep in piece
or (os.path.altsep and os.path.altsep in piece)
or piece == os.path.pardir
):
raise TemplateNotFound(template)
elif piece and piece != ".":
@ -163,22 +159,17 @@ class FileSystemLoader(BaseLoader):
"""
def __init__(self, searchpath, encoding="utf-8", followlinks=False):
if not isinstance(searchpath, abc.Iterable) or isinstance(
searchpath, string_types
):
if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str):
searchpath = [searchpath]
# In Python 3.5, os.path.join doesn't support Path. This can be
# simplified to list(searchpath) when Python 3.5 is dropped.
self.searchpath = [fspath(p) for p in searchpath]
self.searchpath = list(searchpath)
self.encoding = encoding
self.followlinks = followlinks
def get_source(self, environment, template):
pieces = split_template_path(template)
for searchpath in self.searchpath:
filename = path.join(searchpath, *pieces)
filename = os.path.join(searchpath, *pieces)
f = open_if_exists(filename)
if f is None:
continue
@ -187,11 +178,11 @@ class FileSystemLoader(BaseLoader):
finally:
f.close()
mtime = path.getmtime(filename)
mtime = os.path.getmtime(filename)
def uptodate():
try:
return path.getmtime(filename) == mtime
return os.path.getmtime(filename) == mtime
except OSError:
return False
@ -403,7 +394,7 @@ class FunctionLoader(BaseLoader):
rv = self.load_func(template)
if rv is None:
raise TemplateNotFound(template)
elif isinstance(rv, string_types):
elif isinstance(rv, str):
return rv, None, None
return rv
@ -456,7 +447,7 @@ class PrefixLoader(BaseLoader):
def list_templates(self):
result = []
for prefix, loader in iteritems(self.mapping):
for prefix, loader in self.mapping.items():
for template in loader.list_templates():
result.append(prefix + self.delimiter + template)
return result
@ -529,10 +520,10 @@ class ModuleLoader(BaseLoader):
# path given.
mod = _TemplateModule(package_name)
if not isinstance(path, abc.Iterable) or isinstance(path, string_types):
if not isinstance(path, abc.Iterable) or isinstance(path, str):
path = [path]
mod.__path__ = [fspath(p) for p in path]
mod.__path__ = [os.fspath(p) for p in path]
sys.modules[package_name] = weakref.proxy(
mod, lambda x: sys.modules.pop(package_name, None)

View File

@ -3,8 +3,6 @@
interesting for introspection.
"""
from . import nodes
from ._compat import iteritems
from ._compat import string_types
from .compiler import CodeGenerator
@ -21,7 +19,7 @@ class TrackingCodeGenerator(CodeGenerator):
def enter_frame(self, frame):
"""Remember all undeclared identifiers."""
CodeGenerator.enter_frame(self, frame)
for _, (action, param) in iteritems(frame.symbols.loads):
for _, (action, param) in frame.symbols.loads.items():
if action == "resolve" and param not in self.environment.globals:
self.undeclared_identifiers.add(param)
@ -35,7 +33,7 @@ def find_undeclared_variables(ast):
>>> from jinja2 import Environment, meta
>>> env = Environment()
>>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
>>> meta.find_undeclared_variables(ast) == set(['bar'])
>>> meta.find_undeclared_variables(ast) == {'bar'}
True
.. admonition:: Implementation
@ -75,7 +73,7 @@ def find_referenced_templates(ast):
# something const, only yield the strings and ignore
# non-string consts that really just make no sense
if isinstance(template_name, nodes.Const):
if isinstance(template_name.value, string_types):
if isinstance(template_name.value, str):
yield template_name.value
# something dynamic in there
else:
@ -85,7 +83,7 @@ def find_referenced_templates(ast):
yield None
continue
# constant is a basestring, direct template name
if isinstance(node.template.value, string_types):
if isinstance(node.template.value, str):
yield node.template.value
# a tuple or list (latter *should* not happen) made of consts,
# yield the consts that are strings. We could warn here for
@ -94,7 +92,7 @@ def find_referenced_templates(ast):
node.template.value, (tuple, list)
):
for template_name in node.template.value:
if isinstance(template_name, string_types):
if isinstance(template_name, str):
yield template_name
# something else we don't care about, we could warn here
else:

View File

@ -4,7 +4,6 @@ from itertools import chain
from itertools import islice
from . import nodes
from ._compat import text_type
from .compiler import CodeGenerator
from .compiler import has_safe_repr
from .environment import Environment
@ -33,7 +32,7 @@ def native_concat(nodes, preserve_quotes=True):
else:
if isinstance(nodes, types.GeneratorType):
nodes = chain(head, nodes)
raw = u"".join([text_type(v) for v in nodes])
raw = u"".join([str(v) for v in nodes])
try:
literal = literal_eval(raw)
@ -52,7 +51,7 @@ def native_concat(nodes, preserve_quotes=True):
class NativeCodeGenerator(CodeGenerator):
"""A code generator which renders Python types by not adding
``to_string()`` around output nodes, and using :func:`native_concat`
``str()`` around output nodes, and using :func:`native_concat`
to convert complex strings back to Python types if possible.
"""

View File

@ -8,11 +8,6 @@ from collections import deque
from markupsafe import Markup
from ._compat import izip
from ._compat import PY2
from ._compat import text_type
from ._compat import with_metaclass
_binop_to_func = {
"*": operator.mul,
"/": operator.truediv,
@ -49,9 +44,9 @@ class NodeType(type):
def __new__(mcs, name, bases, d):
for attr in "fields", "attributes":
storage = []
storage.extend(getattr(bases[0], attr, ()))
storage.extend(getattr(bases[0] if bases else object, attr, ()))
storage.extend(d.get(attr, ()))
assert len(bases) == 1, "multiple inheritance not allowed"
assert len(bases) <= 1, "multiple inheritance not allowed"
assert len(storage) == len(set(storage)), "layout conflict"
d[attr] = tuple(storage)
d.setdefault("abstract", False)
@ -91,7 +86,7 @@ def get_eval_context(node, ctx):
return ctx
class Node(with_metaclass(NodeType, object)):
class Node(metaclass=NodeType):
"""Baseclass for all Jinja nodes. There are a number of nodes available
of different types. There are four major types:
@ -127,7 +122,7 @@ class Node(with_metaclass(NodeType, object)):
len(self.fields) != 1 and "s" or "",
)
)
for name, arg in izip(self.fields, fields):
for name, arg in zip(self.fields, fields):
setattr(self, name, arg)
for attr in self.attributes:
setattr(self, attr, attributes.pop(attr, None))
@ -510,17 +505,7 @@ class Const(Literal):
fields = ("value",)
def as_const(self, eval_ctx=None):
rv = self.value
if (
PY2
and type(rv) is text_type
and self.environment.policies["compiler.ascii_str"]
):
try:
rv = rv.encode("ascii")
except UnicodeError:
pass
return rv
return self.value
@classmethod
def from_untrusted(cls, value, lineno=None, environment=None):
@ -796,7 +781,7 @@ class Concat(Expr):
def as_const(self, eval_ctx=None):
eval_ctx = get_eval_context(self, eval_ctx)
return "".join(text_type(x.as_const(eval_ctx)) for x in self.nodes)
return "".join(str(x.as_const(eval_ctx)) for x in self.nodes)
class Compare(Expr):

View File

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Parse tokens from the lexer into nodes for the compiler."""
from . import nodes
from ._compat import imap
from .exceptions import TemplateAssertionError
from .exceptions import TemplateSyntaxError
from .lexer import describe_token
@ -66,7 +65,7 @@ class Parser(object):
def _fail_ut_eof(self, name, end_token_stack, lineno):
expected = []
for exprs in end_token_stack:
expected.extend(imap(describe_token_expr, exprs))
expected.extend(map(describe_token_expr, exprs))
if end_token_stack:
currently_looking = " or ".join(
"'%s'" % describe_token_expr(expr) for expr in end_token_stack[-1]

View File

@ -1,22 +1,14 @@
# -*- coding: utf-8 -*-
"""The runtime functions and state used by compiled templates."""
import sys
from collections import abc
from itertools import chain
from types import MethodType
from markupsafe import escape # noqa: F401
from markupsafe import Markup
from markupsafe import soft_unicode
from markupsafe import soft_str
from ._compat import abc
from ._compat import imap
from ._compat import implements_iterator
from ._compat import implements_to_string
from ._compat import iteritems
from ._compat import PY2
from ._compat import string_types
from ._compat import text_type
from ._compat import with_metaclass
from .exceptions import TemplateNotFound # noqa: F401
from .exceptions import TemplateRuntimeError # noqa: F401
from .exceptions import UndefinedError
@ -39,18 +31,13 @@ exported = [
"concat",
"escape",
"markup_join",
"unicode_join",
"to_string",
"str_join",
"identity",
"TemplateNotFound",
"Namespace",
"Undefined",
]
#: the name of the function that is used to convert something into
#: a string. We can just use the text type here.
to_string = text_type
def identity(x):
"""Returns its argument. Useful for certain things in the
@ -62,7 +49,7 @@ def identity(x):
def markup_join(seq):
"""Concatenation that escapes if necessary and converts to string."""
buf = []
iterator = imap(soft_unicode, seq)
iterator = map(soft_str, seq)
for arg in iterator:
buf.append(arg)
if hasattr(arg, "__html__"):
@ -70,9 +57,21 @@ def markup_join(seq):
return concat(buf)
def unicode_join(seq):
def str_join(seq):
"""Simple args to string conversion and concatenation."""
return concat(imap(text_type, seq))
return concat(map(str, seq))
def unicode_join(seq):
import warnings
warnings.warn(
"This template must be recompiled with at least Jinja 3.0, or"
" it will fail in 3.1.",
DeprecationWarning,
stacklevel=2,
)
return str_join(seq)
def new_context(
@ -96,7 +95,7 @@ def new_context(
# we don't want to modify the dict passed
if shared:
parent = dict(parent)
for key, value in iteritems(locals):
for key, value in locals.items():
if value is not missing:
parent[key] = value
return environment.context_class(environment, parent, template_name, blocks)
@ -155,7 +154,8 @@ def resolve_or_missing(context, key, missing=missing):
return missing
class Context(with_metaclass(ContextMeta)):
@abc.Mapping.register
class Context(metaclass=ContextMeta):
"""The template context holds the variables of a template. It stores the
values passed to the template and also the names the template exports.
Creating instances is neither supported nor useful as it's created
@ -191,7 +191,7 @@ class Context(with_metaclass(ContextMeta)):
# create the initial mapping of blocks. Whenever template inheritance
# takes place the runtime will update this mapping with the new blocks
# from the template.
self.blocks = dict((k, [v]) for k, v in iteritems(blocks))
self.blocks = {k: [v] for k, v in blocks.items()}
# In case we detect the fast resolve mode we can set up an alias
# here that bypasses the legacy code logic.
@ -304,7 +304,7 @@ class Context(with_metaclass(ContextMeta)):
self.environment, self.name, {}, self.get_all(), True, None, locals
)
context.eval_ctx = self.eval_ctx
context.blocks.update((k, list(v)) for k, v in iteritems(self.blocks))
context.blocks.update((k, list(v)) for k, v in self.blocks.items())
return context
def _all(meth): # noqa: B902
@ -318,12 +318,6 @@ class Context(with_metaclass(ContextMeta)):
keys = _all("keys")
values = _all("values")
items = _all("items")
# not available on python 3
if PY2:
iterkeys = _all("iterkeys")
itervalues = _all("itervalues")
iteritems = _all("iteritems")
del _all
def __contains__(self, name):
@ -346,9 +340,6 @@ class Context(with_metaclass(ContextMeta)):
)
abc.Mapping.register(Context)
class BlockReference(object):
"""One block on a template reference."""
@ -375,7 +366,6 @@ class BlockReference(object):
return rv
@implements_iterator
class LoopContext:
"""A wrapper iterable for dynamic ``for`` loops, with information
about the loop and iteration.
@ -688,7 +678,6 @@ class Macro(object):
)
@implements_to_string
class Undefined(object):
"""The default undefined type. This undefined type can be printed and
iterated over, but every other access will raise an :exc:`UndefinedError`:
@ -728,7 +717,7 @@ class Undefined(object):
if self._undefined_obj is missing:
return "%r is undefined" % self._undefined_name
if not isinstance(self._undefined_name, string_types):
if not isinstance(self._undefined_name, str):
return "%s has no element %r" % (
object_type_repr(self._undefined_obj),
self._undefined_name,
@ -752,51 +741,16 @@ class Undefined(object):
raise AttributeError(name)
return self._fail_with_undefined_error()
__add__ = (
__radd__
) = (
__mul__
) = (
__rmul__
) = (
__div__
) = (
__rdiv__
) = (
__truediv__
) = (
__rtruediv__
) = (
__floordiv__
) = (
__rfloordiv__
) = (
__mod__
) = (
__rmod__
) = (
__pos__
) = (
__neg__
) = (
__call__
) = (
__getitem__
) = (
__lt__
) = (
__le__
) = (
__gt__
) = (
__ge__
) = (
__int__
) = (
__float__
) = (
__complex__
) = __pow__ = __rpow__ = __sub__ = __rsub__ = _fail_with_undefined_error
__add__ = __radd__ = __sub__ = __rsub__ = _fail_with_undefined_error
__mul__ = __rmul__ = __div__ = __rdiv__ = _fail_with_undefined_error
__truediv__ = __rtruediv__ = _fail_with_undefined_error
__floordiv__ = __rfloordiv__ = _fail_with_undefined_error
__mod__ = __rmod__ = _fail_with_undefined_error
__pos__ = __neg__ = _fail_with_undefined_error
__call__ = __getitem__ = _fail_with_undefined_error
__lt__ = __le__ = __gt__ = __ge__ = _fail_with_undefined_error
__int__ = __float__ = __complex__ = _fail_with_undefined_error
__pow__ = __rpow__ = _fail_with_undefined_error
def __eq__(self, other):
return type(self) is type(other)
@ -817,11 +771,9 @@ class Undefined(object):
if 0:
yield None
def __nonzero__(self):
def __bool__(self):
return False
__bool__ = __nonzero__
def __repr__(self):
return "Undefined"
@ -858,7 +810,7 @@ def make_logging_undefined(logger=None, base=None):
if undef._undefined_hint is None:
if undef._undefined_obj is missing:
hint = "%s is undefined" % undef._undefined_name
elif not isinstance(undef._undefined_name, string_types):
elif not isinstance(undef._undefined_name, str):
hint = "%s has no element %s" % (
object_type_repr(undef._undefined_obj),
undef._undefined_name,
@ -890,31 +842,14 @@ def make_logging_undefined(logger=None, base=None):
_log_message(self)
return rv
if PY2:
def __nonzero__(self):
rv = base.__nonzero__(self)
_log_message(self)
return rv
def __unicode__(self):
rv = base.__unicode__(self)
_log_message(self)
return rv
else:
def __bool__(self):
rv = base.__bool__(self)
_log_message(self)
return rv
def __bool__(self):
rv = base.__bool__(self)
_log_message(self)
return rv
return LoggingUndefined
# No @implements_to_string decorator here because __str__
# is not overwritten from Undefined in this class.
# This would cause a recursion error in Python 2.
class ChainableUndefined(Undefined):
"""An undefined that is chainable, where both ``__getattr__`` and
``__getitem__`` return itself rather than raising an
@ -942,7 +877,6 @@ class ChainableUndefined(Undefined):
__getitem__ = __getattr__
@implements_to_string
class DebugUndefined(Undefined):
"""An undefined that returns the debug info when printed.
@ -970,7 +904,6 @@ class DebugUndefined(Undefined):
return u"{{ undefined value printed: %s }}" % self._undefined_hint
@implements_to_string
class StrictUndefined(Undefined):
"""An undefined that barks on print and iteration as well as boolean
tests and all kinds of comparisons. In other words: you can do nothing
@ -992,17 +925,12 @@ class StrictUndefined(Undefined):
"""
__slots__ = ()
__iter__ = (
__str__
) = (
__len__
) = (
__nonzero__
) = __eq__ = __ne__ = __bool__ = __hash__ = Undefined._fail_with_undefined_error
__iter__ = __str__ = __len__ = Undefined._fail_with_undefined_error
__eq__ = __ne__ = __bool__ = __hash__ = Undefined._fail_with_undefined_error
# remove remaining slots attributes, after the metaclass did the magic they
# are unneeded and irritating as they contain wrong data for the subclasses.
# Remove slots attributes, after the metaclass is applied they are
# unneeded and contain wrong data for subclasses.
del (
Undefined.__slots__,
ChainableUndefined.__slots__,

View File

@ -4,38 +4,24 @@ Useful when the template itself comes from an untrusted source.
"""
import operator
import types
from collections import abc
from collections import deque
from string import Formatter
from markupsafe import EscapeFormatter
from markupsafe import Markup
from ._compat import abc
from ._compat import PY2
from ._compat import range_type
from ._compat import string_types
from .environment import Environment
from .exceptions import SecurityError
#: maximum number of items a range may produce
MAX_RANGE = 100000
#: attributes of function objects that are considered unsafe.
if PY2:
UNSAFE_FUNCTION_ATTRIBUTES = {
"func_closure",
"func_code",
"func_dict",
"func_defaults",
"func_globals",
}
else:
# On versions > python 2 the special attributes on functions are gone,
# but they remain on methods and generators for whatever reason.
UNSAFE_FUNCTION_ATTRIBUTES = set()
#: Unsafe function attributes.
UNSAFE_FUNCTION_ATTRIBUTES = set()
#: unsafe method attributes. function attributes are unsafe for methods too
UNSAFE_METHOD_ATTRIBUTES = {"im_class", "im_func", "im_self"}
#: Unsafe method attributes. Function attributes are unsafe for methods too.
UNSAFE_METHOD_ATTRIBUTES = set()
#: unsafe generator attributes.
UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"}
@ -46,36 +32,9 @@ UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"}
#: unsafe attributes on async generators
UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"}
_mutable_set_types = (set,)
_mutable_mapping_types = (dict,)
_mutable_sequence_types = (list,)
# on python 2.x we can register the user collection types
try:
from UserDict import UserDict, DictMixin
from UserList import UserList
_mutable_mapping_types += (UserDict, DictMixin)
_mutable_set_types += (UserList,)
except ImportError:
pass
# if sets is still available, register the mutable set from there as well
try:
from sets import Set
_mutable_set_types += (Set,)
except ImportError:
pass
#: register Python 2.6 abstract base classes
_mutable_set_types += (abc.MutableSet,)
_mutable_mapping_types += (abc.MutableMapping,)
_mutable_sequence_types += (abc.MutableSequence,)
_mutable_spec = (
(
_mutable_set_types,
abc.MutableSet,
frozenset(
[
"add",
@ -90,11 +49,11 @@ _mutable_spec = (
),
),
(
_mutable_mapping_types,
abc.MutableMapping,
frozenset(["clear", "pop", "popitem", "setdefault", "update"]),
),
(
_mutable_sequence_types,
abc.MutableSequence,
frozenset(["append", "reverse", "insert", "sort", "extend", "remove"]),
),
(
@ -153,7 +112,7 @@ def inspect_format_method(callable):
) or callable.__name__ not in ("format", "format_map"):
return None
obj = callable.__self__
if isinstance(obj, string_types):
if isinstance(obj, str):
return obj
@ -161,7 +120,7 @@ def safe_range(*args):
"""A range that can't generate ranges with a length of more than
MAX_RANGE items.
"""
rng = range_type(*args)
rng = range(*args)
if len(rng) > MAX_RANGE:
raise OverflowError(
@ -376,7 +335,7 @@ class SandboxedEnvironment(Environment):
try:
return obj[argument]
except (TypeError, LookupError):
if isinstance(argument, string_types):
if isinstance(argument, str):
try:
attr = str(argument)
except Exception:

View File

@ -1,13 +1,10 @@
# -*- coding: utf-8 -*-
"""Built-in template tests used with the ``is`` operator."""
import decimal
import operator
import re
from collections import abc
from numbers import Number
from ._compat import abc
from ._compat import integer_types
from ._compat import string_types
from ._compat import text_type
from .runtime import Undefined
number_re = re.compile(r"^-?\d+(\.\d+)?$")
@ -87,7 +84,7 @@ def test_integer(value):
.. versionadded:: 2.11
"""
return isinstance(value, integer_types) and value is not True and value is not False
return isinstance(value, int) and value is not True and value is not False
# NOTE: The existing 'number' test matches booleans and integers
@ -101,17 +98,17 @@ def test_float(value):
def test_lower(value):
"""Return true if the variable is lowercased."""
return text_type(value).islower()
return str(value).islower()
def test_upper(value):
"""Return true if the variable is uppercased."""
return text_type(value).isupper()
return str(value).isupper()
def test_string(value):
"""Return true if the object is a string."""
return isinstance(value, string_types)
return isinstance(value, str)
def test_mapping(value):
@ -124,7 +121,7 @@ def test_mapping(value):
def test_number(value):
"""Return true if the variable is a number."""
return isinstance(value, integer_types + (float, complex, decimal.Decimal))
return isinstance(value, Number)
def test_sequence(value):

View File

@ -2,19 +2,16 @@
import json
import os
import re
from collections import abc
from collections import deque
from random import choice
from random import randrange
from threading import Lock
from urllib.parse import quote_from_bytes
from markupsafe import escape
from markupsafe import Markup
from ._compat import abc
from ._compat import string_types
from ._compat import text_type
from ._compat import url_quote
_word_split_re = re.compile(r"(\s+)")
_punctuation_re = re.compile(
"^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$"
@ -205,8 +202,8 @@ def urlize(text, trim_url_limit=None, rel=None, target=None):
and (x[:limit] + (len(x) >= limit and "..." or ""))
or x
)
words = _word_split_re.split(text_type(escape(text)))
rel_attr = rel and ' rel="%s"' % text_type(escape(rel)) or ""
words = _word_split_re.split(str(escape(text)))
rel_attr = rel and ' rel="%s"' % str(escape(rel)) or ""
target_attr = target and ' target="%s"' % escape(target) or ""
for i, word in enumerate(words):
@ -299,7 +296,7 @@ def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
return Markup(u"\n".join(u"<p>%s</p>" % escape(x) for x in result))
def unicode_urlencode(obj, charset="utf-8", for_qs=False):
def url_quote(obj, charset="utf-8", for_qs=False):
"""Quote a string for use in a URL using the given charset.
This function is misnamed, it is a wrapper around
@ -310,17 +307,14 @@ def unicode_urlencode(obj, charset="utf-8", for_qs=False):
:param charset: Encode text to bytes using this charset.
:param for_qs: Quote "/" and use "+" for spaces.
"""
if not isinstance(obj, string_types):
obj = text_type(obj)
if not isinstance(obj, bytes):
if not isinstance(obj, str):
obj = str(obj)
if isinstance(obj, text_type):
obj = obj.encode(charset)
safe = b"" if for_qs else b"/"
rv = url_quote(obj, safe)
if not isinstance(rv, text_type):
rv = rv.decode("utf-8")
rv = quote_from_bytes(obj, safe)
if for_qs:
rv = rv.replace("%20", "+")
@ -328,6 +322,19 @@ def unicode_urlencode(obj, charset="utf-8", for_qs=False):
return rv
def unicode_urlencode(obj, charset="utf-8", for_qs=False):
import warnings
warnings.warn(
"'unicode_urlencode' has been renamed to 'url_quote'. The old"
" name will be removed in version 3.1.",
DeprecationWarning,
stacklevel=2,
)
return url_quote(obj, charset=charset, for_qs=for_qs)
@abc.MutableMapping.register
class LRUCache(object):
"""A simple LRU Cache implementation."""
@ -484,9 +491,6 @@ class LRUCache(object):
__copy__ = copy
abc.MutableMapping.register(LRUCache)
def select_autoescape(
enabled_extensions=("html", "htm", "xml"),
disabled_extensions=(),

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import re
from io import BytesIO
import pytest
@ -7,9 +8,6 @@ from jinja2 import contextfunction
from jinja2 import DictLoader
from jinja2 import Environment
from jinja2 import nodes
from jinja2._compat import BytesIO
from jinja2._compat import itervalues
from jinja2._compat import text_type
from jinja2.exceptions import TemplateAssertionError
from jinja2.ext import Extension
from jinja2.lexer import count_newlines
@ -231,7 +229,7 @@ class TestExtensions(object):
original = Environment(extensions=[ExampleExtension])
overlay = original.overlay()
for env in original, overlay:
for ext in itervalues(env.extensions):
for ext in env.extensions.values():
assert ext.environment is env
def test_preprocessor_extension(self):
@ -619,7 +617,7 @@ class TestAutoEscape(object):
"""
tmpl = env.from_string(tmplsource)
assert tmpl.render(val=True).split()[0] == "Markup"
assert tmpl.render(val=False).split()[0] == text_type.__name__
assert tmpl.render(val=False).split()[0] == "str"
# looking at the source we should see <testing> there in raw
# (and then escaped as well)

View File

@ -1,14 +1,8 @@
import sys
import pytest
from jinja2 import contextfilter
from jinja2 import Environment
from jinja2 import Template
from jinja2._compat import text_type
@pytest.mark.skipif(sys.version_info < (3, 5), reason="Requires 3.5 or later")
def test_generator_stop():
class X(object):
def __getattr__(self, name):
@ -17,26 +11,3 @@ def test_generator_stop():
t = Template("a{{ bad.bar() }}b")
with pytest.raises(RuntimeError):
t.render(bad=X())
@pytest.mark.skipif(sys.version_info[0] > 2, reason="Feature only supported on 2.x")
def test_ascii_str():
@contextfilter
def assert_func(context, value):
assert type(value) is context["expected_type"]
env = Environment()
env.filters["assert"] = assert_func
env.policies["compiler.ascii_str"] = False
t = env.from_string('{{ "foo"|assert }}')
t.render(expected_type=text_type)
env.policies["compiler.ascii_str"] = True
t = env.from_string('{{ "foo"|assert }}')
t.render(expected_type=str)
for val in True, False:
env.policies["compiler.ascii_str"] = val
t = env.from_string(u'{{ "\N{SNOWMAN}"|assert }}')
t.render(expected_type=text_type)

View File

@ -6,27 +6,23 @@ import pytest
from jinja2 import Environment
from jinja2 import Markup
from jinja2._compat import implements_to_string
from jinja2._compat import text_type
@implements_to_string
class Magic(object):
def __init__(self, value):
self.value = value
def __str__(self):
return text_type(self.value)
return str(self.value)
@implements_to_string
class Magic2(object):
def __init__(self, value1, value2):
self.value1 = value1
self.value2 = value2
def __str__(self):
return u"(%s,%s)" % (text_type(self.value1), text_type(self.value2))
return u"(%s,%s)" % (str(self.value1), str(self.value2))
@pytest.mark.filter
@ -281,7 +277,7 @@ class TestFilter(object):
def test_string(self, env):
x = [1, 2, 3, 4, 5]
tmpl = env.from_string("""{{ obj|string }}""")
assert tmpl.render(obj=x) == text_type(x)
assert tmpl.render(obj=x) == str(x)
def test_title(self, env):
tmpl = env.from_string("""{{ "foo bar"|title }}""")

View File

@ -6,9 +6,6 @@ from jinja2 import nodes
from jinja2 import Template
from jinja2 import TemplateSyntaxError
from jinja2 import UndefinedError
from jinja2._compat import iteritems
from jinja2._compat import PY2
from jinja2._compat import text_type
from jinja2.lexer import Token
from jinja2.lexer import TOKEN_BLOCK_BEGIN
from jinja2.lexer import TOKEN_BLOCK_END
@ -16,17 +13,6 @@ from jinja2.lexer import TOKEN_EOF
from jinja2.lexer import TokenStream
# how does a string look like in jinja syntax?
if PY2:
def jinja_string_repr(string):
return repr(string)[1:]
else:
jinja_string_repr = repr
@pytest.mark.lexnparse
@pytest.mark.tokenstream
class TestTokenStream(object):
@ -111,7 +97,7 @@ class TestLexer(object):
def test_string_escapes(self, env):
for char in u"\0", u"\u2668", u"\xe4", u"\t", u"\r", u"\n":
tmpl = env.from_string("{{ %s }}" % jinja_string_repr(char))
tmpl = env.from_string("{{ %s }}" % repr(char))
assert tmpl.render() == char
assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == u"\u2668"
@ -124,7 +110,7 @@ class TestLexer(object):
def test_operators(self, env):
from jinja2.lexer import operators
for test, expect in iteritems(operators):
for test, expect in operators.items():
if test in "([{}])":
continue
stream = env.lexer.tokenize("{{ %s }}" % test)
@ -153,30 +139,30 @@ class TestLexer(object):
assert result == expect, (keep, template, result, expect)
@pytest.mark.parametrize(
"name,valid2,valid3",
(
(u"foo", True, True),
(u"föö", False, True),
(u"", False, True),
(u"_", True, True),
(u"1a", False, False), # invalid ascii start
(u"a-", False, False), # invalid ascii continue
(u"🐍", False, False), # invalid unicode start
(u"a🐍", False, False), # invalid unicode continue
("name", "valid"),
[
(u"foo", True),
(u"föö", True),
(u"", True),
(u"_", True),
(u"1a", False), # invalid ascii start
(u"a-", False), # invalid ascii continue
(u"🐍", False), # invalid unicode start
(u"a🐍", False), # invalid unicode continue
# start characters not matched by \w
(u"\u1885", False, True),
(u"\u1886", False, True),
(u"\u2118", False, True),
(u"\u212e", False, True),
(u"\u1885", True),
(u"\u1886", True),
(u"\u2118", True),
(u"\u212e", True),
# continue character not matched by \w
(u"\xb7", False, False),
(u"a\xb7", False, True),
),
(u"\xb7", False),
(u"a\xb7", True),
],
)
def test_name(self, env, name, valid2, valid3):
def test_name(self, env, name, valid):
t = u"{{ " + name + u" }}"
if (valid2 and PY2) or (valid3 and not PY2):
if valid:
# valid for version being tested, shouldn't raise
env.from_string(t)
else:
@ -528,7 +514,7 @@ class TestSyntax(object):
def test_operator_precedence(self, env):
tmpl = env.from_string("""{{ 2 * 3 + 4 % 2 + 1 - 2 }}""")
assert tmpl.render() == text_type(2 * 3 + 4 % 2 + 1 - 2)
assert tmpl.render() == "5"
def test_implicit_subscribed_tuple(self, env):
class Foo(object):

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import os
import platform
import shutil
import sys
import tempfile
@ -11,8 +12,6 @@ import pytest
from jinja2 import Environment
from jinja2 import loaders
from jinja2 import PackageLoader
from jinja2._compat import PY2
from jinja2._compat import PYPY
from jinja2.exceptions import TemplateNotFound
from jinja2.loaders import split_template_path
@ -138,25 +137,19 @@ class TestFileSystemLoader(object):
env = Environment(loader=filesystem_loader)
self._test_common(env)
@pytest.mark.skipif(PY2, reason="pathlib is not available in Python 2")
def test_searchpath_as_pathlib(self):
import pathlib
searchpath = pathlib.Path(self.searchpath)
filesystem_loader = loaders.FileSystemLoader(searchpath)
env = Environment(loader=filesystem_loader)
self._test_common(env)
@pytest.mark.skipif(PY2, reason="pathlib is not available in Python 2")
def test_searchpath_as_list_including_pathlib(self):
import pathlib
searchpath = pathlib.Path(self.searchpath)
filesystem_loader = loaders.FileSystemLoader(["/tmp/templates", searchpath])
env = Environment(loader=filesystem_loader)
self._test_common(env)
@ -265,17 +258,6 @@ class TestModuleLoader(object):
assert name not in sys.modules
# This test only makes sense on non-pypy python 2
@pytest.mark.skipif(
not (PY2 and not PYPY), reason="This test only makes sense on non-pypy python 2"
)
def test_byte_compilation(self, prefix_loader):
log = self.compile_down(prefix_loader, py_compile=True)
assert 'Byte-compiled "a/test.html"' in log
self.mod_env.get_template("a/test.html")
mod = self.mod_env.loader.module.tmpl_3c4ddf650c1a73df961a6d3d2ce2752f1b8fd490
assert mod.__file__.endswith(".pyc")
def test_choice_loader(self, prefix_loader):
self.compile_down(prefix_loader)
self.mod_env.loader = loaders.ChoiceLoader(
@ -299,7 +281,6 @@ class TestModuleLoader(object):
tmpl2 = self.mod_env.get_template("DICT/test.html")
assert tmpl2.render() == "DICT_TEMPLATE"
@pytest.mark.skipif(PY2, reason="pathlib is not available in Python 2")
def test_path_as_pathlib(self, prefix_loader):
self.compile_down(prefix_loader)
@ -312,7 +293,6 @@ class TestModuleLoader(object):
self._test_common()
@pytest.mark.skipif(PY2, reason="pathlib is not available in Python 2")
def test_supports_pathlib_in_list_of_paths(self, prefix_loader):
self.compile_down(prefix_loader)
@ -367,7 +347,7 @@ def test_package_zip_source(package_zip_loader, template, expect):
@pytest.mark.xfail(
PYPY,
platform.python_implementation() == "PyPy",
reason="PyPy's zipimporter doesn't have a _files attribute.",
raises=TypeError,
)

View File

@ -1,6 +1,5 @@
import pytest
from jinja2._compat import text_type
from jinja2.exceptions import UndefinedError
from jinja2.nativetypes import NativeEnvironment
from jinja2.nativetypes import NativeTemplate
@ -53,7 +52,7 @@ def test_multi_expression_add(env):
def test_loops(env):
t = env.from_string("{% for x in value %}{{ x }}{% endfor %}")
result = t.render(value=["a", "b", "c", "d"])
assert isinstance(result, text_type)
assert isinstance(result, str)
assert result == "abcd"
@ -111,7 +110,7 @@ def test_constant_dunder_to_string(env):
def test_string_literal_var(env):
t = env.from_string("[{{ 'all' }}]")
result = t.render()
assert isinstance(result, text_type)
assert isinstance(result, str)
assert result == "[all]"

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
import sys
import pytest
from jinja2 import DictLoader
@ -10,7 +8,6 @@ from jinja2 import Template
from jinja2 import TemplateAssertionError
from jinja2 import TemplateNotFound
from jinja2 import TemplateSyntaxError
from jinja2._compat import text_type
@pytest.mark.regression
@ -134,7 +131,7 @@ class TestBug(object):
"""
)
assert tmpl.render().split() == [text_type(x) for x in range(1, 11)] * 5
assert tmpl.render().split() == [str(x) for x in range(1, 11)] * 5
def test_weird_inline_comment(self, env):
env = Environment(line_statement_prefix="%")
@ -308,13 +305,6 @@ class TestBug(object):
assert output == expected
@pytest.mark.skipif(sys.version_info[0] > 2, reason="This only works on 2.x")
def test_old_style_attribute(self, env):
class Foo:
x = 42
assert env.getitem(Foo(), "x") == 42
def test_block_set_with_extends(self):
env = Environment(
loader=DictLoader({"main": "{% block body %}[{{ x }}]{% endblock %}"})

View File

@ -3,8 +3,6 @@ import pytest
from jinja2 import Environment
from jinja2 import escape
from jinja2 import Markup
from jinja2._compat import text_type
from jinja2.exceptions import SecurityError
from jinja2.exceptions import TemplateRuntimeError
from jinja2.exceptions import TemplateSyntaxError
@ -77,44 +75,6 @@ class TestSandbox(object):
"{% for foo, bar.baz in seq %}...{% endfor %}",
)
def test_markup_operations(self, env):
# adding two strings should escape the unsafe one
unsafe = '<script type="application/x-some-script">alert("foo");</script>'
safe = Markup("<em>username</em>")
assert unsafe + safe == text_type(escape(unsafe)) + text_type(safe)
# string interpolations are safe to use too
assert Markup("<em>%s</em>") % "<bad user>" == "<em>&lt;bad user&gt;</em>"
assert (
Markup("<em>%(username)s</em>") % {"username": "<bad user>"}
== "<em>&lt;bad user&gt;</em>"
)
# an escaped object is markup too
assert type(Markup("foo") + "bar") is Markup
# and it implements __html__ by returning itself
x = Markup("foo")
assert x.__html__() is x
# it also knows how to treat __html__ objects
class Foo(object):
def __html__(self):
return "<em>awesome</em>"
def __unicode__(self):
return "awesome"
assert Markup(Foo()) == "<em>awesome</em>"
assert (
Markup("<strong>%s</strong>") % Foo() == "<strong><em>awesome</em></strong>"
)
# escaping and unescaping
assert escape("\"<>&'") == "&#34;&lt;&gt;&amp;&#39;"
assert Markup("<em>Foo &amp; Bar</em>").striptags() == "Foo & Bar"
assert Markup("&lt;test&gt;").unescape() == "<test>"
def test_template_data(self, env):
env = Environment(autoescape=True)
t = env.from_string(
@ -124,7 +84,7 @@ class TestSandbox(object):
)
escaped_out = "<p>Hello &lt;blink&gt;foo&lt;/blink&gt;!</p>"
assert t.render() == escaped_out
assert text_type(t.module) == escaped_out
assert str(t.module) == escaped_out
assert escape(t.module) == escaped_out
assert t.module.say_hello("<blink>foo</blink>") == escaped_out
assert (

View File

@ -7,8 +7,6 @@ from copy import copy as shallow_copy
import pytest
from markupsafe import Markup
from jinja2._compat import range_type
from jinja2._compat import string_types
from jinja2.utils import consume
from jinja2.utils import generate_lorem_ipsum
from jinja2.utils import LRUCache
@ -165,26 +163,26 @@ class TestLoremIpsum(object):
def test_lorem_ipsum_html(self):
"""Test that output of lorem_ipsum is a string_type when not html."""
assert isinstance(generate_lorem_ipsum(html=False), string_types)
assert isinstance(generate_lorem_ipsum(html=False), str)
def test_lorem_ipsum_n(self):
"""Test that the n (number of lines) works as expected."""
assert generate_lorem_ipsum(n=0, html=False) == u""
for n in range_type(1, 50):
for n in range(1, 50):
assert generate_lorem_ipsum(n=n, html=False).count("\n") == (n - 1) * 2
def test_lorem_ipsum_min(self):
"""Test that at least min words are in the output of each line"""
for _ in range_type(5):
for _ in range(5):
m = random.randrange(20, 99)
for _ in range_type(10):
for _ in range(10):
assert generate_lorem_ipsum(n=1, min=m, html=False).count(" ") >= m - 1
def test_lorem_ipsum_max(self):
"""Test that at least max words are in the output of each line"""
for _ in range_type(5):
for _ in range(5):
m = random.randrange(21, 100)
for _ in range_type(10):
for _ in range(10):
assert generate_lorem_ipsum(n=1, max=m, html=False).count(" ") < m - 1