:class:`~unittest.mock.Mock` to be treated as a
:func:`contextfunction`. :issue:`1145`
- Update ``wordcount`` filter to trigger :class:`Undefined` methods
- by wrapping the input in :func:`soft_unicode`. :pr:`1160`
+ by wrapping the input in :func:`soft_str`. :pr:`1160`
+ - Fix a hang when displaying tracebacks on Python 32-bit.
+ :issue:`1162`
+ - Showing an undefined error for an object that raises
+ ``AttributeError`` on access doesn't cause a recursion error.
+ :issue:`1177`
+ - Revert changes to :class:`~loaders.PackageLoader` from 2.10 which
+ removed the dependency on setuptools and pkg_resources, and added
+ limited support for namespace packages. The changes caused issues
+ when using Pytest. Due to the difficulty in supporting Python 2 and
+ :pep:`451` simultaneously, the changes are reverted until 3.0.
+ :pr:`1182`
+
Version 2.11.1
--------------
class PackageLoader(BaseLoader):
- """Load templates from python eggs or packages. It is constructed with
- the name of the python package and the path to the templates in that
- package::
+ """Load templates from a directory in a Python package.
- loader = PackageLoader('mypackage', 'views')
+ :param package_name: Import name of the package that contains the
+ template directory.
+ :param package_path: Directory within the imported package that
+ contains the templates.
+ :param encoding: Encoding of template files.
- If the package path is not given, ``'templates'`` is assumed.
+ The following example looks up templates in the ``pages`` directory
+ within the ``project.ui`` package.
- Per default the template encoding is ``'utf-8'`` which can be changed
- by setting the `encoding` parameter to something else. Due to the nature
- of eggs it's only possible to reload templates if the package was loaded
- from the file system and not a zip file.
+ .. code-block:: python
+
+ loader = PackageLoader("project.ui", "pages")
+
+ Only packages installed as directories (standard pip behavior) or
+ zip/egg files (less common) are supported. The Python API for
+ introspecting data in packages is too limited to support other
+ installation methods the way this loader requires.
+
+ There is limited support for :pep:`420` namespace packages. The
+ template directory is assumed to only be in one namespace
+ contributor. Zip files contributing to a namespace are not
+ supported.
+
- .. versionchanged:: 2.11.0
++ .. versionchanged:: 3.0
+ No longer uses ``setuptools`` as a dependency.
+
- .. versionchanged:: 2.11.0
++ .. versionchanged:: 3.0
+ Limited PEP 420 namespace package support.
"""
def __init__(self, package_name, package_path="templates", encoding="utf-8"):
return "None"
elif obj is Ellipsis:
return "Ellipsis"
- # __builtin__ in 2.x, builtins in 3.x
- if obj.__class__.__module__ in ("__builtin__", "builtins"):
- name = obj.__class__.__name__
- else:
- name = f"{obj.__class__.__module__}.{obj.__class__.__name__}"
- return f"{name} object"
+
+ cls = type(obj)
+
- # __builtin__ in 2.x, builtins in 3.x
- if cls.__module__ in ("__builtin__", "builtins"):
- name = cls.__name__
- else:
- name = cls.__module__ + "." + cls.__name__
++ if cls.__module__ == "builtins":
++ return f"{cls.__name__} object"
+
- return "%s object" % name
++ return f"{cls.__module__}.{cls.__name__} object"
-def pformat(obj, verbose=False):
- """Prettyprint an object. Either use the `pretty` library or the
- builtin `pprint`.
+def pformat(obj):
+ """Format an object using :func:`pprint.pformat`.
"""
- try:
- from pretty import pretty
-
- return pretty(obj, verbose=verbose)
- except ImportError:
- from pprint import pformat
+ from pprint import pformat
- return pformat(obj)
+ return pformat(obj)
def urlize(text, trim_url_limit=None, rel=None, target=None):
with pytest.raises(AttributeError):
Undefined("Foo").__dict__
- class Error(object):
+ def test_undefined_attribute_error(self):
+ # Django's LazyObject turns the __class__ attribute into a
+ # property that resolves the wrapped function. If that wrapped
+ # function raises an AttributeError, printing the repr of the
+ # object in the undefined message would cause a RecursionError.
++ class Error:
+ @property
+ def __class__(self):
+ raise AttributeError()
+
+ u = Undefined(obj=Error(), name="hello")
+
+ with pytest.raises(UndefinedError):
+ getattr(u, "recursion", None)
+
def test_logging_undefined(self):
_messages = []