summaryrefslogtreecommitdiff
path: root/jinja2/loaders.py
diff options
context:
space:
mode:
Diffstat (limited to 'jinja2/loaders.py')
-rw-r--r--jinja2/loaders.py128
1 files changed, 81 insertions, 47 deletions
diff --git a/jinja2/loaders.py b/jinja2/loaders.py
index 4c79793..031fa7a 100644
--- a/jinja2/loaders.py
+++ b/jinja2/loaders.py
@@ -9,6 +9,7 @@
:license: BSD, see LICENSE for more details.
"""
import os
+import pkgutil
import sys
import weakref
from types import ModuleType
@@ -203,66 +204,99 @@ class FileSystemLoader(BaseLoader):
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.
+
+ .. versionchanged:: 2.11.0
+ No longer uses ``setuptools`` as a dependency.
"""
- def __init__(self, package_name, package_path='templates',
- encoding='utf-8'):
- from pkg_resources import DefaultProvider, ResourceManager, \
- get_provider
- provider = get_provider(package_name)
- self.encoding = encoding
- self.manager = ResourceManager()
- self.filesystem_bound = isinstance(provider, DefaultProvider)
- self.provider = provider
+ def __init__(self, package_name, package_path="templates", encoding="utf-8"):
+ if package_path == os.path.curdir:
+ package_path = ""
+ elif package_path[:2] == os.path.curdir + "/":
+ package_path = package_path[2:]
+
+ self.package_name = package_name
self.package_path = package_path
+ self.encoding = encoding
+
+ self._loader = pkgutil.get_loader(package_name)
+ # Zip loader's archive attribute points at the zip.
+ self._archive = getattr(self._loader, "archive", None)
+ self._template_root = os.path.join(
+ os.path.dirname(self._loader.get_filename(package_name)), package_path
+ ).rstrip(os.path.sep)
def get_source(self, environment, template):
- pieces = split_template_path(template)
- p = '/'.join((self.package_path,) + tuple(pieces))
- if not self.provider.has_resource(p):
- raise TemplateNotFound(template)
+ p = "/".join([self._template_root] + split_template_path(template))
- filename = uptodate = None
- if self.filesystem_bound:
- filename = self.provider.get_resource_filename(self.manager, p)
- mtime = path.getmtime(filename)
- def uptodate():
- try:
- return path.getmtime(filename) == mtime
- except OSError:
- return False
+ if self._archive is None:
+ # Package is a directory.
+ if not os.path.isfile(p):
+ raise TemplateNotFound(template)
+
+ with open(p, "rb") as f:
+ source = f.read()
- source = self.provider.get_resource_string(self.manager, p)
- return source.decode(self.encoding), filename, uptodate
+ mtime = os.path.getmtime(p)
+
+ def up_to_date():
+ return os.path.isfile(p) and os.path.getmtime(p) == mtime
+ else:
+ # Package is a zip file.
+ try:
+ source = self._loader.get_data(p)
+ except OSError:
+ raise TemplateNotFound(template)
+
+ # Could use the zip's mtime for all template mtimes, but
+ # would need to safely reload the module if it's out of
+ # date, so just report it as always current.
+ up_to_date = None
+
+ return source.decode(self.encoding), p, up_to_date
def list_templates(self):
- path = self.package_path
- if path[:2] == './':
- path = path[2:]
- elif path == '.':
- path = ''
- offset = len(path)
results = []
- def _walk(path):
- for filename in self.provider.resource_listdir(path):
- fullname = path + '/' + filename
- if self.provider.resource_isdir(fullname):
- _walk(fullname)
- else:
- results.append(fullname[offset:].lstrip('/'))
- _walk(path)
+
+ if self._archive is None:
+ # Package is a directory.
+ offset = len(self._template_root)
+
+ for dirpath, _, filenames in os.walk(self._template_root):
+ dirpath = dirpath[offset:].lstrip(os.path.sep)
+ results.extend(os.path.join(dirpath, name) for name in filenames)
+ else:
+ # Package is a zip file.
+ prefix = (
+ self._template_root[len(self._archive):].lstrip(os.path.sep)
+ + os.path.sep
+ )
+ offset = len(prefix)
+
+ for name in self._loader._files.keys():
+ # Find names under the templates directory that aren't directories.
+ if name.startswith(prefix) and name[-1] != os.path.sep:
+ results.append(name[offset:])
+
results.sort()
return results