wrapper class that takes a list of template loaders as an argument and attempts to load templates from them in order caching the result perform the caching that gives this loader its name often many of the templates attempted will be missing so memory use is of concern here to keep it in check caching behavior is a little complicated when a template is not found see ticket 26306 for more details with template debugging disabled cache the templatedoesnotexist class for every missing template and raise a new instance of it after fetching it from the cache with template debugging enabled a unique templatedoesnotexist object is cached for each missing template to preserve debug data when raising an exception python sets traceback context and cause attributes on it those attributes can contain references to all sorts of objects up the call chain and caching them creates a memory leak thus unraised copies of the exceptions are cached and copies of those copies are raised after they re fetched from the cache generate a cache key for the template name and skip if skip is provided only origins that match templatename are included in the cache key this ensures each template is only parsed and cached once if contained in different extend chains like x a a y a a z a a perform the caching that gives this loader its name often many of the templates attempted will be missing so memory use is of concern here to keep it in check caching behavior is a little complicated when a template is not found see ticket 26306 for more details with template debugging disabled cache the templatedoesnotexist class for every missing template and raise a new instance of it after fetching it from the cache with template debugging enabled a unique templatedoesnotexist object is cached for each missing template to preserve debug data when raising an exception python sets __traceback__ __context__ and __cause__ attributes on it those attributes can contain references to all sorts of objects up the call chain and caching them creates a memory leak thus unraised copies of the exceptions are cached and copies of those copies are raised after they re fetched from the cache generate a cache key for the template name and skip if skip is provided only origins that match template_name are included in the cache key this ensures each template is only parsed and cached once if contained in different extend chains like x a a y a a z a a
import hashlib

from django.template import TemplateDoesNotExist
from django.template.backends.django import copy_exception

from .base import Loader as BaseLoader


class Loader(BaseLoader):
    def __init__(self, engine, loaders):
        self.get_template_cache = {}
        self.loaders = engine.get_template_loaders(loaders)
        super().__init__(engine)

    def get_dirs(self):
        for loader in self.loaders:
            if hasattr(loader, "get_dirs"):
                yield from loader.get_dirs()

    def get_contents(self, origin):
        return origin.loader.get_contents(origin)

    def get_template(self, template_name, skip=None):
        
        key = self.cache_key(template_name, skip)
        cached = self.get_template_cache.get(key)
        if cached:
            if isinstance(cached, type) and issubclass(cached, TemplateDoesNotExist):
                raise cached(template_name)
            elif isinstance(cached, TemplateDoesNotExist):
                raise copy_exception(cached)
            return cached

        try:
            template = super().get_template(template_name, skip)
        except TemplateDoesNotExist as e:
            self.get_template_cache[key] = (
                copy_exception(e) if self.engine.debug else TemplateDoesNotExist
            )
            raise
        else:
            self.get_template_cache[key] = template

        return template

    def get_template_sources(self, template_name):
        for loader in self.loaders:
            yield from loader.get_template_sources(template_name)

    def cache_key(self, template_name, skip=None):
        
        skip_prefix = ""

        if skip:
            matching = [
                origin.name for origin in skip if origin.template_name == template_name
            ]
            if matching:
                skip_prefix = self.generate_hash(matching)

        return "-".join(s for s in (str(template_name), skip_prefix) if s)

    def generate_hash(self, values):
        return hashlib.sha1("|".join(values).encode()).hexdigest()

    def reset(self):
        "Empty the template cache."
        self.get_template_cache.clear()