Bump mako from 1.1.6 to 1.2.0 (#1684)

* Bump mako from 1.1.6 to 1.2.0

Bumps [mako](https://github.com/sqlalchemy/mako) from 1.1.6 to 1.2.0.
- [Release notes](https://github.com/sqlalchemy/mako/releases)
- [Changelog](https://github.com/sqlalchemy/mako/blob/main/CHANGES)
- [Commits](https://github.com/sqlalchemy/mako/commits)

---
updated-dependencies:
- dependency-name: mako
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update mako==1.2.0

* Update MarkupSafe==2.1.1

* Add importlib-metadata==4.11.3

* Update requirements.txt

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com>

[skip ci]
This commit is contained in:
dependabot[bot] 2022-05-16 20:33:50 -07:00 committed by GitHub
parent aa0c58ef0e
commit 238afb4794
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 2948 additions and 848 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,68 @@
import re
import textwrap
import email.message
from ._text import FoldedCase
class Message(email.message.Message):
multiple_use_keys = set(
map(
FoldedCase,
[
'Classifier',
'Obsoletes-Dist',
'Platform',
'Project-URL',
'Provides-Dist',
'Provides-Extra',
'Requires-Dist',
'Requires-External',
'Supported-Platform',
'Dynamic',
],
)
)
"""
Keys that may be indicated multiple times per PEP 566.
"""
def __new__(cls, orig: email.message.Message):
res = super().__new__(cls)
vars(res).update(vars(orig))
return res
def __init__(self, *args, **kwargs):
self._headers = self._repair_headers()
# suppress spurious error from mypy
def __iter__(self):
return super().__iter__()
def _repair_headers(self):
def redent(value):
"Correct for RFC822 indentation"
if not value or '\n' not in value:
return value
return textwrap.dedent(' ' * 8 + value)
headers = [(key, redent(value)) for key, value in vars(self)['_headers']]
if self._payload:
headers.append(('Description', self.get_payload()))
return headers
@property
def json(self):
"""
Convert PackageMetadata to a JSON-compatible format
per PEP 0566.
"""
def transform(key):
value = self.get_all(key) if key in self.multiple_use_keys else self[key]
if key == 'Keywords':
value = re.split(r'\s+', value)
tk = key.lower().replace('-', '_')
return tk, value
return dict(map(transform, map(FoldedCase, self)))

View file

@ -0,0 +1,30 @@
import collections
# from jaraco.collections 3.3
class FreezableDefaultDict(collections.defaultdict):
"""
Often it is desirable to prevent the mutation of
a default dict after its initial construction, such
as to prevent mutation during iteration.
>>> dd = FreezableDefaultDict(list)
>>> dd[0].append('1')
>>> dd.freeze()
>>> dd[1]
[]
>>> len(dd)
1
"""
def __missing__(self, key):
return getattr(self, '_frozen', super().__missing__)(key)
def freeze(self):
self._frozen = lambda key: self.default_factory()
class Pair(collections.namedtuple('Pair', 'name value')):
@classmethod
def parse(cls, text):
return cls(*map(str.strip, text.split("=", 1)))

View file

@ -0,0 +1,71 @@
import sys
import platform
__all__ = ['install', 'NullFinder', 'Protocol']
try:
from typing import Protocol
except ImportError: # pragma: no cover
from typing_extensions import Protocol # type: ignore
def install(cls):
"""
Class decorator for installation on sys.meta_path.
Adds the backport DistributionFinder to sys.meta_path and
attempts to disable the finder functionality of the stdlib
DistributionFinder.
"""
sys.meta_path.append(cls())
disable_stdlib_finder()
return cls
def disable_stdlib_finder():
"""
Give the backport primacy for discovering path-based distributions
by monkey-patching the stdlib O_O.
See #91 for more background for rationale on this sketchy
behavior.
"""
def matches(finder):
return getattr(
finder, '__module__', None
) == '_frozen_importlib_external' and hasattr(finder, 'find_distributions')
for finder in filter(matches, sys.meta_path): # pragma: nocover
del finder.find_distributions
class NullFinder:
"""
A "Finder" (aka "MetaClassFinder") that never finds any modules,
but may find distributions.
"""
@staticmethod
def find_spec(*args, **kwargs):
return None
# In Python 2, the import system requires finders
# to have a find_module() method, but this usage
# is deprecated in Python 3 in favor of find_spec().
# For the purposes of this finder (i.e. being present
# on sys.meta_path but having no other import
# system functionality), the two methods are identical.
find_module = find_spec
def pypy_partial(val):
"""
Adjust for variable stacklevel on partial under PyPy.
Workaround for #327.
"""
is_pypy = platform.python_implementation() == 'PyPy'
return val + is_pypy

View file

@ -0,0 +1,104 @@
import types
import functools
# from jaraco.functools 3.3
def method_cache(method, cache_wrapper=None):
"""
Wrap lru_cache to support storing the cache data in the object instances.
Abstracts the common paradigm where the method explicitly saves an
underscore-prefixed protected property on first call and returns that
subsequently.
>>> class MyClass:
... calls = 0
...
... @method_cache
... def method(self, value):
... self.calls += 1
... return value
>>> a = MyClass()
>>> a.method(3)
3
>>> for x in range(75):
... res = a.method(x)
>>> a.calls
75
Note that the apparent behavior will be exactly like that of lru_cache
except that the cache is stored on each instance, so values in one
instance will not flush values from another, and when an instance is
deleted, so are the cached values for that instance.
>>> b = MyClass()
>>> for x in range(35):
... res = b.method(x)
>>> b.calls
35
>>> a.method(0)
0
>>> a.calls
75
Note that if method had been decorated with ``functools.lru_cache()``,
a.calls would have been 76 (due to the cached value of 0 having been
flushed by the 'b' instance).
Clear the cache with ``.cache_clear()``
>>> a.method.cache_clear()
Same for a method that hasn't yet been called.
>>> c = MyClass()
>>> c.method.cache_clear()
Another cache wrapper may be supplied:
>>> cache = functools.lru_cache(maxsize=2)
>>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache)
>>> a = MyClass()
>>> a.method2()
3
Caution - do not subsequently wrap the method with another decorator, such
as ``@property``, which changes the semantics of the function.
See also
http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
for another implementation and additional justification.
"""
cache_wrapper = cache_wrapper or functools.lru_cache()
def wrapper(self, *args, **kwargs):
# it's the first call, replace the method with a cached, bound method
bound_method = types.MethodType(method, self)
cached_method = cache_wrapper(bound_method)
setattr(self, method.__name__, cached_method)
return cached_method(*args, **kwargs)
# Support cache clear even before cache has been created.
wrapper.cache_clear = lambda: None
return wrapper
# From jaraco.functools 3.3
def pass_none(func):
"""
Wrap func so it's not called if its first param is None
>>> print_text = pass_none(print)
>>> print_text('text')
text
>>> print_text(None)
"""
@functools.wraps(func)
def wrapper(param, *args, **kwargs):
if param is not None:
return func(param, *args, **kwargs)
return wrapper

View file

@ -0,0 +1,73 @@
from itertools import filterfalse
def unique_everseen(iterable, key=None):
"List unique elements, preserving order. Remember all elements ever seen."
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
# unique_everseen('ABBCcAD', str.lower) --> A B C D
seen = set()
seen_add = seen.add
if key is None:
for element in filterfalse(seen.__contains__, iterable):
seen_add(element)
yield element
else:
for element in iterable:
k = key(element)
if k not in seen:
seen_add(k)
yield element
# copied from more_itertools 8.8
def always_iterable(obj, base_type=(str, bytes)):
"""If *obj* is iterable, return an iterator over its items::
>>> obj = (1, 2, 3)
>>> list(always_iterable(obj))
[1, 2, 3]
If *obj* is not iterable, return a one-item iterable containing *obj*::
>>> obj = 1
>>> list(always_iterable(obj))
[1]
If *obj* is ``None``, return an empty iterable:
>>> obj = None
>>> list(always_iterable(None))
[]
By default, binary and text strings are not considered iterable::
>>> obj = 'foo'
>>> list(always_iterable(obj))
['foo']
If *base_type* is set, objects for which ``isinstance(obj, base_type)``
returns ``True`` won't be considered iterable.
>>> obj = {'a': 1}
>>> list(always_iterable(obj)) # Iterate over the dict's keys
['a']
>>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit
[{'a': 1}]
Set *base_type* to ``None`` to avoid any special handling and treat objects
Python considers iterable as iterable:
>>> obj = 'foo'
>>> list(always_iterable(obj, base_type=None))
['f', 'o', 'o']
"""
if obj is None:
return iter(())
if (base_type is not None) and isinstance(obj, base_type):
return iter((obj,))
try:
return iter(obj)
except TypeError:
return iter((obj,))

View file

@ -0,0 +1,48 @@
from ._compat import Protocol
from typing import Any, Dict, Iterator, List, TypeVar, Union
_T = TypeVar("_T")
class PackageMetadata(Protocol):
def __len__(self) -> int:
... # pragma: no cover
def __contains__(self, item: str) -> bool:
... # pragma: no cover
def __getitem__(self, key: str) -> str:
... # pragma: no cover
def __iter__(self) -> Iterator[str]:
... # pragma: no cover
def get_all(self, name: str, failobj: _T = ...) -> Union[List[Any], _T]:
"""
Return all values associated with a possibly multi-valued key.
"""
@property
def json(self) -> Dict[str, Union[str, List[str]]]:
"""
A JSON-compatible form of the metadata.
"""
class SimplePath(Protocol):
"""
A minimal subset of pathlib.Path required by PathDistribution.
"""
def joinpath(self) -> 'SimplePath':
... # pragma: no cover
def __truediv__(self) -> 'SimplePath':
... # pragma: no cover
def parent(self) -> 'SimplePath':
... # pragma: no cover
def read_text(self) -> str:
... # pragma: no cover

View file

@ -0,0 +1,99 @@
import re
from ._functools import method_cache
# from jaraco.text 3.5
class FoldedCase(str):
"""
A case insensitive string class; behaves just like str
except compares equal when the only variation is case.
>>> s = FoldedCase('hello world')
>>> s == 'Hello World'
True
>>> 'Hello World' == s
True
>>> s != 'Hello World'
False
>>> s.index('O')
4
>>> s.split('O')
['hell', ' w', 'rld']
>>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta']))
['alpha', 'Beta', 'GAMMA']
Sequence membership is straightforward.
>>> "Hello World" in [s]
True
>>> s in ["Hello World"]
True
You may test for set inclusion, but candidate and elements
must both be folded.
>>> FoldedCase("Hello World") in {s}
True
>>> s in {FoldedCase("Hello World")}
True
String inclusion works as long as the FoldedCase object
is on the right.
>>> "hello" in FoldedCase("Hello World")
True
But not if the FoldedCase object is on the left:
>>> FoldedCase('hello') in 'Hello World'
False
In that case, use in_:
>>> FoldedCase('hello').in_('Hello World')
True
>>> FoldedCase('hello') > FoldedCase('Hello')
False
"""
def __lt__(self, other):
return self.lower() < other.lower()
def __gt__(self, other):
return self.lower() > other.lower()
def __eq__(self, other):
return self.lower() == other.lower()
def __ne__(self, other):
return self.lower() != other.lower()
def __hash__(self):
return hash(self.lower())
def __contains__(self, other):
return super().lower().__contains__(other.lower())
def in_(self, other):
"Does self appear in other?"
return self in FoldedCase(other)
# cache lower since it's likely to be called frequently.
@method_cache
def lower(self):
return super().lower()
def index(self, sub):
return self.lower().index(sub.lower())
def split(self, splitter=' ', maxsplit=0):
pattern = re.compile(re.escape(splitter), re.I)
return pattern.split(self, maxsplit)

View file

View file

@ -1,8 +1,8 @@
# mako/__init__.py # mako/__init__.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
__version__ = "1.1.6" __version__ = "1.2.0"

View file

@ -1,5 +1,5 @@
# mako/_ast_util.py # mako/_ast_util.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -47,7 +47,6 @@ from _ast import Sub
from _ast import UAdd from _ast import UAdd
from _ast import USub from _ast import USub
from mako.compat import arg_stringname
BOOLOP_SYMBOLS = {And: "and", Or: "or"} BOOLOP_SYMBOLS = {And: "and", Or: "or"}
@ -94,9 +93,7 @@ def parse(expr, filename="<unknown>", mode="exec"):
def iter_fields(node): def iter_fields(node):
"""Iterate over all fields of a node, only yielding existing fields.""" """Iterate over all fields of a node, only yielding existing fields."""
# CPython 2.5 compat
if not hasattr(node, "_fields") or not node._fields:
return
for field in node._fields: for field in node._fields:
try: try:
yield field, getattr(node, field) yield field, getattr(node, field)
@ -104,7 +101,7 @@ def iter_fields(node):
pass pass
class NodeVisitor(object): class NodeVisitor:
""" """
Walks the abstract syntax tree and call visitor functions for every node Walks the abstract syntax tree and call visitor functions for every node
@ -266,10 +263,10 @@ class SourceGenerator(NodeVisitor):
self.visit(default) self.visit(default)
if node.vararg is not None: if node.vararg is not None:
write_comma() write_comma()
self.write("*" + arg_stringname(node.vararg)) self.write("*" + node.vararg.arg)
if node.kwarg is not None: if node.kwarg is not None:
write_comma() write_comma()
self.write("**" + arg_stringname(node.kwarg)) self.write("**" + node.kwarg.arg)
def decorators(self, node): def decorators(self, node):
for decorator in node.decorator_list: for decorator in node.decorator_list:

View file

@ -1,5 +1,5 @@
# mako/ast.py # mako/ast.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -9,12 +9,11 @@ code, as well as generating Python from AST nodes"""
import re import re
from mako import compat
from mako import exceptions from mako import exceptions
from mako import pyparser from mako import pyparser
class PythonCode(object): class PythonCode:
"""represents information about a string containing Python code""" """represents information about a string containing Python code"""
@ -39,7 +38,7 @@ class PythonCode(object):
# - AST is less likely to break with version changes # - AST is less likely to break with version changes
# (for example, the behavior of co_names changed a little bit # (for example, the behavior of co_names changed a little bit
# in python version 2.5) # in python version 2.5)
if isinstance(code, compat.string_types): if isinstance(code, str):
expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs) expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs)
else: else:
expr = code expr = code
@ -48,7 +47,7 @@ class PythonCode(object):
f.visit(expr) f.visit(expr)
class ArgumentList(object): class ArgumentList:
"""parses a fragment of code as a comma-separated list of expressions""" """parses a fragment of code as a comma-separated list of expressions"""
@ -57,7 +56,7 @@ class ArgumentList(object):
self.args = [] self.args = []
self.declared_identifiers = set() self.declared_identifiers = set()
self.undeclared_identifiers = set() self.undeclared_identifiers = set()
if isinstance(code, compat.string_types): if isinstance(code, str):
if re.match(r"\S", code) and not re.match(r",\s*$", code): if re.match(r"\S", code) and not re.match(r",\s*$", code):
# if theres text and no trailing comma, insure its parsed # if theres text and no trailing comma, insure its parsed
# as a tuple by adding a trailing comma # as a tuple by adding a trailing comma
@ -88,7 +87,7 @@ class PythonFragment(PythonCode):
if not m: if not m:
raise exceptions.CompileException( raise exceptions.CompileException(
"Fragment '%s' is not a partial control statement" % code, "Fragment '%s' is not a partial control statement" % code,
**exception_kwargs **exception_kwargs,
) )
if m.group(3): if m.group(3):
code = code[: m.start(3)] code = code[: m.start(3)]
@ -97,7 +96,7 @@ class PythonFragment(PythonCode):
code = code + "pass" code = code + "pass"
elif keyword == "try": elif keyword == "try":
code = code + "pass\nexcept:pass" code = code + "pass\nexcept:pass"
elif keyword == "elif" or keyword == "else": elif keyword in ["elif", "else"]:
code = "if False:pass\n" + code + "pass" code = "if False:pass\n" + code + "pass"
elif keyword == "except": elif keyword == "except":
code = "try:pass\n" + code + "pass" code = "try:pass\n" + code + "pass"
@ -106,12 +105,12 @@ class PythonFragment(PythonCode):
else: else:
raise exceptions.CompileException( raise exceptions.CompileException(
"Unsupported control keyword: '%s'" % keyword, "Unsupported control keyword: '%s'" % keyword,
**exception_kwargs **exception_kwargs,
) )
super(PythonFragment, self).__init__(code, **exception_kwargs) super().__init__(code, **exception_kwargs)
class FunctionDecl(object): class FunctionDecl:
"""function declaration""" """function declaration"""
@ -124,13 +123,13 @@ class FunctionDecl(object):
if not hasattr(self, "funcname"): if not hasattr(self, "funcname"):
raise exceptions.CompileException( raise exceptions.CompileException(
"Code '%s' is not a function declaration" % code, "Code '%s' is not a function declaration" % code,
**exception_kwargs **exception_kwargs,
) )
if not allow_kwargs and self.kwargs: if not allow_kwargs and self.kwargs:
raise exceptions.CompileException( raise exceptions.CompileException(
"'**%s' keyword argument not allowed here" "'**%s' keyword argument not allowed here"
% self.kwargnames[-1], % self.kwargnames[-1],
**exception_kwargs **exception_kwargs,
) )
def get_argument_expressions(self, as_call=False): def get_argument_expressions(self, as_call=False):
@ -200,6 +199,4 @@ class FunctionArgs(FunctionDecl):
"""the argument portion of a function declaration""" """the argument portion of a function declaration"""
def __init__(self, code, **kwargs): def __init__(self, code, **kwargs):
super(FunctionArgs, self).__init__( super().__init__("def ANON(%s):pass" % code, **kwargs)
"def ANON(%s):pass" % code, **kwargs
)

View file

@ -1,10 +1,9 @@
# mako/cache.py # mako/cache.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
from mako import compat
from mako import util from mako import util
_cache_plugins = util.PluginLoader("mako.cache") _cache_plugins = util.PluginLoader("mako.cache")
@ -13,7 +12,7 @@ register_plugin = _cache_plugins.register
register_plugin("beaker", "mako.ext.beaker_cache", "BeakerCacheImpl") register_plugin("beaker", "mako.ext.beaker_cache", "BeakerCacheImpl")
class Cache(object): class Cache:
"""Represents a data content cache made available to the module """Represents a data content cache made available to the module
space of a specific :class:`.Template` object. space of a specific :class:`.Template` object.
@ -66,7 +65,7 @@ class Cache(object):
def __init__(self, template, *args): def __init__(self, template, *args):
# check for a stale template calling the # check for a stale template calling the
# constructor # constructor
if isinstance(template, compat.string_types) and args: if isinstance(template, str) and args:
return return
self.template = template self.template = template
self.id = template.module.__name__ self.id = template.module.__name__
@ -181,7 +180,7 @@ class Cache(object):
return tmpl_kw return tmpl_kw
class CacheImpl(object): class CacheImpl:
"""Provide a cache implementation for use by :class:`.Cache`.""" """Provide a cache implementation for use by :class:`.Cache`."""

View file

@ -1,10 +1,9 @@
# mako/cmd.py # mako/cmd.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
from argparse import ArgumentParser from argparse import ArgumentParser
import io
from os.path import dirname from os.path import dirname
from os.path import isfile from os.path import isfile
import sys import sys
@ -85,16 +84,14 @@ def cmdline(argv=None):
except: except:
_exit() _exit()
kw = dict([varsplit(var) for var in options.var]) kw = dict(varsplit(var) for var in options.var)
try: try:
rendered = template.render(**kw) rendered = template.render(**kw)
except: except:
_exit() _exit()
else: else:
if output_file: if output_file:
io.open(output_file, "wt", encoding=output_encoding).write( open(output_file, "wt", encoding=output_encoding).write(rendered)
rendered
)
else: else:
sys.stdout.write(rendered) sys.stdout.write(rendered)

View file

@ -1,5 +1,5 @@
# mako/codegen.py # mako/codegen.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -12,7 +12,6 @@ import re
import time import time
from mako import ast from mako import ast
from mako import compat
from mako import exceptions from mako import exceptions
from mako import filters from mako import filters
from mako import parsetree from mako import parsetree
@ -25,8 +24,8 @@ MAGIC_NUMBER = 10
# names which are hardwired into the # names which are hardwired into the
# template and are not accessed via the # template and are not accessed via the
# context itself # context itself
TOPLEVEL_DECLARED = set(["UNDEFINED", "STOP_RENDERING"]) TOPLEVEL_DECLARED = {"UNDEFINED", "STOP_RENDERING"}
RESERVED_NAMES = set(["context", "loop"]).union(TOPLEVEL_DECLARED) RESERVED_NAMES = {"context", "loop"}.union(TOPLEVEL_DECLARED)
def compile( # noqa def compile( # noqa
@ -39,7 +38,6 @@ def compile( # noqa
future_imports=None, future_imports=None,
source_encoding=None, source_encoding=None,
generate_magic_comment=True, generate_magic_comment=True,
disable_unicode=False,
strict_undefined=False, strict_undefined=False,
enable_loop=True, enable_loop=True,
reserved_names=frozenset(), reserved_names=frozenset(),
@ -47,13 +45,6 @@ def compile( # noqa
"""Generate module source code given a parsetree node, """Generate module source code given a parsetree node,
uri, and optional source filename""" uri, and optional source filename"""
# if on Py2K, push the "source_encoding" string to be
# a bytestring itself, as we will be embedding it into
# the generated source and we don't want to coerce the
# result into a unicode object, in "disable_unicode" mode
if not compat.py3k and isinstance(source_encoding, compat.text_type):
source_encoding = source_encoding.encode(source_encoding)
buf = util.FastEncodingBuffer() buf = util.FastEncodingBuffer()
printer = PythonPrinter(buf) printer = PythonPrinter(buf)
@ -68,7 +59,6 @@ def compile( # noqa
future_imports, future_imports,
source_encoding, source_encoding,
generate_magic_comment, generate_magic_comment,
disable_unicode,
strict_undefined, strict_undefined,
enable_loop, enable_loop,
reserved_names, reserved_names,
@ -78,7 +68,7 @@ def compile( # noqa
return buf.getvalue() return buf.getvalue()
class _CompileContext(object): class _CompileContext:
def __init__( def __init__(
self, self,
uri, uri,
@ -89,7 +79,6 @@ class _CompileContext(object):
future_imports, future_imports,
source_encoding, source_encoding,
generate_magic_comment, generate_magic_comment,
disable_unicode,
strict_undefined, strict_undefined,
enable_loop, enable_loop,
reserved_names, reserved_names,
@ -102,13 +91,12 @@ class _CompileContext(object):
self.future_imports = future_imports self.future_imports = future_imports
self.source_encoding = source_encoding self.source_encoding = source_encoding
self.generate_magic_comment = generate_magic_comment self.generate_magic_comment = generate_magic_comment
self.disable_unicode = disable_unicode
self.strict_undefined = strict_undefined self.strict_undefined = strict_undefined
self.enable_loop = enable_loop self.enable_loop = enable_loop
self.reserved_names = reserved_names self.reserved_names = reserved_names
class _GenerateRenderMethod(object): class _GenerateRenderMethod:
"""A template visitor object which generates the """A template visitor object which generates the
full module source for a template. full module source for a template.
@ -196,7 +184,7 @@ class _GenerateRenderMethod(object):
self.compiler.pagetag = None self.compiler.pagetag = None
class FindTopLevel(object): class FindTopLevel:
def visitInheritTag(s, node): def visitInheritTag(s, node):
inherit.append(node) inherit.append(node)
@ -392,7 +380,7 @@ class _GenerateRenderMethod(object):
identifiers = self.compiler.identifiers.branch(node) identifiers = self.compiler.identifiers.branch(node)
self.in_def = True self.in_def = True
class NSDefVisitor(object): class NSDefVisitor:
def visitDefTag(s, node): def visitDefTag(s, node):
s.visitDefOrBase(node) s.visitDefOrBase(node)
@ -404,7 +392,7 @@ class _GenerateRenderMethod(object):
raise exceptions.CompileException( raise exceptions.CompileException(
"Can't put anonymous blocks inside " "Can't put anonymous blocks inside "
"<%namespace>", "<%namespace>",
**node.exception_kwargs **node.exception_kwargs,
) )
self.write_inline_def(node, identifiers, nested=False) self.write_inline_def(node, identifiers, nested=False)
export.append(node.funcname) export.append(node.funcname)
@ -481,7 +469,7 @@ class _GenerateRenderMethod(object):
""" """
# collection of all defs available to us in this scope # collection of all defs available to us in this scope
comp_idents = dict([(c.funcname, c) for c in identifiers.defs]) comp_idents = {c.funcname: c for c in identifiers.defs}
to_write = set() to_write = set()
# write "context.get()" for all variables we are going to # write "context.get()" for all variables we are going to
@ -794,8 +782,6 @@ class _GenerateRenderMethod(object):
def locate_encode(name): def locate_encode(name):
if re.match(r"decode\..+", name): if re.match(r"decode\..+", name):
return "filters." + name return "filters." + name
elif self.compiler.disable_unicode:
return filters.NON_UNICODE_ESCAPES.get(name, name)
else: else:
return filters.DEFAULT_ESCAPES.get(name, name) return filters.DEFAULT_ESCAPES.get(name, name)
@ -859,11 +845,11 @@ class _GenerateRenderMethod(object):
# and end control lines, and # and end control lines, and
# 3) any control line with no content other than comments # 3) any control line with no content other than comments
if not children or ( if not children or (
compat.all( all(
isinstance(c, (parsetree.Comment, parsetree.ControlLine)) isinstance(c, (parsetree.Comment, parsetree.ControlLine))
for c in children for c in children
) )
and compat.all( and all(
(node.is_ternary(c.keyword) or c.isend) (node.is_ternary(c.keyword) or c.isend)
for c in children for c in children
if isinstance(c, parsetree.ControlLine) if isinstance(c, parsetree.ControlLine)
@ -969,7 +955,7 @@ class _GenerateRenderMethod(object):
self.identifier_stack.append(body_identifiers) self.identifier_stack.append(body_identifiers)
class DefVisitor(object): class DefVisitor:
def visitDefTag(s, node): def visitDefTag(s, node):
s.visitDefOrBase(node) s.visitDefOrBase(node)
@ -1025,7 +1011,7 @@ class _GenerateRenderMethod(object):
) )
class _Identifiers(object): class _Identifiers:
"""tracks the status of identifier names as template code is rendered.""" """tracks the status of identifier names as template code is rendered."""
@ -1170,7 +1156,7 @@ class _Identifiers(object):
raise exceptions.CompileException( raise exceptions.CompileException(
"%%def or %%block named '%s' already " "%%def or %%block named '%s' already "
"exists in this template." % node.funcname, "exists in this template." % node.funcname,
**node.exception_kwargs **node.exception_kwargs,
) )
def visitDefTag(self, node): def visitDefTag(self, node):
@ -1200,7 +1186,7 @@ class _Identifiers(object):
raise exceptions.CompileException( raise exceptions.CompileException(
"Named block '%s' not allowed inside of def '%s'" "Named block '%s' not allowed inside of def '%s'"
% (node.name, self.node.name), % (node.name, self.node.name),
**node.exception_kwargs **node.exception_kwargs,
) )
elif isinstance( elif isinstance(
self.node, (parsetree.CallTag, parsetree.CallNamespaceTag) self.node, (parsetree.CallTag, parsetree.CallNamespaceTag)
@ -1208,7 +1194,7 @@ class _Identifiers(object):
raise exceptions.CompileException( raise exceptions.CompileException(
"Named block '%s' not allowed inside of <%%call> tag" "Named block '%s' not allowed inside of <%%call> tag"
% (node.name,), % (node.name,),
**node.exception_kwargs **node.exception_kwargs,
) )
for ident in node.undeclared_identifiers(): for ident in node.undeclared_identifiers():
@ -1293,7 +1279,7 @@ def mangle_mako_loop(node, printer):
return text return text
class LoopVariable(object): class LoopVariable:
"""A node visitor which looks for the name 'loop' within undeclared """A node visitor which looks for the name 'loop' within undeclared
identifiers.""" identifiers."""

View file

@ -1,19 +1,17 @@
# mako/compat.py # mako/compat.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
import collections import collections
from importlib import util
import inspect import inspect
import sys import sys
py3k = sys.version_info >= (3, 0)
py2k = sys.version_info < (3,)
py27 = sys.version_info >= (2, 7)
jython = sys.platform.startswith("java")
win32 = sys.platform.startswith("win") win32 = sys.platform.startswith("win")
pypy = hasattr(sys, "pypy_version_info") pypy = hasattr(sys, "pypy_version_info")
py38 = sys.version_info >= (3, 8)
ArgSpec = collections.namedtuple( ArgSpec = collections.namedtuple(
"ArgSpec", ["args", "varargs", "keywords", "defaults"] "ArgSpec", ["args", "varargs", "keywords", "defaults"]
@ -26,15 +24,15 @@ def inspect_getargspec(func):
if inspect.ismethod(func): if inspect.ismethod(func):
func = func.__func__ func = func.__func__
if not inspect.isfunction(func): if not inspect.isfunction(func):
raise TypeError("{!r} is not a Python function".format(func)) raise TypeError(f"{func!r} is not a Python function")
co = func.__code__ co = func.__code__
if not inspect.iscode(co): if not inspect.iscode(co):
raise TypeError("{!r} is not a code object".format(co)) raise TypeError(f"{co!r} is not a code object")
nargs = co.co_argcount nargs = co.co_argcount
names = co.co_varnames names = co.co_varnames
nkwargs = co.co_kwonlyargcount if py3k else 0 nkwargs = co.co_kwonlyargcount
args = list(names[:nargs]) args = list(names[:nargs])
nargs += nkwargs nargs += nkwargs
@ -49,129 +47,30 @@ def inspect_getargspec(func):
return ArgSpec(args, varargs, varkw, func.__defaults__) return ArgSpec(args, varargs, varkw, func.__defaults__)
if py3k: def load_module(module_id, path):
from io import StringIO
import builtins as compat_builtins
from urllib.parse import quote_plus, unquote_plus
from html.entities import codepoint2name, name2codepoint
string_types = (str,)
binary_type = bytes
text_type = str
from io import BytesIO as byte_buffer
def u(s):
return s
def b(s):
return s.encode("latin-1")
def octal(lit):
return eval("0o" + lit)
else:
import __builtin__ as compat_builtins # noqa
try:
from cStringIO import StringIO
except:
from StringIO import StringIO
byte_buffer = StringIO
from urllib import quote_plus, unquote_plus # noqa
from htmlentitydefs import codepoint2name, name2codepoint # noqa
string_types = (basestring,) # noqa
binary_type = str
text_type = unicode # noqa
def u(s):
return unicode(s, "utf-8") # noqa
def b(s):
return s
def octal(lit):
return eval("0" + lit)
if py3k:
from importlib import machinery, util
if hasattr(util, 'module_from_spec'):
# Python 3.5+
def load_module(module_id, path):
spec = util.spec_from_file_location(module_id, path) spec = util.spec_from_file_location(module_id, path)
module = util.module_from_spec(spec) module = util.module_from_spec(spec)
spec.loader.exec_module(module) spec.loader.exec_module(module)
return module return module
else:
def load_module(module_id, path):
module = machinery.SourceFileLoader(module_id, path).load_module()
del sys.modules[module_id]
return module
else:
import imp
def load_module(module_id, path):
fp = open(path, "rb")
try:
module = imp.load_source(module_id, path, fp)
del sys.modules[module_id]
return module
finally:
fp.close()
if py3k:
def reraise(tp, value, tb=None, cause=None):
if cause is not None:
value.__cause__ = cause
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
else:
exec(
"def reraise(tp, value, tb=None, cause=None):\n"
" raise tp, value, tb\n"
)
def exception_as(): def exception_as():
return sys.exc_info()[1] return sys.exc_info()[1]
all = all # noqa
def exception_name(exc): def exception_name(exc):
return exc.__class__.__name__ return exc.__class__.__name__
################################################ if py38:
# cross-compatible metaclass implementation from importlib import metadata as importlib_metadata
# Copyright (c) 2010-2012 Benjamin Peterson else:
def with_metaclass(meta, base=object): import importlib_metadata # noqa
"""Create a base class with a metaclass."""
return meta("%sBase" % meta.__name__, (base,), {})
################################################ def importlib_metadata_get(group):
ep = importlib_metadata.entry_points()
if hasattr(ep, "select"):
def arg_stringname(func_arg): return ep.select(group=group)
"""Gets the string name of a kwarg or vararg
In Python3.4 a function's args are
of _ast.arg type not _ast.name
"""
if hasattr(func_arg, "arg"):
return func_arg.arg
else: else:
return str(func_arg) return ep.get(group, ())

View file

@ -1,5 +1,5 @@
# mako/exceptions.py # mako/exceptions.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -68,7 +68,7 @@ class TopLevelLookupException(TemplateLookupException):
pass pass
class RichTraceback(object): class RichTraceback:
"""Pull the current exception from the ``sys`` traceback and extracts """Pull the current exception from the ``sys`` traceback and extracts
Mako-specific template information. Mako-specific template information.
@ -106,7 +106,7 @@ class RichTraceback(object):
def _init_message(self): def _init_message(self):
"""Find a unicode representation of self.error""" """Find a unicode representation of self.error"""
try: try:
self.message = compat.text_type(self.error) self.message = str(self.error)
except UnicodeError: except UnicodeError:
try: try:
self.message = str(self.error) self.message = str(self.error)
@ -114,8 +114,8 @@ class RichTraceback(object):
# Fallback to args as neither unicode nor # Fallback to args as neither unicode nor
# str(Exception(u'\xe6')) work in Python < 2.6 # str(Exception(u'\xe6')) work in Python < 2.6
self.message = self.error.args[0] self.message = self.error.args[0]
if not isinstance(self.message, compat.text_type): if not isinstance(self.message, str):
self.message = compat.text_type(self.message, "ascii", "replace") self.message = str(self.message, "ascii", "replace")
def _get_reformatted_records(self, records): def _get_reformatted_records(self, records):
for rec in records: for rec in records:
@ -139,8 +139,7 @@ class RichTraceback(object):
@property @property
def reverse_traceback(self): def reverse_traceback(self):
"""Return the same data as traceback, except in reverse order. """Return the same data as traceback, except in reverse order."""
"""
return list(self._get_reformatted_records(self.reverse_records)) return list(self._get_reformatted_records(self.reverse_records))
@ -170,17 +169,6 @@ class RichTraceback(object):
) )
except KeyError: except KeyError:
# A normal .py file (not a Template) # A normal .py file (not a Template)
if not compat.py3k:
try:
fp = open(filename, "rb")
encoding = util.parse_encoding(fp)
fp.close()
except IOError:
encoding = None
if encoding:
line = line.decode(encoding)
else:
line = line.decode("ascii", "replace")
new_trcback.append( new_trcback.append(
( (
filename, filename,
@ -236,13 +224,12 @@ class RichTraceback(object):
if new_trcback: if new_trcback:
try: try:
# A normal .py file (not a Template) # A normal .py file (not a Template)
fp = open(new_trcback[-1][0], "rb") with open(new_trcback[-1][0], "rb") as fp:
encoding = util.parse_encoding(fp) encoding = util.parse_encoding(fp)
if compat.py3k and not encoding: if not encoding:
encoding = "utf-8" encoding = "utf-8"
fp.seek(0) fp.seek(0)
self.source = fp.read() self.source = fp.read()
fp.close()
if encoding: if encoding:
self.source = self.source.decode(encoding) self.source = self.source.decode(encoding)
except IOError: except IOError:

View file

@ -1,5 +1,5 @@
# ext/autohandler.py # ext/autohandler.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php

View file

@ -1,10 +1,10 @@
# ext/babelplugin.py # ext/babelplugin.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
"""gettext message extraction via Babel: http://babel.edgewall.org/""" """gettext message extraction via Babel: https://pypi.org/project/Babel/"""
from babel.messages.extract import extract_python from babel.messages.extract import extract_python
from mako.ext.extract import MessageExtractor from mako.ext.extract import MessageExtractor
@ -15,12 +15,12 @@ class BabelMakoExtractor(MessageExtractor):
self.keywords = keywords self.keywords = keywords
self.options = options self.options = options
self.config = { self.config = {
"comment-tags": u" ".join(comment_tags), "comment-tags": " ".join(comment_tags),
"encoding": options.get( "encoding": options.get(
"input_encoding", options.get("encoding", None) "input_encoding", options.get("encoding", None)
), ),
} }
super(BabelMakoExtractor, self).__init__() super().__init__()
def __call__(self, fileobj): def __call__(self, fileobj):
return self.process_file(fileobj) return self.process_file(fileobj)
@ -54,5 +54,4 @@ def extract(fileobj, keywords, comment_tags, options):
:rtype: ``iterator`` :rtype: ``iterator``
""" """
extractor = BabelMakoExtractor(keywords, comment_tags, options) extractor = BabelMakoExtractor(keywords, comment_tags, options)
for message in extractor(fileobj): yield from extractor(fileobj)
yield message

View file

@ -1,5 +1,5 @@
# ext/beaker_cache.py # ext/beaker_cache.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -40,7 +40,7 @@ class BeakerCacheImpl(CacheImpl):
_beaker_cache = cache.template.cache_args["manager"] _beaker_cache = cache.template.cache_args["manager"]
else: else:
_beaker_cache = beaker_cache.CacheManager() _beaker_cache = beaker_cache.CacheManager()
super(BeakerCacheImpl, self).__init__(cache) super().__init__(cache)
def _get_cache(self, **kw): def _get_cache(self, **kw):
expiretime = kw.pop("timeout", None) expiretime = kw.pop("timeout", None)

View file

@ -1,23 +1,25 @@
# ext/extract.py # ext/extract.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
from io import BytesIO
from io import StringIO
import re import re
from mako import compat
from mako import lexer from mako import lexer
from mako import parsetree from mako import parsetree
class MessageExtractor(object): class MessageExtractor:
use_bytes = True
def process_file(self, fileobj): def process_file(self, fileobj):
template_node = lexer.Lexer( template_node = lexer.Lexer(
fileobj.read(), input_encoding=self.config["encoding"] fileobj.read(), input_encoding=self.config["encoding"]
).parse() ).parse()
for extracted in self.extract_nodes(template_node.get_children()): yield from self.extract_nodes(template_node.get_children())
yield extracted
def extract_nodes(self, nodes): def extract_nodes(self, nodes):
translator_comments = [] translator_comments = []
@ -90,7 +92,7 @@ class MessageExtractor(object):
comment[1] for comment in translator_comments comment[1] for comment in translator_comments
] ]
if isinstance(code, compat.text_type): if isinstance(code, str) and self.use_bytes:
code = code.encode(input_encoding, "backslashreplace") code = code.encode(input_encoding, "backslashreplace")
used_translator_comments = False used_translator_comments = False
@ -99,7 +101,10 @@ class MessageExtractor(object):
# input string of the input is non-ascii) # input string of the input is non-ascii)
# Also, because we added it, we have to subtract one from # Also, because we added it, we have to subtract one from
# node.lineno # node.lineno
code = compat.byte_buffer(compat.b("\n") + code) if self.use_bytes:
code = BytesIO(b"\n" + code)
else:
code = StringIO("\n" + code)
for message in self.process_python( for message in self.process_python(
code, node.lineno - 1, translator_strings code, node.lineno - 1, translator_strings
@ -112,8 +117,7 @@ class MessageExtractor(object):
in_translator_comments = False in_translator_comments = False
if child_nodes: if child_nodes:
for extracted in self.extract_nodes(child_nodes): yield from self.extract_nodes(child_nodes)
yield extracted
@staticmethod @staticmethod
def _split_comment(lineno, comment): def _split_comment(lineno, comment):

View file

@ -1,23 +1,23 @@
# ext/linguaplugin.py # ext/linguaplugin.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
import contextlib
import io import io
from lingua.extractors import Extractor from lingua.extractors import Extractor
from lingua.extractors import get_extractor from lingua.extractors import get_extractor
from lingua.extractors import Message from lingua.extractors import Message
from mako import compat
from mako.ext.extract import MessageExtractor from mako.ext.extract import MessageExtractor
class LinguaMakoExtractor(Extractor, MessageExtractor): class LinguaMakoExtractor(Extractor, MessageExtractor):
"""Mako templates""" """Mako templates"""
use_bytes = False
extensions = [".mako"] extensions = [".mako"]
default_config = {"encoding": "utf-8", "comment-tags": ""} default_config = {"encoding": "utf-8", "comment-tags": ""}
@ -26,29 +26,21 @@ class LinguaMakoExtractor(Extractor, MessageExtractor):
self.filename = filename self.filename = filename
self.python_extractor = get_extractor("x.py") self.python_extractor = get_extractor("x.py")
if fileobj is None: if fileobj is None:
fileobj = open(filename, "rb") ctx = open(filename, "r")
must_close = True
else: else:
must_close = False ctx = contextlib.nullcontext(fileobj)
try: with ctx as file_:
for message in self.process_file(fileobj): yield from self.process_file(file_)
yield message
finally:
if must_close:
fileobj.close()
def process_python(self, code, code_lineno, translator_strings): def process_python(self, code, code_lineno, translator_strings):
source = code.getvalue().strip() source = code.getvalue().strip()
if source.endswith(compat.b(":")): if source.endswith(":"):
if source in ( if source in ("try:", "else:") or source.startswith("except"):
compat.b("try:"), source = "" # Ignore try/except and else
compat.b("else:"), elif source.startswith("elif"):
) or source.startswith(compat.b("except")):
source = compat.b("") # Ignore try/except and else
elif source.startswith(compat.b("elif")):
source = source[2:] # Replace "elif" with "if" source = source[2:] # Replace "elif" with "if"
source += compat.b("pass") source += "pass"
code = io.BytesIO(source) code = io.StringIO(source)
for msg in self.python_extractor( for msg in self.python_extractor(
self.filename, self.options, code, code_lineno - 1 self.filename, self.options, code, code_lineno - 1
): ):
@ -58,7 +50,7 @@ class LinguaMakoExtractor(Extractor, MessageExtractor):
msg.msgid, msg.msgid,
msg.msgid_plural, msg.msgid_plural,
msg.flags, msg.flags,
compat.u(" ").join(translator_strings + [msg.comment]), " ".join(translator_strings + [msg.comment]),
msg.tcomment, msg.tcomment,
msg.location, msg.location,
) )

View file

@ -1,5 +1,5 @@
# ext/preprocessors.py # ext/preprocessors.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php

View file

@ -1,5 +1,5 @@
# ext/pygmentplugin.py # ext/pygmentplugin.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -25,8 +25,6 @@ from pygments.token import Other
from pygments.token import String from pygments.token import String
from pygments.token import Text from pygments.token import Text
from mako import compat
class MakoLexer(RegexLexer): class MakoLexer(RegexLexer):
name = "Mako" name = "Mako"
@ -108,7 +106,7 @@ class MakoHtmlLexer(DelegatingLexer):
aliases = ["html+mako"] aliases = ["html+mako"]
def __init__(self, **options): def __init__(self, **options):
super(MakoHtmlLexer, self).__init__(HtmlLexer, MakoLexer, **options) super().__init__(HtmlLexer, MakoLexer, **options)
class MakoXmlLexer(DelegatingLexer): class MakoXmlLexer(DelegatingLexer):
@ -116,7 +114,7 @@ class MakoXmlLexer(DelegatingLexer):
aliases = ["xml+mako"] aliases = ["xml+mako"]
def __init__(self, **options): def __init__(self, **options):
super(MakoXmlLexer, self).__init__(XmlLexer, MakoLexer, **options) super().__init__(XmlLexer, MakoLexer, **options)
class MakoJavascriptLexer(DelegatingLexer): class MakoJavascriptLexer(DelegatingLexer):
@ -124,9 +122,7 @@ class MakoJavascriptLexer(DelegatingLexer):
aliases = ["js+mako", "javascript+mako"] aliases = ["js+mako", "javascript+mako"]
def __init__(self, **options): def __init__(self, **options):
super(MakoJavascriptLexer, self).__init__( super().__init__(JavascriptLexer, MakoLexer, **options)
JavascriptLexer, MakoLexer, **options
)
class MakoCssLexer(DelegatingLexer): class MakoCssLexer(DelegatingLexer):
@ -134,7 +130,7 @@ class MakoCssLexer(DelegatingLexer):
aliases = ["css+mako"] aliases = ["css+mako"]
def __init__(self, **options): def __init__(self, **options):
super(MakoCssLexer, self).__init__(CssLexer, MakoLexer, **options) super().__init__(CssLexer, MakoLexer, **options)
pygments_html_formatter = HtmlFormatter( pygments_html_formatter = HtmlFormatter(
@ -144,10 +140,7 @@ pygments_html_formatter = HtmlFormatter(
def syntax_highlight(filename="", language=None): def syntax_highlight(filename="", language=None):
mako_lexer = MakoLexer() mako_lexer = MakoLexer()
if compat.py3k:
python_lexer = Python3Lexer() python_lexer = Python3Lexer()
else:
python_lexer = PythonLexer()
if filename.startswith("memory:") or language == "mako": if filename.startswith("memory:") or language == "mako":
return lambda string: highlight( return lambda string: highlight(
string, mako_lexer, pygments_html_formatter string, mako_lexer, pygments_html_formatter

View file

@ -1,5 +1,5 @@
# ext/turbogears.py # ext/turbogears.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -9,7 +9,7 @@ from mako.lookup import TemplateLookup
from mako.template import Template from mako.template import Template
class TGPlugin(object): class TGPlugin:
"""TurboGears compatible Template Plugin.""" """TurboGears compatible Template Plugin."""
@ -51,7 +51,7 @@ class TGPlugin(object):
def render( def render(
self, info, format="html", fragment=False, template=None # noqa self, info, format="html", fragment=False, template=None # noqa
): ):
if isinstance(template, compat.string_types): if isinstance(template, str):
template = self.load_template(template) template = self.load_template(template)
# Load extra vars func if provided # Load extra vars func if provided

View file

@ -1,18 +1,19 @@
# mako/filters.py # mako/filters.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
import codecs import codecs
from html.entities import codepoint2name
from html.entities import name2codepoint
import re import re
from urllib.parse import quote_plus
from mako import compat import markupsafe
from mako.compat import codepoint2name
from mako.compat import name2codepoint html_escape = markupsafe.escape
from mako.compat import quote_plus
from mako.compat import unquote_plus
xml_escapes = { xml_escapes = {
"&": "&amp;", "&": "&amp;",
@ -22,27 +23,6 @@ xml_escapes = {
"'": "&#39;", # also &apos; in html-only "'": "&#39;", # also &apos; in html-only
} }
# XXX: &quot; is valid in HTML and XML
# &apos; is not valid HTML, but is valid XML
def legacy_html_escape(s):
"""legacy HTML escape for non-unicode mode."""
s = s.replace("&", "&amp;")
s = s.replace(">", "&gt;")
s = s.replace("<", "&lt;")
s = s.replace('"', "&#34;")
s = s.replace("'", "&#39;")
return s
try:
import markupsafe
html_escape = markupsafe.escape
except ImportError:
html_escape = legacy_html_escape
def xml_escape(string): def xml_escape(string):
return re.sub(r'([&<"\'>])', lambda m: xml_escapes[m.group()], string) return re.sub(r'([&<"\'>])', lambda m: xml_escapes[m.group()], string)
@ -54,31 +34,19 @@ def url_escape(string):
return quote_plus(string) return quote_plus(string)
def legacy_url_escape(string):
# convert into a list of octets
return quote_plus(string)
def url_unescape(string):
text = unquote_plus(string)
if not is_ascii_str(text):
text = text.decode("utf8")
return text
def trim(string): def trim(string):
return string.strip() return string.strip()
class Decode(object): class Decode:
def __getattr__(self, key): def __getattr__(self, key):
def decode(x): def decode(x):
if isinstance(x, compat.text_type): if isinstance(x, str):
return x return x
elif not isinstance(x, compat.binary_type): elif not isinstance(x, bytes):
return decode(str(x)) return decode(str(x))
else: else:
return compat.text_type(x, encoding=key) return str(x, encoding=key)
return decode return decode
@ -86,24 +54,11 @@ class Decode(object):
decode = Decode() decode = Decode()
_ASCII_re = re.compile(r"\A[\x00-\x7f]*\Z") class XMLEntityEscaper:
def is_ascii_str(text):
return isinstance(text, str) and _ASCII_re.match(text)
################################################################
class XMLEntityEscaper(object):
def __init__(self, codepoint2name, name2codepoint): def __init__(self, codepoint2name, name2codepoint):
self.codepoint2entity = dict( self.codepoint2entity = {
[ c: str("&%s;" % n) for c, n in codepoint2name.items()
(c, compat.text_type("&%s;" % n)) }
for c, n in codepoint2name.items()
]
)
self.name2codepoint = name2codepoint self.name2codepoint = name2codepoint
def escape_entities(self, text): def escape_entities(self, text):
@ -111,7 +66,7 @@ class XMLEntityEscaper(object):
Only characters corresponding to a named entity are replaced. Only characters corresponding to a named entity are replaced.
""" """
return compat.text_type(text).translate(self.codepoint2entity) return str(text).translate(self.codepoint2entity)
def __escape(self, m): def __escape(self, m):
codepoint = ord(m.group()) codepoint = ord(m.group())
@ -131,9 +86,7 @@ class XMLEntityEscaper(object):
The return value is guaranteed to be ASCII. The return value is guaranteed to be ASCII.
""" """
return self.__escapable.sub( return self.__escapable.sub(self.__escape, str(text)).encode("ascii")
self.__escape, compat.text_type(text)
).encode("ascii")
# XXX: This regexp will not match all valid XML entity names__. # XXX: This regexp will not match all valid XML entity names__.
# (It punts on details involving involving CombiningChars and Extenders.) # (It punts on details involving involving CombiningChars and Extenders.)
@ -183,37 +136,28 @@ def htmlentityreplace_errors(ex):
characters with HTML entities, or, if no HTML entity exists for characters with HTML entities, or, if no HTML entity exists for
the character, XML character references:: the character, XML character references::
>>> u'The cost was \u20ac12.'.encode('latin1', 'htmlentityreplace') >>> 'The cost was \u20ac12.'.encode('latin1', 'htmlentityreplace')
'The cost was &euro;12.' 'The cost was &euro;12.'
""" """
if isinstance(ex, UnicodeEncodeError): if isinstance(ex, UnicodeEncodeError):
# Handle encoding errors # Handle encoding errors
bad_text = ex.object[ex.start : ex.end] bad_text = ex.object[ex.start : ex.end]
text = _html_entities_escaper.escape(bad_text) text = _html_entities_escaper.escape(bad_text)
return (compat.text_type(text), ex.end) return (str(text), ex.end)
raise ex raise ex
codecs.register_error("htmlentityreplace", htmlentityreplace_errors) codecs.register_error("htmlentityreplace", htmlentityreplace_errors)
# TODO: options to make this dynamic per-compilation will be added in a later
# release
DEFAULT_ESCAPES = { DEFAULT_ESCAPES = {
"x": "filters.xml_escape", "x": "filters.xml_escape",
"h": "filters.html_escape", "h": "filters.html_escape",
"u": "filters.url_escape", "u": "filters.url_escape",
"trim": "filters.trim", "trim": "filters.trim",
"entity": "filters.html_entities_escape", "entity": "filters.html_entities_escape",
"unicode": "unicode", "unicode": "str",
"decode": "decode", "decode": "decode",
"str": "str", "str": "str",
"n": "n", "n": "n",
} }
if compat.py3k:
DEFAULT_ESCAPES.update({"unicode": "str"})
NON_UNICODE_ESCAPES = DEFAULT_ESCAPES.copy()
NON_UNICODE_ESCAPES["h"] = "filters.legacy_html_escape"
NON_UNICODE_ESCAPES["u"] = "filters.legacy_url_escape"

View file

@ -1,5 +1,5 @@
# mako/lexer.py # mako/lexer.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -9,7 +9,6 @@
import codecs import codecs
import re import re
from mako import compat
from mako import exceptions from mako import exceptions
from mako import parsetree from mako import parsetree
from mako.pygen import adjust_whitespace from mako.pygen import adjust_whitespace
@ -17,14 +16,9 @@ from mako.pygen import adjust_whitespace
_regexp_cache = {} _regexp_cache = {}
class Lexer(object): class Lexer:
def __init__( def __init__(
self, self, text, filename=None, input_encoding=None, preprocessor=None
text,
filename=None,
disable_unicode=False,
input_encoding=None,
preprocessor=None,
): ):
self.text = text self.text = text
self.filename = filename self.filename = filename
@ -36,14 +30,8 @@ class Lexer(object):
self.tag = [] self.tag = []
self.control_line = [] self.control_line = []
self.ternary_stack = [] self.ternary_stack = []
self.disable_unicode = disable_unicode
self.encoding = input_encoding self.encoding = input_encoding
if compat.py3k and disable_unicode:
raise exceptions.UnsupportedError(
"Mako for Python 3 does not " "support disabling Unicode"
)
if preprocessor is None: if preprocessor is None:
self.preprocessor = [] self.preprocessor = []
elif not hasattr(preprocessor, "__iter__"): elif not hasattr(preprocessor, "__iter__"):
@ -66,10 +54,7 @@ class Lexer(object):
try: try:
reg = _regexp_cache[(regexp, flags)] reg = _regexp_cache[(regexp, flags)]
except KeyError: except KeyError:
if flags: reg = re.compile(regexp, flags) if flags else re.compile(regexp)
reg = re.compile(regexp, flags)
else:
reg = re.compile(regexp)
_regexp_cache[(regexp, flags)] = reg _regexp_cache[(regexp, flags)] = reg
return self.match_reg(reg) return self.match_reg(reg)
@ -87,10 +72,7 @@ class Lexer(object):
match = reg.match(self.text, self.match_position) match = reg.match(self.text, self.match_position)
if match: if match:
(start, end) = match.span() (start, end) = match.span()
if end == start: self.match_position = end + 1 if end == start else end
self.match_position = end + 1
else:
self.match_position = end
self.matched_lineno = self.lineno self.matched_lineno = self.lineno
lines = re.findall(r"\n", self.text[mp : self.match_position]) lines = re.findall(r"\n", self.text[mp : self.match_position])
cp = mp - 1 cp = mp - 1
@ -98,10 +80,6 @@ class Lexer(object):
cp -= 1 cp -= 1
self.matched_charpos = mp - cp self.matched_charpos = mp - cp
self.lineno += len(lines) self.lineno += len(lines)
# print "MATCHED:", match.group(0), "LINE START:",
# self.matched_lineno, "LINE END:", self.lineno
# print "MATCH:", regexp, "\n", self.text[mp : mp + 15], \
# (match and "TRUE" or "FALSE")
return match return match
def parse_until_text(self, watch_nesting, *text): def parse_until_text(self, watch_nesting, *text):
@ -161,11 +139,14 @@ class Lexer(object):
if self.control_line: if self.control_line:
control_frame = self.control_line[-1] control_frame = self.control_line[-1]
control_frame.nodes.append(node) control_frame.nodes.append(node)
if not ( if (
not (
isinstance(node, parsetree.ControlLine) isinstance(node, parsetree.ControlLine)
and control_frame.is_ternary(node.keyword) and control_frame.is_ternary(node.keyword)
)
and self.ternary_stack
and self.ternary_stack[-1]
): ):
if self.ternary_stack and self.ternary_stack[-1]:
self.ternary_stack[-1][-1].nodes.append(node) self.ternary_stack[-1][-1].nodes.append(node)
if isinstance(node, parsetree.Tag): if isinstance(node, parsetree.Tag):
if len(self.tag): if len(self.tag):
@ -188,7 +169,7 @@ class Lexer(object):
raise exceptions.SyntaxException( raise exceptions.SyntaxException(
"Keyword '%s' not a legal ternary for keyword '%s'" "Keyword '%s' not a legal ternary for keyword '%s'"
% (node.keyword, self.control_line[-1].keyword), % (node.keyword, self.control_line[-1].keyword),
**self.exception_kwargs **self.exception_kwargs,
) )
_coding_re = re.compile(r"#.*coding[:=]\s*([-\w.]+).*\r?\n") _coding_re = re.compile(r"#.*coding[:=]\s*([-\w.]+).*\r?\n")
@ -199,7 +180,7 @@ class Lexer(object):
or raw if decode_raw=False or raw if decode_raw=False
""" """
if isinstance(text, compat.text_type): if isinstance(text, str):
m = self._coding_re.match(text) m = self._coding_re.match(text)
encoding = m and m.group(1) or known_encoding or "utf-8" encoding = m and m.group(1) or known_encoding or "utf-8"
return encoding, text return encoding, text
@ -219,11 +200,7 @@ class Lexer(object):
) )
else: else:
m = self._coding_re.match(text.decode("utf-8", "ignore")) m = self._coding_re.match(text.decode("utf-8", "ignore"))
if m: parsed_encoding = m.group(1) if m else known_encoding or "utf-8"
parsed_encoding = m.group(1)
else:
parsed_encoding = known_encoding or "utf-8"
if decode_raw: if decode_raw:
try: try:
text = text.decode(parsed_encoding) text = text.decode(parsed_encoding)
@ -241,7 +218,7 @@ class Lexer(object):
def parse(self): def parse(self):
self.encoding, self.text = self.decode_raw_stream( self.encoding, self.text = self.decode_raw_stream(
self.text, not self.disable_unicode, self.encoding, self.filename self.text, True, self.encoding, self.filename
) )
for preproc in self.preprocessor: for preproc in self.preprocessor:
@ -276,12 +253,13 @@ class Lexer(object):
if self.match_position > self.textlength: if self.match_position > self.textlength:
break break
raise exceptions.CompileException("assertion failed") # TODO: no coverage here
raise exceptions.MakoException("assertion failed")
if len(self.tag): if len(self.tag):
raise exceptions.SyntaxException( raise exceptions.SyntaxException(
"Unclosed tag: <%%%s>" % self.tag[-1].keyword, "Unclosed tag: <%%%s>" % self.tag[-1].keyword,
**self.exception_kwargs **self.exception_kwargs,
) )
if len(self.control_line): if len(self.control_line):
raise exceptions.SyntaxException( raise exceptions.SyntaxException(
@ -312,7 +290,9 @@ class Lexer(object):
re.I | re.S | re.X, re.I | re.S | re.X,
) )
if match: if not match:
return False
keyword, attr, isend = match.groups() keyword, attr, isend = match.groups()
self.keyword = keyword self.keyword = keyword
attributes = {} attributes = {}
@ -327,19 +307,16 @@ class Lexer(object):
self.append_node(parsetree.Tag, keyword, attributes) self.append_node(parsetree.Tag, keyword, attributes)
if isend: if isend:
self.tag.pop() self.tag.pop()
else: elif keyword == "text":
if keyword == "text":
match = self.match(r"(.*?)(?=\</%text>)", re.S) match = self.match(r"(.*?)(?=\</%text>)", re.S)
if not match: if not match:
raise exceptions.SyntaxException( raise exceptions.SyntaxException(
"Unclosed tag: <%%%s>" % self.tag[-1].keyword, "Unclosed tag: <%%%s>" % self.tag[-1].keyword,
**self.exception_kwargs **self.exception_kwargs,
) )
self.append_node(parsetree.Text, match.group(1)) self.append_node(parsetree.Text, match.group(1))
return self.match_tag_end() return self.match_tag_end()
return True return True
else:
return False
def match_tag_end(self): def match_tag_end(self):
match = self.match(r"\</%[\t ]*(.+?)[\t ]*>") match = self.match(r"\</%[\t ]*(.+?)[\t ]*>")
@ -348,13 +325,13 @@ class Lexer(object):
raise exceptions.SyntaxException( raise exceptions.SyntaxException(
"Closing tag without opening tag: </%%%s>" "Closing tag without opening tag: </%%%s>"
% match.group(1), % match.group(1),
**self.exception_kwargs **self.exception_kwargs,
) )
elif self.tag[-1].keyword != match.group(1): elif self.tag[-1].keyword != match.group(1):
raise exceptions.SyntaxException( raise exceptions.SyntaxException(
"Closing tag </%%%s> does not match tag: <%%%s>" "Closing tag </%%%s> does not match tag: <%%%s>"
% (match.group(1), self.tag[-1].keyword), % (match.group(1), self.tag[-1].keyword),
**self.exception_kwargs **self.exception_kwargs,
) )
self.tag.pop() self.tag.pop()
return True return True
@ -363,14 +340,14 @@ class Lexer(object):
def match_end(self): def match_end(self):
match = self.match(r"\Z", re.S) match = self.match(r"\Z", re.S)
if match: if not match:
return False
string = match.group() string = match.group()
if string: if string:
return string return string
else: else:
return True return True
else:
return False
def match_text(self): def match_text(self):
match = self.match( match = self.match(
@ -422,7 +399,9 @@ class Lexer(object):
def match_expression(self): def match_expression(self):
match = self.match(r"\${") match = self.match(r"\${")
if match: if not match:
return False
line, pos = self.matched_lineno, self.matched_charpos line, pos = self.matched_lineno, self.matched_charpos
text, end = self.parse_until_text(True, r"\|", r"}") text, end = self.parse_until_text(True, r"\|", r"}")
if end == "|": if end == "|":
@ -438,8 +417,6 @@ class Lexer(object):
pos=pos, pos=pos,
) )
return True return True
else:
return False
def match_control_line(self): def match_control_line(self):
match = self.match( match = self.match(
@ -447,7 +424,9 @@ class Lexer(object):
r"(?:\r?\n|\Z)", r"(?:\r?\n|\Z)",
re.M, re.M,
) )
if match: if not match:
return False
operator = match.group(1) operator = match.group(1)
text = match.group(2) text = match.group(2)
if operator == "%": if operator == "%":
@ -455,7 +434,7 @@ class Lexer(object):
if not m2: if not m2:
raise exceptions.SyntaxException( raise exceptions.SyntaxException(
"Invalid control line: '%s'" % text, "Invalid control line: '%s'" % text,
**self.exception_kwargs **self.exception_kwargs,
) )
isend, keyword = m2.group(1, 2) isend, keyword = m2.group(1, 2)
isend = isend is not None isend = isend is not None
@ -463,22 +442,19 @@ class Lexer(object):
if isend: if isend:
if not len(self.control_line): if not len(self.control_line):
raise exceptions.SyntaxException( raise exceptions.SyntaxException(
"No starting keyword '%s' for '%s'" "No starting keyword '%s' for '%s'" % (keyword, text),
% (keyword, text), **self.exception_kwargs,
**self.exception_kwargs
) )
elif self.control_line[-1].keyword != keyword: elif self.control_line[-1].keyword != keyword:
raise exceptions.SyntaxException( raise exceptions.SyntaxException(
"Keyword '%s' doesn't match keyword '%s'" "Keyword '%s' doesn't match keyword '%s'"
% (text, self.control_line[-1].keyword), % (text, self.control_line[-1].keyword),
**self.exception_kwargs **self.exception_kwargs,
) )
self.append_node(parsetree.ControlLine, keyword, isend, text) self.append_node(parsetree.ControlLine, keyword, isend, text)
else: else:
self.append_node(parsetree.Comment, text) self.append_node(parsetree.Comment, text)
return True return True
else:
return False
def match_comment(self): def match_comment(self):
"""matches the multiline version of a comment""" """matches the multiline version of a comment"""

View file

@ -1,5 +1,5 @@
# mako/lookup.py # mako/lookup.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -8,18 +8,14 @@ import os
import posixpath import posixpath
import re import re
import stat import stat
import threading
from mako import exceptions from mako import exceptions
from mako import util from mako import util
from mako.template import Template from mako.template import Template
try:
import threading
except:
import dummy_threading as threading
class TemplateCollection:
class TemplateCollection(object):
"""Represent a collection of :class:`.Template` objects, """Represent a collection of :class:`.Template` objects,
identifiable via URI. identifiable via URI.
@ -161,8 +157,6 @@ class TemplateLookup(TemplateCollection):
collection_size=-1, collection_size=-1,
format_exceptions=False, format_exceptions=False,
error_handler=None, error_handler=None,
disable_unicode=False,
bytestring_passthrough=False,
output_encoding=None, output_encoding=None,
encoding_errors="strict", encoding_errors="strict",
cache_args=None, cache_args=None,
@ -207,8 +201,6 @@ class TemplateLookup(TemplateCollection):
"format_exceptions": format_exceptions, "format_exceptions": format_exceptions,
"error_handler": error_handler, "error_handler": error_handler,
"include_error_handler": include_error_handler, "include_error_handler": include_error_handler,
"disable_unicode": disable_unicode,
"bytestring_passthrough": bytestring_passthrough,
"output_encoding": output_encoding, "output_encoding": output_encoding,
"cache_impl": cache_impl, "cache_impl": cache_impl,
"encoding_errors": encoding_errors, "encoding_errors": encoding_errors,
@ -249,7 +241,7 @@ class TemplateLookup(TemplateCollection):
return self._check(uri, self._collection[uri]) return self._check(uri, self._collection[uri])
else: else:
return self._collection[uri] return self._collection[uri]
except KeyError: except KeyError as e:
u = re.sub(r"^\/+", "", uri) u = re.sub(r"^\/+", "", uri)
for dir_ in self.directories: for dir_ in self.directories:
# make sure the path seperators are posix - os.altsep is empty # make sure the path seperators are posix - os.altsep is empty
@ -260,8 +252,8 @@ class TemplateLookup(TemplateCollection):
return self._load(srcfile, uri) return self._load(srcfile, uri)
else: else:
raise exceptions.TopLevelLookupException( raise exceptions.TopLevelLookupException(
"Cant locate template for uri %r" % uri "Can't locate template for uri %r" % uri
) ) from e
def adjust_uri(self, uri, relativeto): def adjust_uri(self, uri, relativeto):
"""Adjust the given ``uri`` based on the given relative URI.""" """Adjust the given ``uri`` based on the given relative URI."""
@ -270,15 +262,14 @@ class TemplateLookup(TemplateCollection):
if key in self._uri_cache: if key in self._uri_cache:
return self._uri_cache[key] return self._uri_cache[key]
if uri[0] != "/": if uri[0] == "/":
if relativeto is not None: v = self._uri_cache[key] = uri
elif relativeto is not None:
v = self._uri_cache[key] = posixpath.join( v = self._uri_cache[key] = posixpath.join(
posixpath.dirname(relativeto), uri posixpath.dirname(relativeto), uri
) )
else: else:
v = self._uri_cache[key] = "/" + uri v = self._uri_cache[key] = "/" + uri
else:
v = self._uri_cache[key] = uri
return v return v
def filename_to_uri(self, filename): def filename_to_uri(self, filename):
@ -324,7 +315,7 @@ class TemplateLookup(TemplateCollection):
filename=posixpath.normpath(filename), filename=posixpath.normpath(filename),
lookup=self, lookup=self,
module_filename=module_filename, module_filename=module_filename,
**self.template_args **self.template_args,
) )
return template return template
except: except:
@ -342,16 +333,15 @@ class TemplateLookup(TemplateCollection):
try: try:
template_stat = os.stat(template.filename) template_stat = os.stat(template.filename)
if template.module._modified_time < template_stat[stat.ST_MTIME]: if template.module._modified_time >= template_stat[stat.ST_MTIME]:
return template
self._collection.pop(uri, None) self._collection.pop(uri, None)
return self._load(template.filename, uri) return self._load(template.filename, uri)
else: except OSError as e:
return template
except OSError:
self._collection.pop(uri, None) self._collection.pop(uri, None)
raise exceptions.TemplateLookupException( raise exceptions.TemplateLookupException(
"Cant locate template for uri %r" % uri "Can't locate template for uri %r" % uri
) ) from e
def put_string(self, uri, text): def put_string(self, uri, text):
"""Place a new :class:`.Template` object into this """Place a new :class:`.Template` object into this

View file

@ -1,5 +1,5 @@
# mako/parsetree.py # mako/parsetree.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -9,13 +9,12 @@
import re import re
from mako import ast from mako import ast
from mako import compat
from mako import exceptions from mako import exceptions
from mako import filters from mako import filters
from mako import util from mako import util
class Node(object): class Node:
"""base class for a Node in the parse tree.""" """base class for a Node in the parse tree."""
@ -51,7 +50,7 @@ class TemplateNode(Node):
"""a 'container' node that stores the overall collection of nodes.""" """a 'container' node that stores the overall collection of nodes."""
def __init__(self, filename): def __init__(self, filename):
super(TemplateNode, self).__init__("", 0, 0, filename) super().__init__("", 0, 0, filename)
self.nodes = [] self.nodes = []
self.page_attributes = {} self.page_attributes = {}
@ -80,7 +79,7 @@ class ControlLine(Node):
has_loop_context = False has_loop_context = False
def __init__(self, keyword, isend, text, **kwargs): def __init__(self, keyword, isend, text, **kwargs):
super(ControlLine, self).__init__(**kwargs) super().__init__(**kwargs)
self.text = text self.text = text
self.keyword = keyword self.keyword = keyword
self.isend = isend self.isend = isend
@ -107,11 +106,13 @@ class ControlLine(Node):
"""return true if the given keyword is a ternary keyword """return true if the given keyword is a ternary keyword
for this ControlLine""" for this ControlLine"""
return keyword in { cases = {
"if": set(["else", "elif"]), "if": {"else", "elif"},
"try": set(["except", "finally"]), "try": {"except", "finally"},
"for": set(["else"]), "for": {"else"},
}.get(self.keyword, []) }
return keyword in cases.get(self.keyword, set())
def __repr__(self): def __repr__(self):
return "ControlLine(%r, %r, %r, %r)" % ( return "ControlLine(%r, %r, %r, %r)" % (
@ -123,11 +124,10 @@ class ControlLine(Node):
class Text(Node): class Text(Node):
"""defines plain text in the template.""" """defines plain text in the template."""
def __init__(self, content, **kwargs): def __init__(self, content, **kwargs):
super(Text, self).__init__(**kwargs) super().__init__(**kwargs)
self.content = content self.content = content
def __repr__(self): def __repr__(self):
@ -135,7 +135,6 @@ class Text(Node):
class Code(Node): class Code(Node):
"""defines a Python code block, either inline or module level. """defines a Python code block, either inline or module level.
e.g.:: e.g.::
@ -153,7 +152,7 @@ class Code(Node):
""" """
def __init__(self, text, ismodule, **kwargs): def __init__(self, text, ismodule, **kwargs):
super(Code, self).__init__(**kwargs) super().__init__(**kwargs)
self.text = text self.text = text
self.ismodule = ismodule self.ismodule = ismodule
self.code = ast.PythonCode(text, **self.exception_kwargs) self.code = ast.PythonCode(text, **self.exception_kwargs)
@ -173,7 +172,6 @@ class Code(Node):
class Comment(Node): class Comment(Node):
"""defines a comment line. """defines a comment line.
# this is a comment # this is a comment
@ -181,7 +179,7 @@ class Comment(Node):
""" """
def __init__(self, text, **kwargs): def __init__(self, text, **kwargs):
super(Comment, self).__init__(**kwargs) super().__init__(**kwargs)
self.text = text self.text = text
def __repr__(self): def __repr__(self):
@ -189,7 +187,6 @@ class Comment(Node):
class Expression(Node): class Expression(Node):
"""defines an inline expression. """defines an inline expression.
${x+y} ${x+y}
@ -197,7 +194,7 @@ class Expression(Node):
""" """
def __init__(self, text, escapes, **kwargs): def __init__(self, text, escapes, **kwargs):
super(Expression, self).__init__(**kwargs) super().__init__(**kwargs)
self.text = text self.text = text
self.escapes = escapes self.escapes = escapes
self.escapes_code = ast.ArgumentList(escapes, **self.exception_kwargs) self.escapes_code = ast.ArgumentList(escapes, **self.exception_kwargs)
@ -210,7 +207,7 @@ class Expression(Node):
# TODO: make the "filter" shortcut list configurable at parse/gen time # TODO: make the "filter" shortcut list configurable at parse/gen time
return self.code.undeclared_identifiers.union( return self.code.undeclared_identifiers.union(
self.escapes_code.undeclared_identifiers.difference( self.escapes_code.undeclared_identifiers.difference(
set(filters.DEFAULT_ESCAPES.keys()) filters.DEFAULT_ESCAPES
) )
).difference(self.code.declared_identifiers) ).difference(self.code.declared_identifiers)
@ -223,7 +220,6 @@ class Expression(Node):
class _TagMeta(type): class _TagMeta(type):
"""metaclass to allow Tag to produce a subclass according to """metaclass to allow Tag to produce a subclass according to
its keyword""" its keyword"""
@ -232,7 +228,7 @@ class _TagMeta(type):
def __init__(cls, clsname, bases, dict_): def __init__(cls, clsname, bases, dict_):
if getattr(cls, "__keyword__", None) is not None: if getattr(cls, "__keyword__", None) is not None:
cls._classmap[cls.__keyword__] = cls cls._classmap[cls.__keyword__] = cls
super(_TagMeta, cls).__init__(clsname, bases, dict_) super().__init__(clsname, bases, dict_)
def __call__(cls, keyword, attributes, **kwargs): def __call__(cls, keyword, attributes, **kwargs):
if ":" in keyword: if ":" in keyword:
@ -254,7 +250,7 @@ class _TagMeta(type):
return type.__call__(cls, keyword, attributes, **kwargs) return type.__call__(cls, keyword, attributes, **kwargs)
class Tag(compat.with_metaclass(_TagMeta, Node)): class Tag(Node, metaclass=_TagMeta):
"""abstract base class for tags. """abstract base class for tags.
e.g.:: e.g.::
@ -276,7 +272,7 @@ class Tag(compat.with_metaclass(_TagMeta, Node)):
expressions, expressions,
nonexpressions, nonexpressions,
required, required,
**kwargs **kwargs,
): ):
r"""construct a new Tag instance. r"""construct a new Tag instance.
@ -297,17 +293,20 @@ class Tag(compat.with_metaclass(_TagMeta, Node)):
other arguments passed to the Node superclass (lineno, pos) other arguments passed to the Node superclass (lineno, pos)
""" """
super(Tag, self).__init__(**kwargs) super().__init__(**kwargs)
self.keyword = keyword self.keyword = keyword
self.attributes = attributes self.attributes = attributes
self._parse_attributes(expressions, nonexpressions) self._parse_attributes(expressions, nonexpressions)
missing = [r for r in required if r not in self.parsed_attributes] missing = [r for r in required if r not in self.parsed_attributes]
if len(missing): if len(missing):
raise exceptions.CompileException( raise exceptions.CompileException(
(
"Missing attribute(s): %s" "Missing attribute(s): %s"
% ",".join([repr(m) for m in missing]), % ",".join(repr(m) for m in missing)
**self.exception_kwargs ),
**self.exception_kwargs,
) )
self.parent = None self.parent = None
self.nodes = [] self.nodes = []
@ -339,23 +338,22 @@ class Tag(compat.with_metaclass(_TagMeta, Node)):
code.undeclared_identifiers code.undeclared_identifiers
) )
expr.append("(%s)" % m.group(1)) expr.append("(%s)" % m.group(1))
else: elif x:
if x:
expr.append(repr(x)) expr.append(repr(x))
self.parsed_attributes[key] = " + ".join(expr) or repr("") self.parsed_attributes[key] = " + ".join(expr) or repr("")
elif key in nonexpressions: elif key in nonexpressions:
if re.search(r"\${.+?}", self.attributes[key]): if re.search(r"\${.+?}", self.attributes[key]):
raise exceptions.CompileException( raise exceptions.CompileException(
"Attibute '%s' in tag '%s' does not allow embedded " "Attribute '%s' in tag '%s' does not allow embedded "
"expressions" % (key, self.keyword), "expressions" % (key, self.keyword),
**self.exception_kwargs **self.exception_kwargs,
) )
self.parsed_attributes[key] = repr(self.attributes[key]) self.parsed_attributes[key] = repr(self.attributes[key])
else: else:
raise exceptions.CompileException( raise exceptions.CompileException(
"Invalid attribute for tag '%s': '%s'" "Invalid attribute for tag '%s': '%s'"
% (self.keyword, key), % (self.keyword, key),
**self.exception_kwargs **self.exception_kwargs,
) )
self.expression_undeclared_identifiers = undeclared_identifiers self.expression_undeclared_identifiers = undeclared_identifiers
@ -379,13 +377,13 @@ class IncludeTag(Tag):
__keyword__ = "include" __keyword__ = "include"
def __init__(self, keyword, attributes, **kwargs): def __init__(self, keyword, attributes, **kwargs):
super(IncludeTag, self).__init__( super().__init__(
keyword, keyword,
attributes, attributes,
("file", "import", "args"), ("file", "import", "args"),
(), (),
("file",), ("file",),
**kwargs **kwargs,
) )
self.page_args = ast.PythonCode( self.page_args = ast.PythonCode(
"__DUMMY(%s)" % attributes.get("args", ""), **self.exception_kwargs "__DUMMY(%s)" % attributes.get("args", ""), **self.exception_kwargs
@ -396,24 +394,22 @@ class IncludeTag(Tag):
def undeclared_identifiers(self): def undeclared_identifiers(self):
identifiers = self.page_args.undeclared_identifiers.difference( identifiers = self.page_args.undeclared_identifiers.difference(
set(["__DUMMY"]) {"__DUMMY"}
).difference(self.page_args.declared_identifiers) ).difference(self.page_args.declared_identifiers)
return identifiers.union( return identifiers.union(super().undeclared_identifiers())
super(IncludeTag, self).undeclared_identifiers()
)
class NamespaceTag(Tag): class NamespaceTag(Tag):
__keyword__ = "namespace" __keyword__ = "namespace"
def __init__(self, keyword, attributes, **kwargs): def __init__(self, keyword, attributes, **kwargs):
super(NamespaceTag, self).__init__( super().__init__(
keyword, keyword,
attributes, attributes,
("file",), ("file",),
("name", "inheritable", "import", "module"), ("name", "inheritable", "import", "module"),
(), (),
**kwargs **kwargs,
) )
self.name = attributes.get("name", "__anon_%s" % hex(abs(id(self)))) self.name = attributes.get("name", "__anon_%s" % hex(abs(id(self))))
@ -421,12 +417,12 @@ class NamespaceTag(Tag):
raise exceptions.CompileException( raise exceptions.CompileException(
"'name' and/or 'import' attributes are required " "'name' and/or 'import' attributes are required "
"for <%namespace>", "for <%namespace>",
**self.exception_kwargs **self.exception_kwargs,
) )
if "file" in attributes and "module" in attributes: if "file" in attributes and "module" in attributes:
raise exceptions.CompileException( raise exceptions.CompileException(
"<%namespace> may only have one of 'file' or 'module'", "<%namespace> may only have one of 'file' or 'module'",
**self.exception_kwargs **self.exception_kwargs,
) )
def declared_identifiers(self): def declared_identifiers(self):
@ -437,9 +433,7 @@ class TextTag(Tag):
__keyword__ = "text" __keyword__ = "text"
def __init__(self, keyword, attributes, **kwargs): def __init__(self, keyword, attributes, **kwargs):
super(TextTag, self).__init__( super().__init__(keyword, attributes, (), ("filter"), (), **kwargs)
keyword, attributes, (), ("filter"), (), **kwargs
)
self.filter_args = ast.ArgumentList( self.filter_args = ast.ArgumentList(
attributes.get("filter", ""), **self.exception_kwargs attributes.get("filter", ""), **self.exception_kwargs
) )
@ -458,13 +452,13 @@ class DefTag(Tag):
c for c in attributes if c.startswith("cache_") c for c in attributes if c.startswith("cache_")
] ]
super(DefTag, self).__init__( super().__init__(
keyword, keyword,
attributes, attributes,
expressions, expressions,
("name", "filter", "decorator"), ("name", "filter", "decorator"),
("name",), ("name",),
**kwargs **kwargs,
) )
name = attributes["name"] name = attributes["name"]
if re.match(r"^[\w_]+$", name): if re.match(r"^[\w_]+$", name):
@ -521,19 +515,19 @@ class BlockTag(Tag):
c for c in attributes if c.startswith("cache_") c for c in attributes if c.startswith("cache_")
] ]
super(BlockTag, self).__init__( super().__init__(
keyword, keyword,
attributes, attributes,
expressions, expressions,
("name", "filter", "decorator"), ("name", "filter", "decorator"),
(), (),
**kwargs **kwargs,
) )
name = attributes.get("name") name = attributes.get("name")
if name and not re.match(r"^[\w_]+$", name): if name and not re.match(r"^[\w_]+$", name):
raise exceptions.CompileException( raise exceptions.CompileException(
"%block may not specify an argument signature", "%block may not specify an argument signature",
**self.exception_kwargs **self.exception_kwargs,
) )
if not name and attributes.get("args", None): if not name and attributes.get("args", None):
raise exceptions.CompileException( raise exceptions.CompileException(
@ -577,7 +571,7 @@ class CallTag(Tag):
__keyword__ = "call" __keyword__ = "call"
def __init__(self, keyword, attributes, **kwargs): def __init__(self, keyword, attributes, **kwargs):
super(CallTag, self).__init__( super().__init__(
keyword, attributes, ("args"), ("expr",), ("expr",), **kwargs keyword, attributes, ("args"), ("expr",), ("expr",), **kwargs
) )
self.expression = attributes["expr"] self.expression = attributes["expr"]
@ -597,26 +591,25 @@ class CallTag(Tag):
class CallNamespaceTag(Tag): class CallNamespaceTag(Tag):
def __init__(self, namespace, defname, attributes, **kwargs): def __init__(self, namespace, defname, attributes, **kwargs):
super(CallNamespaceTag, self).__init__( super().__init__(
namespace + ":" + defname, namespace + ":" + defname,
attributes, attributes,
tuple(attributes.keys()) + ("args",), tuple(attributes.keys()) + ("args",),
(), (),
(), (),
**kwargs **kwargs,
) )
self.expression = "%s.%s(%s)" % ( self.expression = "%s.%s(%s)" % (
namespace, namespace,
defname, defname,
",".join( ",".join(
[
"%s=%s" % (k, v) "%s=%s" % (k, v)
for k, v in self.parsed_attributes.items() for k, v in self.parsed_attributes.items()
if k != "args" if k != "args"
]
), ),
) )
self.code = ast.PythonCode(self.expression, **self.exception_kwargs) self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
self.body_decl = ast.FunctionArgs( self.body_decl = ast.FunctionArgs(
attributes.get("args", ""), **self.exception_kwargs attributes.get("args", ""), **self.exception_kwargs
@ -635,7 +628,7 @@ class InheritTag(Tag):
__keyword__ = "inherit" __keyword__ = "inherit"
def __init__(self, keyword, attributes, **kwargs): def __init__(self, keyword, attributes, **kwargs):
super(InheritTag, self).__init__( super().__init__(
keyword, attributes, ("file",), (), ("file",), **kwargs keyword, attributes, ("file",), (), ("file",), **kwargs
) )
@ -651,9 +644,7 @@ class PageTag(Tag):
"enable_loop", "enable_loop",
] + [c for c in attributes if c.startswith("cache_")] ] + [c for c in attributes if c.startswith("cache_")]
super(PageTag, self).__init__( super().__init__(keyword, attributes, expressions, (), (), **kwargs)
keyword, attributes, expressions, (), (), **kwargs
)
self.body_decl = ast.FunctionArgs( self.body_decl = ast.FunctionArgs(
attributes.get("args", ""), **self.exception_kwargs attributes.get("args", ""), **self.exception_kwargs
) )

View file

@ -1,5 +1,5 @@
# mako/pygen.py # mako/pygen.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -11,7 +11,7 @@ import re
from mako import exceptions from mako import exceptions
class PythonPrinter(object): class PythonPrinter:
def __init__(self, stream): def __init__(self, stream):
# indentation counter # indentation counter
self.indent = 0 self.indent = 0
@ -96,17 +96,18 @@ class PythonPrinter(object):
is_comment = line and len(line) and line[0] == "#" is_comment = line and len(line) and line[0] == "#"
# see if this line should decrease the indentation level # see if this line should decrease the indentation level
if not is_comment and (not hastext or self._is_unindentor(line)): if (
not is_comment
if self.indent > 0: and (not hastext or self._is_unindentor(line))
and self.indent > 0
):
self.indent -= 1 self.indent -= 1
# if the indent_detail stack is empty, the user # if the indent_detail stack is empty, the user
# probably put extra closures - the resulting # probably put extra closures - the resulting
# module wont compile. # module wont compile.
if len(self.indent_detail) == 0: if len(self.indent_detail) == 0:
raise exceptions.SyntaxException( # TODO: no coverage here
"Too many whitespace closures" raise exceptions.MakoException("Too many whitespace closures")
)
self.indent_detail.pop() self.indent_detail.pop()
if line is None: if line is None:
@ -167,13 +168,10 @@ class PythonPrinter(object):
# if the current line doesnt have one of the "unindentor" keywords, # if the current line doesnt have one of the "unindentor" keywords,
# return False # return False
match = re.match(r"^\s*(else|elif|except|finally).*\:", line) match = re.match(r"^\s*(else|elif|except|finally).*\:", line)
if not match: # if True, whitespace matches up, we have a compound indentor,
return False
# whitespace matches up, we have a compound indentor,
# and this line has an unindentor, this # and this line has an unindentor, this
# is probably good enough # is probably good enough
return True return bool(match)
# should we decide that its not good enough, heres # should we decide that its not good enough, heres
# more stuff to check. # more stuff to check.
@ -218,11 +216,7 @@ class PythonPrinter(object):
current_state = self.backslashed or self.triplequoted current_state = self.backslashed or self.triplequoted
if re.search(r"\\$", line): self.backslashed = bool(re.search(r"\\$", line))
self.backslashed = True
else:
self.backslashed = False
triples = len(re.findall(r"\"\"\"|\'\'\'", line)) triples = len(re.findall(r"\"\"\"|\'\'\'", line))
if triples == 1 or triples % 2 != 0: if triples == 1 or triples % 2 != 0:
self.triplequoted = not self.triplequoted self.triplequoted = not self.triplequoted

View file

@ -1,5 +1,5 @@
# mako/pyparser.py # mako/pyparser.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -18,22 +18,13 @@ from mako import _ast_util
from mako import compat from mako import compat
from mako import exceptions from mako import exceptions
from mako import util from mako import util
from mako.compat import arg_stringname
if compat.py3k: # words that cannot be assigned to (notably
# words that cannot be assigned to (notably # smaller than the total keys in __builtins__)
# smaller than the total keys in __builtins__) reserved = {"True", "False", "None", "print"}
reserved = set(["True", "False", "None", "print"])
# the "id" attribute on a function node # the "id" attribute on a function node
arg_id = operator.attrgetter("arg") arg_id = operator.attrgetter("arg")
else:
# words that cannot be assigned to (notably
# smaller than the total keys in __builtins__)
reserved = set(["True", "False", "None"])
# the "id" attribute on a function node
arg_id = operator.attrgetter("id")
util.restore__ast(_ast) util.restore__ast(_ast)
@ -43,7 +34,7 @@ def parse(code, mode="exec", **exception_kwargs):
try: try:
return _ast_util.parse(code, "<unknown>", mode) return _ast_util.parse(code, "<unknown>", mode)
except Exception: except Exception as e:
raise exceptions.SyntaxException( raise exceptions.SyntaxException(
"(%s) %s (%r)" "(%s) %s (%r)"
% ( % (
@ -51,8 +42,8 @@ def parse(code, mode="exec", **exception_kwargs):
compat.exception_as(), compat.exception_as(),
code[0:50], code[0:50],
), ),
**exception_kwargs **exception_kwargs,
) ) from e
class FindIdentifiers(_ast_util.NodeVisitor): class FindIdentifiers(_ast_util.NodeVisitor):
@ -85,11 +76,6 @@ class FindIdentifiers(_ast_util.NodeVisitor):
self.visit(n) self.visit(n)
self.in_assign_targets = in_a self.in_assign_targets = in_a
if compat.py3k:
# ExceptHandler is in Python 2, but this block only works in
# Python 3 (and is required there)
def visit_ExceptHandler(self, node): def visit_ExceptHandler(self, node):
if node.name is not None: if node.name is not None:
self._add_declared(node.name) self._add_declared(node.name)
@ -108,8 +94,7 @@ class FindIdentifiers(_ast_util.NodeVisitor):
def _expand_tuples(self, args): def _expand_tuples(self, args):
for arg in args: for arg in args:
if isinstance(arg, _ast.Tuple): if isinstance(arg, _ast.Tuple):
for n in arg.elts: yield from arg.elts
yield n
else: else:
yield arg yield arg
@ -170,15 +155,15 @@ class FindIdentifiers(_ast_util.NodeVisitor):
for name in node.names: for name in node.names:
if name.asname is not None: if name.asname is not None:
self._add_declared(name.asname) self._add_declared(name.asname)
else: elif name.name == "*":
if name.name == "*":
raise exceptions.CompileException( raise exceptions.CompileException(
"'import *' is not supported, since all identifier " "'import *' is not supported, since all identifier "
"names must be explicitly declared. Please use the " "names must be explicitly declared. Please use the "
"form 'from <modulename> import <name1>, <name2>, " "form 'from <modulename> import <name1>, <name2>, "
"...' instead.", "...' instead.",
**self.exception_kwargs **self.exception_kwargs,
) )
else:
self._add_declared(name.name) self._add_declared(name.name)
@ -213,27 +198,20 @@ class ParseFunc(_ast_util.NodeVisitor):
argnames = [arg_id(arg) for arg in node.args.args] argnames = [arg_id(arg) for arg in node.args.args]
if node.args.vararg: if node.args.vararg:
argnames.append(arg_stringname(node.args.vararg)) argnames.append(node.args.vararg.arg)
if compat.py2k:
# kw-only args don't exist in Python 2
kwargnames = []
else:
kwargnames = [arg_id(arg) for arg in node.args.kwonlyargs] kwargnames = [arg_id(arg) for arg in node.args.kwonlyargs]
if node.args.kwarg: if node.args.kwarg:
kwargnames.append(arg_stringname(node.args.kwarg)) kwargnames.append(node.args.kwarg.arg)
self.listener.argnames = argnames self.listener.argnames = argnames
self.listener.defaults = node.args.defaults # ast self.listener.defaults = node.args.defaults # ast
self.listener.kwargnames = kwargnames self.listener.kwargnames = kwargnames
if compat.py2k:
self.listener.kwdefaults = []
else:
self.listener.kwdefaults = node.args.kw_defaults self.listener.kwdefaults = node.args.kw_defaults
self.listener.varargs = node.args.vararg self.listener.varargs = node.args.vararg
self.listener.kwargs = node.args.kwarg self.listener.kwargs = node.args.kwarg
class ExpressionGenerator(object): class ExpressionGenerator:
def __init__(self, astnode): def __init__(self, astnode):
self.generator = _ast_util.SourceGenerator(" " * 4) self.generator = _ast_util.SourceGenerator(" " * 4)
self.generator.visit(astnode) self.generator.visit(astnode)

View file

@ -7,16 +7,16 @@
"""provides runtime services for templates, including Context, """provides runtime services for templates, including Context,
Namespace, and various helper functions.""" Namespace, and various helper functions."""
import builtins
import functools import functools
import sys import sys
from mako import compat from mako import compat
from mako import exceptions from mako import exceptions
from mako import util from mako import util
from mako.compat import compat_builtins
class Context(object): class Context:
"""Provides runtime namespace, output buffer, and various """Provides runtime namespace, output buffer, and various
callstacks for templates. callstacks for templates.
@ -103,7 +103,7 @@ class Context(object):
if key in self._data: if key in self._data:
return self._data[key] return self._data[key]
else: else:
return compat_builtins.__dict__[key] return builtins.__dict__[key]
def _push_writer(self): def _push_writer(self):
"""push a capturing buffer onto this Context and return """push a capturing buffer onto this Context and return
@ -135,7 +135,7 @@ class Context(object):
def get(self, key, default=None): def get(self, key, default=None):
"""Return a value from this :class:`.Context`.""" """Return a value from this :class:`.Context`."""
return self._data.get(key, compat_builtins.__dict__.get(key, default)) return self._data.get(key, builtins.__dict__.get(key, default))
def write(self, string): def write(self, string):
"""Write a string to this :class:`.Context` object's """Write a string to this :class:`.Context` object's
@ -216,7 +216,7 @@ class CallerStack(list):
self.nextcaller = self.pop() self.nextcaller = self.pop()
class Undefined(object): class Undefined:
"""Represents an undefined value in a template. """Represents an undefined value in a template.
@ -240,7 +240,7 @@ UNDEFINED = Undefined()
STOP_RENDERING = "" STOP_RENDERING = ""
class LoopStack(object): class LoopStack:
"""a stack for LoopContexts that implements the context manager protocol """a stack for LoopContexts that implements the context manager protocol
to automatically pop off the top of the stack on context exit to automatically pop off the top of the stack on context exit
@ -280,7 +280,7 @@ class LoopStack(object):
return iter(self._top) return iter(self._top)
class LoopContext(object): class LoopContext:
"""A magic loop variable. """A magic loop variable.
Automatically accessible in any ``% for`` block. Automatically accessible in any ``% for`` block.
@ -339,14 +339,13 @@ class LoopContext(object):
return bool(self.index % 2) return bool(self.index % 2)
def cycle(self, *values): def cycle(self, *values):
"""Cycle through values as the loop progresses. """Cycle through values as the loop progresses."""
"""
if not values: if not values:
raise ValueError("You must provide values to cycle through") raise ValueError("You must provide values to cycle through")
return values[self.index % len(values)] return values[self.index % len(values)]
class _NSAttr(object): class _NSAttr:
def __init__(self, parent): def __init__(self, parent):
self.__parent = parent self.__parent = parent
@ -360,7 +359,7 @@ class _NSAttr(object):
raise AttributeError(key) raise AttributeError(key)
class Namespace(object): class Namespace:
"""Provides access to collections of rendering methods, which """Provides access to collections of rendering methods, which
can be local, from other templates, or from imported modules. can be local, from other templates, or from imported modules.
@ -390,7 +389,7 @@ class Namespace(object):
self.context = context self.context = context
self.inherits = inherits self.inherits = inherits
if callables is not None: if callables is not None:
self.callables = dict([(c.__name__, c) for c in callables]) self.callables = {c.__name__: c for c in callables}
callables = () callables = ()
@ -482,7 +481,6 @@ class Namespace(object):
key = (self, uri) key = (self, uri)
if key in self.context.namespaces: if key in self.context.namespaces:
return self.context.namespaces[key] return self.context.namespaces[key]
else:
ns = TemplateNamespace( ns = TemplateNamespace(
uri, uri,
self.context._copy(), self.context._copy(),
@ -574,7 +572,7 @@ class TemplateNamespace(Namespace):
self.context = context self.context = context
self.inherits = inherits self.inherits = inherits
if callables is not None: if callables is not None:
self.callables = dict([(c.__name__, c) for c in callables]) self.callables = {c.__name__: c for c in callables}
if templateuri is not None: if templateuri is not None:
self.template = _lookup_template(context, templateuri, calling_uri) self.template = _lookup_template(context, templateuri, calling_uri)
@ -666,7 +664,7 @@ class ModuleNamespace(Namespace):
self.context = context self.context = context
self.inherits = inherits self.inherits = inherits
if callables is not None: if callables is not None:
self.callables = dict([(c.__name__, c) for c in callables]) self.callables = {c.__name__: c for c in callables}
mod = __import__(module) mod = __import__(module)
for token in module.split(".")[1:]: for token in module.split(".")[1:]:
@ -790,7 +788,7 @@ def _include_file(context, uri, calling_uri, **kwargs):
except Exception: except Exception:
result = template.include_error_handler(ctx, compat.exception_as()) result = template.include_error_handler(ctx, compat.exception_as())
if not result: if not result:
compat.reraise(*sys.exc_info()) raise
else: else:
callable_(ctx, **kwargs) callable_(ctx, **kwargs)
@ -837,8 +835,10 @@ def _lookup_template(context, uri, relativeto):
uri = lookup.adjust_uri(uri, relativeto) uri = lookup.adjust_uri(uri, relativeto)
try: try:
return lookup.get_template(uri) return lookup.get_template(uri)
except exceptions.TopLevelLookupException: except exceptions.TopLevelLookupException as e:
raise exceptions.TemplateLookupException(str(compat.exception_as())) raise exceptions.TemplateLookupException(
str(compat.exception_as())
) from e
def _populate_self_namespace(context, template, self_ns=None): def _populate_self_namespace(context, template, self_ns=None):
@ -862,14 +862,10 @@ def _render(template, callable_, args, data, as_unicode=False):
output of the given template and template callable.""" output of the given template and template callable."""
if as_unicode: if as_unicode:
buf = util.FastEncodingBuffer(as_unicode=True) buf = util.FastEncodingBuffer()
elif template.bytestring_passthrough:
buf = compat.StringIO()
else: else:
buf = util.FastEncodingBuffer( buf = util.FastEncodingBuffer(
as_unicode=as_unicode, encoding=template.output_encoding, errors=template.encoding_errors
encoding=template.output_encoding,
errors=template.encoding_errors,
) )
context = Context(buf, **data) context = Context(buf, **data)
context._outputting_as_unicode = as_unicode context._outputting_as_unicode = as_unicode
@ -880,7 +876,7 @@ def _render(template, callable_, args, data, as_unicode=False):
callable_, callable_,
context, context,
*args, *args,
**_kwargs_for_callable(callable_, data) **_kwargs_for_callable(callable_, data),
) )
return context._pop_buffer().getvalue() return context._pop_buffer().getvalue()
@ -951,13 +947,15 @@ def _render_error(template, context, error):
if template.error_handler: if template.error_handler:
result = template.error_handler(context, error) result = template.error_handler(context, error)
if not result: if not result:
compat.reraise(*sys.exc_info()) tp, value, tb = sys.exc_info()
if value and tb:
raise value.with_traceback(tb)
else:
raise error
else: else:
error_template = exceptions.html_error_template() error_template = exceptions.html_error_template()
if context._outputting_as_unicode: if context._outputting_as_unicode:
context._buffer_stack[:] = [ context._buffer_stack[:] = [util.FastEncodingBuffer()]
util.FastEncodingBuffer(as_unicode=True)
]
else: else:
context._buffer_stack[:] = [ context._buffer_stack[:] = [
util.FastEncodingBuffer( util.FastEncodingBuffer(

View file

@ -1,5 +1,5 @@
# mako/template.py # mako/template.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
@ -25,7 +25,7 @@ from mako import util
from mako.lexer import Lexer from mako.lexer import Lexer
class Template(object): class Template:
r"""Represents a compiled template. r"""Represents a compiled template.
@ -53,17 +53,6 @@ class Template(object):
of return-valued ``%def``\ s "opt out" of that filtering via of return-valued ``%def``\ s "opt out" of that filtering via
passing special attributes or objects. passing special attributes or objects.
:param bytestring_passthrough: When ``True``, and ``output_encoding`` is
set to ``None``, and :meth:`.Template.render` is used to render,
the `StringIO` or `cStringIO` buffer will be used instead of the
default "fast" buffer. This allows raw bytestrings in the
output stream, such as in expressions, to pass straight
through to the buffer. This flag is forced
to ``True`` if ``disable_unicode`` is also configured.
.. versionadded:: 0.4
Added to provide the same behavior as that of the previous series.
:param cache_args: Dictionary of cache configuration arguments that :param cache_args: Dictionary of cache configuration arguments that
will be passed to the :class:`.CacheImpl`. See :ref:`caching_toplevel`. will be passed to the :class:`.CacheImpl`. See :ref:`caching_toplevel`.
@ -94,9 +83,6 @@ class Template(object):
:param default_filters: List of string filter names that will :param default_filters: List of string filter names that will
be applied to all expressions. See :ref:`filtering_default_filters`. be applied to all expressions. See :ref:`filtering_default_filters`.
:param disable_unicode: Disables all awareness of Python Unicode
objects. See :ref:`unicode_disabled`.
:param enable_loop: When ``True``, enable the ``loop`` context variable. :param enable_loop: When ``True``, enable the ``loop`` context variable.
This can be set to ``False`` to support templates that may This can be set to ``False`` to support templates that may
be making usage of the name "``loop``". Individual templates can be making usage of the name "``loop``". Individual templates can
@ -255,9 +241,7 @@ class Template(object):
cache_url=None, cache_url=None,
module_filename=None, module_filename=None,
input_encoding=None, input_encoding=None,
disable_unicode=False,
module_writer=None, module_writer=None,
bytestring_passthrough=False,
default_filters=None, default_filters=None,
buffer_filters=(), buffer_filters=(),
strict_undefined=False, strict_undefined=False,
@ -294,26 +278,12 @@ class Template(object):
self.input_encoding = input_encoding self.input_encoding = input_encoding
self.output_encoding = output_encoding self.output_encoding = output_encoding
self.encoding_errors = encoding_errors self.encoding_errors = encoding_errors
self.disable_unicode = disable_unicode
self.bytestring_passthrough = bytestring_passthrough or disable_unicode
self.enable_loop = enable_loop self.enable_loop = enable_loop
self.strict_undefined = strict_undefined self.strict_undefined = strict_undefined
self.module_writer = module_writer self.module_writer = module_writer
if compat.py3k and disable_unicode:
raise exceptions.UnsupportedError(
"Mako for Python 3 does not " "support disabling Unicode"
)
elif output_encoding and disable_unicode:
raise exceptions.UnsupportedError(
"output_encoding must be set to "
"None when disable_unicode is used."
)
if default_filters is None: if default_filters is None:
if compat.py3k or self.disable_unicode:
self.default_filters = ["str"] self.default_filters = ["str"]
else:
self.default_filters = ["unicode"]
else: else:
self.default_filters = default_filters self.default_filters = default_filters
self.buffer_filters = buffer_filters self.buffer_filters = buffer_filters
@ -387,11 +357,7 @@ class Template(object):
): ):
self.cache_impl = cache_impl self.cache_impl = cache_impl
self.cache_enabled = cache_enabled self.cache_enabled = cache_enabled
if cache_args: self.cache_args = cache_args or {}
self.cache_args = cache_args
else:
self.cache_args = {}
# transfer deprecated cache_* args # transfer deprecated cache_* args
if cache_type: if cache_type:
self.cache_args["type"] = cache_type self.cache_args["type"] = cache_type
@ -463,7 +429,7 @@ class Template(object):
If the template specifies an output encoding, the string If the template specifies an output encoding, the string
will be encoded accordingly, else the output is raw (raw will be encoded accordingly, else the output is raw (raw
output uses `cStringIO` and can't handle multibyte output uses `StringIO` and can't handle multibyte
characters). A :class:`.Context` object is created corresponding characters). A :class:`.Context` object is created corresponding
to the given data. Arguments that are explicitly declared to the given data. Arguments that are explicitly declared
by this template's internal rendering method are also by this template's internal rendering method are also
@ -541,8 +507,6 @@ class ModuleTemplate(Template):
template_source=None, template_source=None,
output_encoding=None, output_encoding=None,
encoding_errors="strict", encoding_errors="strict",
disable_unicode=False,
bytestring_passthrough=False,
format_exceptions=False, format_exceptions=False,
error_handler=None, error_handler=None,
lookup=None, lookup=None,
@ -559,20 +523,8 @@ class ModuleTemplate(Template):
self.input_encoding = module._source_encoding self.input_encoding = module._source_encoding
self.output_encoding = output_encoding self.output_encoding = output_encoding
self.encoding_errors = encoding_errors self.encoding_errors = encoding_errors
self.disable_unicode = disable_unicode
self.bytestring_passthrough = bytestring_passthrough or disable_unicode
self.enable_loop = module._enable_loop self.enable_loop = module._enable_loop
if compat.py3k and disable_unicode:
raise exceptions.UnsupportedError(
"Mako for Python 3 does not " "support disabling Unicode"
)
elif output_encoding and disable_unicode:
raise exceptions.UnsupportedError(
"output_encoding must be set to "
"None when disable_unicode is used."
)
self.module = module self.module = module
self.filename = template_filename self.filename = template_filename
ModuleInfo( ModuleInfo(
@ -616,13 +568,12 @@ class DefTemplate(Template):
self.include_error_handler = parent.include_error_handler self.include_error_handler = parent.include_error_handler
self.enable_loop = parent.enable_loop self.enable_loop = parent.enable_loop
self.lookup = parent.lookup self.lookup = parent.lookup
self.bytestring_passthrough = parent.bytestring_passthrough
def get_def(self, name): def get_def(self, name):
return self.parent.get_def(name) return self.parent.get_def(name)
class ModuleInfo(object): class ModuleInfo:
"""Stores information about a module currently loaded into """Stores information about a module currently loaded into
memory, provides reverse lookups of template source, module memory, provides reverse lookups of template source, module
@ -658,9 +609,9 @@ class ModuleInfo(object):
r"__M_BEGIN_METADATA(.+?)__M_END_METADATA", module_source, re.S r"__M_BEGIN_METADATA(.+?)__M_END_METADATA", module_source, re.S
).group(1) ).group(1)
source_map = json.loads(source_map) source_map = json.loads(source_map)
source_map["line_map"] = dict( source_map["line_map"] = {
(int(k), int(v)) for k, v in source_map["line_map"].items() int(k): int(v) for k, v in source_map["line_map"].items()
) }
if full_line_map: if full_line_map:
f_line_map = source_map["full_line_map"] = [] f_line_map = source_map["full_line_map"] = []
line_map = source_map["line_map"] line_map = source_map["line_map"]
@ -681,28 +632,25 @@ class ModuleInfo(object):
@property @property
def source(self): def source(self):
if self.template_source is not None: if self.template_source is None:
if self.module._source_encoding and not isinstance(
self.template_source, compat.text_type
):
return self.template_source.decode(
self.module._source_encoding
)
else:
return self.template_source
else:
data = util.read_file(self.template_filename) data = util.read_file(self.template_filename)
if self.module._source_encoding: if self.module._source_encoding:
return data.decode(self.module._source_encoding) return data.decode(self.module._source_encoding)
else: else:
return data return data
elif self.module._source_encoding and not isinstance(
self.template_source, str
):
return self.template_source.decode(self.module._source_encoding)
else:
return self.template_source
def _compile(template, text, filename, generate_magic_comment): def _compile(template, text, filename, generate_magic_comment):
lexer = template.lexer_cls( lexer = template.lexer_cls(
text, text,
filename, filename,
disable_unicode=template.disable_unicode,
input_encoding=template.input_encoding, input_encoding=template.input_encoding,
preprocessor=template.preprocessor, preprocessor=template.preprocessor,
) )
@ -717,7 +665,6 @@ def _compile(template, text, filename, generate_magic_comment):
future_imports=template.future_imports, future_imports=template.future_imports,
source_encoding=lexer.encoding, source_encoding=lexer.encoding,
generate_magic_comment=generate_magic_comment, generate_magic_comment=generate_magic_comment,
disable_unicode=template.disable_unicode,
strict_undefined=template.strict_undefined, strict_undefined=template.strict_undefined,
enable_loop=template.enable_loop, enable_loop=template.enable_loop,
reserved_names=template.reserved_names, reserved_names=template.reserved_names,
@ -728,15 +675,10 @@ def _compile(template, text, filename, generate_magic_comment):
def _compile_text(template, text, filename): def _compile_text(template, text, filename):
identifier = template.module_id identifier = template.module_id
source, lexer = _compile( source, lexer = _compile(
template, template, text, filename, generate_magic_comment=False
text,
filename,
generate_magic_comment=template.disable_unicode,
) )
cid = identifier cid = identifier
if not compat.py3k and isinstance(cid, compat.text_type):
cid = cid.encode()
module = types.ModuleType(cid) module = types.ModuleType(cid)
code = compile(source, cid, "exec") code = compile(source, cid, "exec")
@ -750,7 +692,7 @@ def _compile_module_file(template, text, filename, outputpath, module_writer):
template, text, filename, generate_magic_comment=True template, text, filename, generate_magic_comment=True
) )
if isinstance(source, compat.text_type): if isinstance(source, str):
source = source.encode(lexer.encoding or "ascii") source = source.encode(lexer.encoding or "ascii")
if module_writer: if module_writer:
@ -767,10 +709,7 @@ def _compile_module_file(template, text, filename, outputpath, module_writer):
def _get_module_info_from_callable(callable_): def _get_module_info_from_callable(callable_):
if compat.py3k:
return _get_module_info(callable_.__globals__["__name__"]) return _get_module_info(callable_.__globals__["__name__"])
else:
return _get_module_info(callable_.func_globals["__name__"])
def _get_module_info(filename): def _get_module_info(filename):

View file

128
lib/mako/testing/_config.py Normal file
View file

@ -0,0 +1,128 @@
import configparser
import dataclasses
from dataclasses import dataclass
from pathlib import Path
from typing import Callable
from typing import ClassVar
from typing import Optional
from typing import Union
from .helpers import make_path
class ConfigError(BaseException):
pass
class MissingConfig(ConfigError):
pass
class MissingConfigSection(ConfigError):
pass
class MissingConfigItem(ConfigError):
pass
class ConfigValueTypeError(ConfigError):
pass
class _GetterDispatch:
def __init__(self, initialdata, default_getter: Callable):
self.default_getter = default_getter
self.data = initialdata
def get_fn_for_type(self, type_):
return self.data.get(type_, self.default_getter)
def get_typed_value(self, type_, name):
get_fn = self.get_fn_for_type(type_)
return get_fn(name)
def _parse_cfg_file(filespec: Union[Path, str]):
cfg = configparser.ConfigParser()
try:
filepath = make_path(filespec, check_exists=True)
except FileNotFoundError as e:
raise MissingConfig(f"No config file found at {filespec}") from e
else:
with open(filepath, encoding="utf-8") as f:
cfg.read_file(f)
return cfg
def _build_getter(cfg_obj, cfg_section, method, converter=None):
def caller(option, **kwargs):
try:
rv = getattr(cfg_obj, method)(cfg_section, option, **kwargs)
except configparser.NoSectionError as nse:
raise MissingConfigSection(
f"No config section named {cfg_section}"
) from nse
except configparser.NoOptionError as noe:
raise MissingConfigItem(f"No config item for {option}") from noe
except ValueError as ve:
# ConfigParser.getboolean, .getint, .getfloat raise ValueError
# on bad types
raise ConfigValueTypeError(
f"Wrong value type for {option}"
) from ve
else:
if converter:
try:
rv = converter(rv)
except Exception as e:
raise ConfigValueTypeError(
f"Wrong value type for {option}"
) from e
return rv
return caller
def _build_getter_dispatch(cfg_obj, cfg_section, converters=None):
converters = converters or {}
default_getter = _build_getter(cfg_obj, cfg_section, "get")
# support ConfigParser builtins
getters = {
int: _build_getter(cfg_obj, cfg_section, "getint"),
bool: _build_getter(cfg_obj, cfg_section, "getboolean"),
float: _build_getter(cfg_obj, cfg_section, "getfloat"),
str: default_getter,
}
# use ConfigParser.get and convert value
getters.update(
{
type_: _build_getter(
cfg_obj, cfg_section, "get", converter=converter_fn
)
for type_, converter_fn in converters.items()
}
)
return _GetterDispatch(getters, default_getter)
@dataclass
class ReadsCfg:
section_header: ClassVar[str]
converters: ClassVar[Optional[dict]] = None
@classmethod
def from_cfg_file(cls, filespec: Union[Path, str]):
cfg = _parse_cfg_file(filespec)
dispatch = _build_getter_dispatch(
cfg, cls.section_header, converters=cls.converters
)
kwargs = {
field.name: dispatch.get_typed_value(field.type, field.name)
for field in dataclasses.fields(cls)
}
return cls(**kwargs)

View file

@ -0,0 +1,167 @@
import contextlib
import re
import sys
def eq_(a, b, msg=None):
"""Assert a == b, with repr messaging on failure."""
assert a == b, msg or "%r != %r" % (a, b)
def ne_(a, b, msg=None):
"""Assert a != b, with repr messaging on failure."""
assert a != b, msg or "%r == %r" % (a, b)
def in_(a, b, msg=None):
"""Assert a in b, with repr messaging on failure."""
assert a in b, msg or "%r not in %r" % (a, b)
def not_in(a, b, msg=None):
"""Assert a in not b, with repr messaging on failure."""
assert a not in b, msg or "%r is in %r" % (a, b)
def _assert_proper_exception_context(exception):
"""assert that any exception we're catching does not have a __context__
without a __cause__, and that __suppress_context__ is never set.
Python 3 will report nested as exceptions as "during the handling of
error X, error Y occurred". That's not what we want to do. We want
these exceptions in a cause chain.
"""
if (
exception.__context__ is not exception.__cause__
and not exception.__suppress_context__
):
assert False, (
"Exception %r was correctly raised but did not set a cause, "
"within context %r as its cause."
% (exception, exception.__context__)
)
def _assert_proper_cause_cls(exception, cause_cls):
"""assert that any exception we're catching does not have a __context__
without a __cause__, and that __suppress_context__ is never set.
Python 3 will report nested as exceptions as "during the handling of
error X, error Y occurred". That's not what we want to do. We want
these exceptions in a cause chain.
"""
assert isinstance(exception.__cause__, cause_cls), (
"Exception %r was correctly raised but has cause %r, which does not "
"have the expected cause type %r."
% (exception, exception.__cause__, cause_cls)
)
def assert_raises(except_cls, callable_, *args, **kw):
return _assert_raises(except_cls, callable_, args, kw)
def assert_raises_with_proper_context(except_cls, callable_, *args, **kw):
return _assert_raises(except_cls, callable_, args, kw, check_context=True)
def assert_raises_with_given_cause(
except_cls, cause_cls, callable_, *args, **kw
):
return _assert_raises(except_cls, callable_, args, kw, cause_cls=cause_cls)
def assert_raises_message(except_cls, msg, callable_, *args, **kwargs):
return _assert_raises(except_cls, callable_, args, kwargs, msg=msg)
def assert_raises_message_with_proper_context(
except_cls, msg, callable_, *args, **kwargs
):
return _assert_raises(
except_cls, callable_, args, kwargs, msg=msg, check_context=True
)
def assert_raises_message_with_given_cause(
except_cls, msg, cause_cls, callable_, *args, **kwargs
):
return _assert_raises(
except_cls, callable_, args, kwargs, msg=msg, cause_cls=cause_cls
)
def _assert_raises(
except_cls,
callable_,
args,
kwargs,
msg=None,
check_context=False,
cause_cls=None,
):
with _expect_raises(except_cls, msg, check_context, cause_cls) as ec:
callable_(*args, **kwargs)
return ec.error
class _ErrorContainer:
error = None
@contextlib.contextmanager
def _expect_raises(except_cls, msg=None, check_context=False, cause_cls=None):
ec = _ErrorContainer()
if check_context:
are_we_already_in_a_traceback = sys.exc_info()[0]
try:
yield ec
success = False
except except_cls as err:
ec.error = err
success = True
if msg is not None:
# I'm often pdbing here, and "err" above isn't
# in scope, so assign the string explicitly
error_as_string = str(err)
assert re.search(msg, error_as_string, re.UNICODE), "%r !~ %s" % (
msg,
error_as_string,
)
if cause_cls is not None:
_assert_proper_cause_cls(err, cause_cls)
if check_context and not are_we_already_in_a_traceback:
_assert_proper_exception_context(err)
print(str(err).encode("utf-8"))
# it's generally a good idea to not carry traceback objects outside
# of the except: block, but in this case especially we seem to have
# hit some bug in either python 3.10.0b2 or greenlet or both which
# this seems to fix:
# https://github.com/python-greenlet/greenlet/issues/242
del ec
# assert outside the block so it works for AssertionError too !
assert success, "Callable did not raise an exception"
def expect_raises(except_cls, check_context=False):
return _expect_raises(except_cls, check_context=check_context)
def expect_raises_message(except_cls, msg, check_context=False):
return _expect_raises(except_cls, msg=msg, check_context=check_context)
def expect_raises_with_proper_context(except_cls, check_context=True):
return _expect_raises(except_cls, check_context=check_context)
def expect_raises_message_with_proper_context(
except_cls, msg, check_context=True
):
return _expect_raises(except_cls, msg=msg, check_context=check_context)

View file

@ -0,0 +1,17 @@
from dataclasses import dataclass
from pathlib import Path
from ._config import ReadsCfg
from .helpers import make_path
@dataclass
class Config(ReadsCfg):
module_base: Path
template_base: Path
section_header = "mako_testing"
converters = {Path: make_path}
config = Config.from_cfg_file("./setup.cfg")

View file

@ -0,0 +1,80 @@
import pytest
from mako.ext.beaker_cache import has_beaker
from mako.util import update_wrapper
try:
import babel.messages.extract as babel
except ImportError:
babel = None
try:
import lingua
except ImportError:
lingua = None
try:
import dogpile.cache # noqa
except ImportError:
has_dogpile_cache = False
else:
has_dogpile_cache = True
requires_beaker = pytest.mark.skipif(
not has_beaker, reason="Beaker is required for these tests."
)
requires_babel = pytest.mark.skipif(
babel is None, reason="babel not installed: skipping babelplugin test"
)
requires_lingua = pytest.mark.skipif(
lingua is None, reason="lingua not installed: skipping linguaplugin test"
)
requires_dogpile_cache = pytest.mark.skipif(
not has_dogpile_cache,
reason="dogpile.cache is required to run these tests",
)
def _pygments_version():
try:
import pygments
version = pygments.__version__
except:
version = "0"
return version
requires_pygments_14 = pytest.mark.skipif(
_pygments_version() < "1.4", reason="Requires pygments 1.4 or greater"
)
# def requires_pygments_14(fn):
# return skip_if(
# lambda: version < "1.4", "Requires pygments 1.4 or greater"
# )(fn)
def requires_no_pygments_exceptions(fn):
def go(*arg, **kw):
from mako import exceptions
exceptions._install_fallback()
try:
return fn(*arg, **kw)
finally:
exceptions._install_highlighting()
return update_wrapper(go, fn)

View file

@ -0,0 +1,109 @@
import os
from mako.cache import CacheImpl
from mako.cache import register_plugin
from mako.template import Template
from .assertions import eq_
from .config import config
class TemplateTest:
def _file_template(self, filename, **kw):
filepath = self._file_path(filename)
return Template(
uri=filename,
filename=filepath,
module_directory=config.module_base,
**kw,
)
def _file_path(self, filename):
name, ext = os.path.splitext(filename)
py3k_path = os.path.join(config.template_base, name + "_py3k" + ext)
if os.path.exists(py3k_path):
return py3k_path
return os.path.join(config.template_base, filename)
def _do_file_test(
self,
filename,
expected,
filters=None,
unicode_=True,
template_args=None,
**kw,
):
t1 = self._file_template(filename, **kw)
self._do_test(
t1,
expected,
filters=filters,
unicode_=unicode_,
template_args=template_args,
)
def _do_memory_test(
self,
source,
expected,
filters=None,
unicode_=True,
template_args=None,
**kw,
):
t1 = Template(text=source, **kw)
self._do_test(
t1,
expected,
filters=filters,
unicode_=unicode_,
template_args=template_args,
)
def _do_test(
self,
template,
expected,
filters=None,
template_args=None,
unicode_=True,
):
if template_args is None:
template_args = {}
if unicode_:
output = template.render_unicode(**template_args)
else:
output = template.render(**template_args)
if filters:
output = filters(output)
eq_(output, expected)
class PlainCacheImpl(CacheImpl):
"""Simple memory cache impl so that tests which
use caching can run without beaker."""
def __init__(self, cache):
self.cache = cache
self.data = {}
def get_or_create(self, key, creation_function, **kw):
if key in self.data:
return self.data[key]
else:
self.data[key] = data = creation_function(**kw)
return data
def put(self, key, value, **kw):
self.data[key] = value
def get(self, key, **kw):
return self.data[key]
def invalidate(self, key, **kw):
del self.data[key]
register_plugin("plain", __name__, "PlainCacheImpl")

View file

@ -0,0 +1,67 @@
import contextlib
import pathlib
from pathlib import Path
import re
import time
from typing import Union
from unittest import mock
def flatten_result(result):
return re.sub(r"[\s\r\n]+", " ", result).strip()
def result_lines(result):
return [
x.strip()
for x in re.split(r"\r?\n", re.sub(r" +", " ", result))
if x.strip() != ""
]
def make_path(
filespec: Union[Path, str],
make_absolute: bool = True,
check_exists: bool = False,
) -> Path:
path = Path(filespec)
if make_absolute:
path = path.resolve(strict=check_exists)
if check_exists and (not path.exists()):
raise FileNotFoundError(f"No file or directory at {filespec}")
return path
def _unlink_path(path, missing_ok=False):
# Replicate 3.8+ functionality in 3.7
cm = contextlib.nullcontext()
if missing_ok:
cm = contextlib.suppress(FileNotFoundError)
with cm:
path.unlink()
def replace_file_with_dir(pathspec):
path = pathlib.Path(pathspec)
_unlink_path(path, missing_ok=True)
path.mkdir(exist_ok=True)
return path
def file_with_template_code(filespec):
with open(filespec, "w") as f:
f.write(
"""
i am an artificial template just for you
"""
)
return filespec
@contextlib.contextmanager
def rewind_compile_time(hours=1):
rewound = time.time() - (hours * 3_600)
with mock.patch("mako.codegen.time") as codegen_time:
codegen_time.time.return_value = rewound
yield

View file

@ -1,10 +1,8 @@
# mako/util.py # mako/util.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> # Copyright 2006-2021 the Mako authors and contributors <see AUTHORS file>
# #
# This module is part of Mako and is released under # This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php # the MIT License: http://www.opensource.org/licenses/mit-license.php
from __future__ import absolute_import
from ast import parse from ast import parse
import codecs import codecs
import collections import collections
@ -13,7 +11,7 @@ import os
import re import re
import timeit import timeit
from mako import compat from .compat import importlib_metadata_get
def update_wrapper(decorated, fn): def update_wrapper(decorated, fn):
@ -22,7 +20,7 @@ def update_wrapper(decorated, fn):
return decorated return decorated
class PluginLoader(object): class PluginLoader:
def __init__(self, group): def __init__(self, group):
self.group = group self.group = group
self.impls = {} self.impls = {}
@ -30,13 +28,12 @@ class PluginLoader(object):
def load(self, name): def load(self, name):
if name in self.impls: if name in self.impls:
return self.impls[name]() return self.impls[name]()
else:
import pkg_resources
for impl in pkg_resources.iter_entry_points(self.group, name): for impl in importlib_metadata_get(self.group):
if impl.name == name:
self.impls[name] = impl.load self.impls[name] = impl.load
return impl.load() return impl.load()
else:
from mako import exceptions from mako import exceptions
raise exceptions.RuntimeException( raise exceptions.RuntimeException(
@ -61,7 +58,7 @@ def verify_directory(dir_):
while not os.path.exists(dir_): while not os.path.exists(dir_):
try: try:
tries += 1 tries += 1
os.makedirs(dir_, compat.octal("0775")) os.makedirs(dir_, 0o755)
except: except:
if tries > 5: if tries > 5:
raise raise
@ -76,7 +73,7 @@ def to_list(x, default=None):
return x return x
class memoized_property(object): class memoized_property:
"""A read-only @property that is only evaluated once.""" """A read-only @property that is only evaluated once."""
@ -92,7 +89,7 @@ class memoized_property(object):
return result return result
class memoized_instancemethod(object): class memoized_instancemethod:
"""Decorate a method memoize its return value. """Decorate a method memoize its return value.
@ -140,19 +137,15 @@ class SetLikeDict(dict):
return x return x
class FastEncodingBuffer(object): class FastEncodingBuffer:
"""a very rudimentary buffer that is faster than StringIO, """a very rudimentary buffer that is faster than StringIO,
but doesn't crash on unicode data like cStringIO.""" and supports unicode data."""
def __init__(self, encoding=None, errors="strict", as_unicode=False): def __init__(self, encoding=None, errors="strict"):
self.data = collections.deque() self.data = collections.deque()
self.encoding = encoding self.encoding = encoding
if as_unicode:
self.delim = compat.u("")
else:
self.delim = "" self.delim = ""
self.as_unicode = as_unicode
self.errors = errors self.errors = errors
self.write = self.data.append self.write = self.data.append
@ -179,7 +172,7 @@ class LRUCache(dict):
is inexact. is inexact.
""" """
class _Item(object): class _Item:
def __init__(self, key, value): def __init__(self, key, value):
self.key = key self.key = key
self.value = value self.value = value
@ -203,7 +196,6 @@ class LRUCache(dict):
def setdefault(self, key, value): def setdefault(self, key, value):
if key in self: if key in self:
return self[key] return self[key]
else:
self[key] = value self[key] = value
return value return value
@ -295,7 +287,7 @@ def sorted_dict_repr(d):
""" """
keys = list(d.keys()) keys = list(d.keys())
keys.sort() keys.sort()
return "{" + ", ".join(["%r: %r" % (k, d[k]) for k in keys]) + "}" return "{" + ", ".join("%r: %r" % (k, d[k]) for k in keys) + "}"
def restore__ast(_ast): def restore__ast(_ast):
@ -308,7 +300,7 @@ def restore__ast(_ast):
m = compile( m = compile(
"""\ """\
def foo(): pass def foo(): pass
class Bar(object): pass class Bar: pass
if False: pass if False: pass
baz = 'mako' baz = 'mako'
1 + 2 - 3 * 4 / 5 1 + 2 - 3 * 4 / 5
@ -380,12 +372,8 @@ mako in baz not in mako""",
def read_file(path, mode="rb"): def read_file(path, mode="rb"):
fp = open(path, mode) with open(path, mode) as fp:
try: return fp.read()
data = fp.read()
return data
finally:
fp.close()
def read_python_file(path): def read_python_file(path):

View file

@ -11,9 +11,10 @@ if t.TYPE_CHECKING:
pass pass
__version__ = "2.0.1" __version__ = "2.1.1"
_striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)") _strip_comments_re = re.compile(r"<!--.*?-->")
_strip_tags_re = re.compile(r"<.*?>")
def _simple_escaping_wrapper(name: str) -> t.Callable[..., "Markup"]: def _simple_escaping_wrapper(name: str) -> t.Callable[..., "Markup"]:
@ -92,19 +93,24 @@ class Markup(str):
return NotImplemented return NotImplemented
def __mul__(self, num: int) -> "Markup": def __mul__(self, num: "te.SupportsIndex") -> "Markup":
if isinstance(num, int): if isinstance(num, int):
return self.__class__(super().__mul__(num)) return self.__class__(super().__mul__(num))
return NotImplemented # type: ignore return NotImplemented
__rmul__ = __mul__ __rmul__ = __mul__
def __mod__(self, arg: t.Any) -> "Markup": def __mod__(self, arg: t.Any) -> "Markup":
if isinstance(arg, tuple): if isinstance(arg, tuple):
# a tuple of arguments, each wrapped
arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg) arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg)
else: elif hasattr(type(arg), "__getitem__") and not isinstance(arg, str):
# a mapping of arguments, wrapped
arg = _MarkupEscapeHelper(arg, self.escape) arg = _MarkupEscapeHelper(arg, self.escape)
else:
# a single argument, wrapped with the helper and a tuple
arg = (_MarkupEscapeHelper(arg, self.escape),)
return self.__class__(super().__mod__(arg)) return self.__class__(super().__mod__(arg))
@ -153,8 +159,11 @@ class Markup(str):
>>> Markup("Main &raquo;\t<em>About</em>").striptags() >>> Markup("Main &raquo;\t<em>About</em>").striptags()
'Main » About' 'Main » About'
""" """
stripped = " ".join(_striptags_re.sub("", self).split()) # Use two regexes to avoid ambiguous matches.
return Markup(stripped).unescape() value = _strip_comments_re.sub("", self)
value = _strip_tags_re.sub("", value)
value = " ".join(value.split())
return Markup(value).unescape()
@classmethod @classmethod
def escape(cls, s: t.Any) -> "Markup": def escape(cls, s: t.Any) -> "Markup":
@ -280,9 +289,7 @@ try:
from ._speedups import escape as escape from ._speedups import escape as escape
from ._speedups import escape_silent as escape_silent from ._speedups import escape_silent as escape_silent
from ._speedups import soft_str as soft_str from ._speedups import soft_str as soft_str
from ._speedups import soft_unicode
except ImportError: except ImportError:
from ._native import escape as escape from ._native import escape as escape
from ._native import escape_silent as escape_silent # noqa: F401 from ._native import escape_silent as escape_silent # noqa: F401
from ._native import soft_str as soft_str # noqa: F401 from ._native import soft_str as soft_str # noqa: F401
from ._native import soft_unicode # noqa: F401

View file

@ -61,15 +61,3 @@ def soft_str(s: t.Any) -> str:
return str(s) return str(s)
return s return s
def soft_unicode(s: t.Any) -> str:
import warnings
warnings.warn(
"'soft_unicode' has been renamed to 'soft_str'. The old name"
" will be removed in MarkupSafe 2.1.",
DeprecationWarning,
stacklevel=2,
)
return soft_str(s)

320
lib/markupsafe/_speedups.c Normal file
View file

@ -0,0 +1,320 @@
#include <Python.h>
static PyObject* markup;
static int
init_constants(void)
{
PyObject *module;
/* import markup type so that we can mark the return value */
module = PyImport_ImportModule("markupsafe");
if (!module)
return 0;
markup = PyObject_GetAttrString(module, "Markup");
Py_DECREF(module);
return 1;
}
#define GET_DELTA(inp, inp_end, delta) \
while (inp < inp_end) { \
switch (*inp++) { \
case '"': \
case '\'': \
case '&': \
delta += 4; \
break; \
case '<': \
case '>': \
delta += 3; \
break; \
} \
}
#define DO_ESCAPE(inp, inp_end, outp) \
{ \
Py_ssize_t ncopy = 0; \
while (inp < inp_end) { \
switch (*inp) { \
case '"': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
*outp++ = '&'; \
*outp++ = '#'; \
*outp++ = '3'; \
*outp++ = '4'; \
*outp++ = ';'; \
break; \
case '\'': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
*outp++ = '&'; \
*outp++ = '#'; \
*outp++ = '3'; \
*outp++ = '9'; \
*outp++ = ';'; \
break; \
case '&': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
*outp++ = '&'; \
*outp++ = 'a'; \
*outp++ = 'm'; \
*outp++ = 'p'; \
*outp++ = ';'; \
break; \
case '<': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
*outp++ = '&'; \
*outp++ = 'l'; \
*outp++ = 't'; \
*outp++ = ';'; \
break; \
case '>': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
*outp++ = '&'; \
*outp++ = 'g'; \
*outp++ = 't'; \
*outp++ = ';'; \
break; \
default: \
ncopy++; \
} \
inp++; \
} \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
}
static PyObject*
escape_unicode_kind1(PyUnicodeObject *in)
{
Py_UCS1 *inp = PyUnicode_1BYTE_DATA(in);
Py_UCS1 *inp_end = inp + PyUnicode_GET_LENGTH(in);
Py_UCS1 *outp;
PyObject *out;
Py_ssize_t delta = 0;
GET_DELTA(inp, inp_end, delta);
if (!delta) {
Py_INCREF(in);
return (PyObject*)in;
}
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta,
PyUnicode_IS_ASCII(in) ? 127 : 255);
if (!out)
return NULL;
inp = PyUnicode_1BYTE_DATA(in);
outp = PyUnicode_1BYTE_DATA(out);
DO_ESCAPE(inp, inp_end, outp);
return out;
}
static PyObject*
escape_unicode_kind2(PyUnicodeObject *in)
{
Py_UCS2 *inp = PyUnicode_2BYTE_DATA(in);
Py_UCS2 *inp_end = inp + PyUnicode_GET_LENGTH(in);
Py_UCS2 *outp;
PyObject *out;
Py_ssize_t delta = 0;
GET_DELTA(inp, inp_end, delta);
if (!delta) {
Py_INCREF(in);
return (PyObject*)in;
}
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, 65535);
if (!out)
return NULL;
inp = PyUnicode_2BYTE_DATA(in);
outp = PyUnicode_2BYTE_DATA(out);
DO_ESCAPE(inp, inp_end, outp);
return out;
}
static PyObject*
escape_unicode_kind4(PyUnicodeObject *in)
{
Py_UCS4 *inp = PyUnicode_4BYTE_DATA(in);
Py_UCS4 *inp_end = inp + PyUnicode_GET_LENGTH(in);
Py_UCS4 *outp;
PyObject *out;
Py_ssize_t delta = 0;
GET_DELTA(inp, inp_end, delta);
if (!delta) {
Py_INCREF(in);
return (PyObject*)in;
}
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, 1114111);
if (!out)
return NULL;
inp = PyUnicode_4BYTE_DATA(in);
outp = PyUnicode_4BYTE_DATA(out);
DO_ESCAPE(inp, inp_end, outp);
return out;
}
static PyObject*
escape_unicode(PyUnicodeObject *in)
{
if (PyUnicode_READY(in))
return NULL;
switch (PyUnicode_KIND(in)) {
case PyUnicode_1BYTE_KIND:
return escape_unicode_kind1(in);
case PyUnicode_2BYTE_KIND:
return escape_unicode_kind2(in);
case PyUnicode_4BYTE_KIND:
return escape_unicode_kind4(in);
}
assert(0); /* shouldn't happen */
return NULL;
}
static PyObject*
escape(PyObject *self, PyObject *text)
{
static PyObject *id_html;
PyObject *s = NULL, *rv = NULL, *html;
if (id_html == NULL) {
id_html = PyUnicode_InternFromString("__html__");
if (id_html == NULL) {
return NULL;
}
}
/* we don't have to escape integers, bools or floats */
if (PyLong_CheckExact(text) ||
PyFloat_CheckExact(text) || PyBool_Check(text) ||
text == Py_None)
return PyObject_CallFunctionObjArgs(markup, text, NULL);
/* if the object has an __html__ method that performs the escaping */
html = PyObject_GetAttr(text ,id_html);
if (html) {
s = PyObject_CallObject(html, NULL);
Py_DECREF(html);
if (s == NULL) {
return NULL;
}
/* Convert to Markup object */
rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
Py_DECREF(s);
return rv;
}
/* otherwise make the object unicode if it isn't, then escape */
PyErr_Clear();
if (!PyUnicode_Check(text)) {
PyObject *unicode = PyObject_Str(text);
if (!unicode)
return NULL;
s = escape_unicode((PyUnicodeObject*)unicode);
Py_DECREF(unicode);
}
else
s = escape_unicode((PyUnicodeObject*)text);
/* convert the unicode string into a markup object. */
rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
Py_DECREF(s);
return rv;
}
static PyObject*
escape_silent(PyObject *self, PyObject *text)
{
if (text != Py_None)
return escape(self, text);
return PyObject_CallFunctionObjArgs(markup, NULL);
}
static PyObject*
soft_str(PyObject *self, PyObject *s)
{
if (!PyUnicode_Check(s))
return PyObject_Str(s);
Py_INCREF(s);
return s;
}
static PyMethodDef module_methods[] = {
{
"escape",
(PyCFunction)escape,
METH_O,
"Replace the characters ``&``, ``<``, ``>``, ``'``, and ``\"`` in"
" the string with HTML-safe sequences. Use this if you need to display"
" text that might contain such characters in HTML.\n\n"
"If the object has an ``__html__`` method, it is called and the"
" return value is assumed to already be safe for HTML.\n\n"
":param s: An object to be converted to a string and escaped.\n"
":return: A :class:`Markup` string with the escaped text.\n"
},
{
"escape_silent",
(PyCFunction)escape_silent,
METH_O,
"Like :func:`escape` but treats ``None`` as the empty string."
" Useful with optional values, as otherwise you get the string"
" ``'None'`` when the value is ``None``.\n\n"
">>> escape(None)\n"
"Markup('None')\n"
">>> escape_silent(None)\n"
"Markup('')\n"
},
{
"soft_str",
(PyCFunction)soft_str,
METH_O,
"Convert an object to a string if it isn't already. This preserves"
" a :class:`Markup` string rather than converting it back to a basic"
" string, so it will still be marked as safe and won't be escaped"
" again.\n\n"
">>> value = escape(\"<User 1>\")\n"
">>> value\n"
"Markup('&lt;User 1&gt;')\n"
">>> escape(str(value))\n"
"Markup('&amp;lt;User 1&amp;gt;')\n"
">>> escape(soft_str(value))\n"
"Markup('&lt;User 1&gt;')\n"
},
{NULL, NULL, 0, NULL} /* Sentinel */
};
static struct PyModuleDef module_definition = {
PyModuleDef_HEAD_INIT,
"markupsafe._speedups",
NULL,
-1,
module_methods,
NULL,
NULL,
NULL,
NULL
};
PyMODINIT_FUNC
PyInit__speedups(void)
{
if (!init_constants())
return NULL;
return PyModule_Create(&module_definition);
}

View file

@ -18,11 +18,12 @@ gntp==1.0.3
html5lib==1.1 html5lib==1.1
httpagentparser==1.9.2 httpagentparser==1.9.2
idna==3.3 idna==3.3
importlib-metadata==4.11.3
importlib-resources==5.6.0 importlib-resources==5.6.0
git+https://github.com/Tautulli/ipwhois.git@master#egg=ipwhois git+https://github.com/Tautulli/ipwhois.git@master#egg=ipwhois
IPy==1.01 IPy==1.01
Mako==1.1.6 Mako==1.2.0
MarkupSafe==2.0.1 MarkupSafe==2.1.1
musicbrainzngs==0.7.1 musicbrainzngs==0.7.1
packaging==21.3 packaging==21.3
paho-mqtt==1.6.1 paho-mqtt==1.6.1