Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
template.py716 linesDownload Raw Back to mako
1# mako/template.py2# Copyright 2006-2024 the Mako authors and contributors <see AUTHORS file>3#4# This module is part of Mako and is released under5# the MIT License: http://www.opensource.org/licenses/mit-license.php6 7"""Provides the Template class, a facade for parsing, generating and executing8template strings, as well as template runtime operations."""9 10import json11import os12import re13import shutil14import stat15import tempfile16import types17import weakref18 19from mako import cache20from mako import codegen21from mako import compat22from mako import exceptions23from mako import runtime24from mako import util25from mako.lexer import Lexer26 27 28class Template:29    r"""Represents a compiled template.30 31    :class:`.Template` includes a reference to the original32    template source (via the :attr:`.source` attribute)33    as well as the source code of the34    generated Python module (i.e. the :attr:`.code` attribute),35    as well as a reference to an actual Python module.36 37    :class:`.Template` is constructed using either a literal string38    representing the template text, or a filename representing a filesystem39    path to a source file.40 41    :param text: textual template source.  This argument is mutually42     exclusive versus the ``filename`` parameter.43 44    :param filename: filename of the source template.  This argument is45     mutually exclusive versus the ``text`` parameter.46 47    :param buffer_filters: string list of filters to be applied48     to the output of ``%def``\ s which are buffered, cached, or otherwise49     filtered, after all filters50     defined with the ``%def`` itself have been applied. Allows the51     creation of default expression filters that let the output52     of return-valued ``%def``\ s "opt out" of that filtering via53     passing special attributes or objects.54 55    :param cache_args: Dictionary of cache configuration arguments that56     will be passed to the :class:`.CacheImpl`.   See :ref:`caching_toplevel`.57 58    :param cache_dir:59 60     .. deprecated:: 0.661        Use the ``'dir'`` argument in the ``cache_args`` dictionary.62        See :ref:`caching_toplevel`.63 64    :param cache_enabled: Boolean flag which enables caching of this65     template.  See :ref:`caching_toplevel`.66 67    :param cache_impl: String name of a :class:`.CacheImpl` caching68     implementation to use.   Defaults to ``'beaker'``.69 70    :param cache_type:71 72     .. deprecated:: 0.673        Use the ``'type'`` argument in the ``cache_args`` dictionary.74        See :ref:`caching_toplevel`.75 76    :param cache_url:77 78     .. deprecated:: 0.679        Use the ``'url'`` argument in the ``cache_args`` dictionary.80        See :ref:`caching_toplevel`.81 82    :param default_filters: List of string filter names that will83     be applied to all expressions.  See :ref:`filtering_default_filters`.84 85    :param enable_loop: When ``True``, enable the ``loop`` context variable.86     This can be set to ``False`` to support templates that may87     be making usage of the name "``loop``".   Individual templates can88     re-enable the "loop" context by placing the directive89     ``enable_loop="True"`` inside the ``<%page>`` tag -- see90     :ref:`migrating_loop`.91 92    :param encoding_errors: Error parameter passed to ``encode()`` when93     string encoding is performed. See :ref:`usage_unicode`.94 95    :param error_handler: Python callable which is called whenever96     compile or runtime exceptions occur. The callable is passed97     the current context as well as the exception. If the98     callable returns ``True``, the exception is considered to99     be handled, else it is re-raised after the function100     completes. Is used to provide custom error-rendering101     functions.102 103     .. seealso::104 105        :paramref:`.Template.include_error_handler` - include-specific106        error handler function107 108    :param format_exceptions: if ``True``, exceptions which occur during109     the render phase of this template will be caught and110     formatted into an HTML error page, which then becomes the111     rendered result of the :meth:`.render` call. Otherwise,112     runtime exceptions are propagated outwards.113 114    :param imports: String list of Python statements, typically individual115     "import" lines, which will be placed into the module level116     preamble of all generated Python modules. See the example117     in :ref:`filtering_default_filters`.118 119    :param future_imports: String list of names to import from `__future__`.120     These will be concatenated into a comma-separated string and inserted121     into the beginning of the template, e.g. ``futures_imports=['FOO',122     'BAR']`` results in ``from __future__ import FOO, BAR``.  If you're123     interested in using features like the new division operator, you must124     use future_imports to convey that to the renderer, as otherwise the125     import will not appear as the first executed statement in the generated126     code and will therefore not have the desired effect.127 128    :param include_error_handler: An error handler that runs when this template129     is included within another one via the ``<%include>`` tag, and raises an130     error.  Compare to the :paramref:`.Template.error_handler` option.131 132     .. versionadded:: 1.0.6133 134     .. seealso::135 136        :paramref:`.Template.error_handler` - top-level error handler function137 138    :param input_encoding: Encoding of the template's source code.  Can139     be used in lieu of the coding comment. See140     :ref:`usage_unicode` as well as :ref:`unicode_toplevel` for141     details on source encoding.142 143    :param lookup: a :class:`.TemplateLookup` instance that will be used144     for all file lookups via the ``<%namespace>``,145     ``<%include>``, and ``<%inherit>`` tags. See146     :ref:`usage_templatelookup`.147 148    :param module_directory: Filesystem location where generated149     Python module files will be placed.150 151    :param module_filename: Overrides the filename of the generated152     Python module file. For advanced usage only.153 154    :param module_writer: A callable which overrides how the Python155     module is written entirely.  The callable is passed the156     encoded source content of the module and the destination157     path to be written to.   The default behavior of module writing158     uses a tempfile in conjunction with a file move in order159     to make the operation atomic.   So a user-defined module160     writing function that mimics the default behavior would be:161 162     .. sourcecode:: python163 164         import tempfile165         import os166         import shutil167 168         def module_writer(source, outputpath):169             (dest, name) = \\170                 tempfile.mkstemp(171                     dir=os.path.dirname(outputpath)172                 )173 174             os.write(dest, source)175             os.close(dest)176             shutil.move(name, outputpath)177 178         from mako.template import Template179         mytemplate = Template(180                         filename="index.html",181                         module_directory="/path/to/modules",182                         module_writer=module_writer183                     )184 185     The function is provided for unusual configurations where186     certain platform-specific permissions or other special187     steps are needed.188 189    :param output_encoding: The encoding to use when :meth:`.render`190     is called.191     See :ref:`usage_unicode` as well as :ref:`unicode_toplevel`.192 193    :param preprocessor: Python callable which will be passed194     the full template source before it is parsed. The return195     result of the callable will be used as the template source196     code.197 198    :param lexer_cls: A :class:`.Lexer` class used to parse199     the template.   The :class:`.Lexer` class is used by200     default.201 202     .. versionadded:: 0.7.4203 204    :param strict_undefined: Replaces the automatic usage of205     ``UNDEFINED`` for any undeclared variables not located in206     the :class:`.Context` with an immediate raise of207     ``NameError``. The advantage is immediate reporting of208     missing variables which include the name.209 210     .. versionadded:: 0.3.6211 212    :param uri: string URI or other identifier for this template.213     If not provided, the ``uri`` is generated from the filesystem214     path, or from the in-memory identity of a non-file-based215     template. The primary usage of the ``uri`` is to provide a key216     within :class:`.TemplateLookup`, as well as to generate the217     file path of the generated Python module file, if218     ``module_directory`` is specified.219 220    """221 222    lexer_cls = Lexer223 224    def __init__(225        self,226        text=None,227        filename=None,228        uri=None,229        format_exceptions=False,230        error_handler=None,231        lookup=None,232        output_encoding=None,233        encoding_errors="strict",234        module_directory=None,235        cache_args=None,236        cache_impl="beaker",237        cache_enabled=True,238        cache_type=None,239        cache_dir=None,240        cache_url=None,241        module_filename=None,242        input_encoding=None,243        module_writer=None,244        default_filters=None,245        buffer_filters=(),246        strict_undefined=False,247        imports=None,248        future_imports=None,249        enable_loop=True,250        preprocessor=None,251        lexer_cls=None,252        include_error_handler=None,253    ):254        if uri:255            self.module_id = re.sub(r"\W", "_", uri)256            self.uri = uri257        elif filename:258            self.module_id = re.sub(r"\W", "_", filename)259            drive, path = os.path.splitdrive(filename)260            path = os.path.normpath(path).replace(os.path.sep, "/")261            self.uri = path262        else:263            self.module_id = "memory:" + hex(id(self))264            self.uri = self.module_id265 266        u_norm = self.uri267        if u_norm.startswith("/"):268            u_norm = u_norm[1:]269        u_norm = os.path.normpath(u_norm)270        if u_norm.startswith(".."):271            raise exceptions.TemplateLookupException(272                'Template uri "%s" is invalid - '273                "it cannot be relative outside "274                "of the root path." % self.uri275            )276 277        self.input_encoding = input_encoding278        self.output_encoding = output_encoding279        self.encoding_errors = encoding_errors280        self.enable_loop = enable_loop281        self.strict_undefined = strict_undefined282        self.module_writer = module_writer283 284        if default_filters is None:285            self.default_filters = ["str"]286        else:287            self.default_filters = default_filters288        self.buffer_filters = buffer_filters289 290        self.imports = imports291        self.future_imports = future_imports292        self.preprocessor = preprocessor293 294        if lexer_cls is not None:295            self.lexer_cls = lexer_cls296 297        # if plain text, compile code in memory only298        if text is not None:299            (code, module) = _compile_text(self, text, filename)300            self._code = code301            self._source = text302            ModuleInfo(module, None, self, filename, code, text, uri)303        elif filename is not None:304            # if template filename and a module directory, load305            # a filesystem-based module file, generating if needed306            if module_filename is not None:307                path = module_filename308            elif module_directory is not None:309                path = os.path.abspath(310                    os.path.join(311                        os.path.normpath(module_directory), u_norm + ".py"312                    )313                )314            else:315                path = None316            module = self._compile_from_file(path, filename)317        else:318            raise exceptions.RuntimeException(319                "Template requires text or filename"320            )321 322        self.module = module323        self.filename = filename324        self.callable_ = self.module.render_body325        self.format_exceptions = format_exceptions326        self.error_handler = error_handler327        self.include_error_handler = include_error_handler328        self.lookup = lookup329 330        self.module_directory = module_directory331 332        self._setup_cache_args(333            cache_impl,334            cache_enabled,335            cache_args,336            cache_type,337            cache_dir,338            cache_url,339        )340 341    @util.memoized_property342    def reserved_names(self):343        if self.enable_loop:344            return codegen.RESERVED_NAMES345        else:346            return codegen.RESERVED_NAMES.difference(["loop"])347 348    def _setup_cache_args(349        self,350        cache_impl,351        cache_enabled,352        cache_args,353        cache_type,354        cache_dir,355        cache_url,356    ):357        self.cache_impl = cache_impl358        self.cache_enabled = cache_enabled359        self.cache_args = cache_args or {}360        # transfer deprecated cache_* args361        if cache_type:362            self.cache_args["type"] = cache_type363        if cache_dir:364            self.cache_args["dir"] = cache_dir365        if cache_url:366            self.cache_args["url"] = cache_url367 368    def _compile_from_file(self, path, filename):369        if path is not None:370            util.verify_directory(os.path.dirname(path))371            filemtime = os.stat(filename)[stat.ST_MTIME]372            if (373                not os.path.exists(path)374                or os.stat(path)[stat.ST_MTIME] < filemtime375            ):376                data = util.read_file(filename)377                _compile_module_file(378                    self, data, filename, path, self.module_writer379                )380            module = compat.load_module(self.module_id, path)381            if module._magic_number != codegen.MAGIC_NUMBER:382                data = util.read_file(filename)383                _compile_module_file(384                    self, data, filename, path, self.module_writer385                )386                module = compat.load_module(self.module_id, path)387            ModuleInfo(module, path, self, filename, None, None, None)388        else:389            # template filename and no module directory, compile code390            # in memory391            data = util.read_file(filename)392            code, module = _compile_text(self, data, filename)393            self._source = None394            self._code = code395            ModuleInfo(module, None, self, filename, code, None, None)396        return module397 398    @property399    def source(self):400        """Return the template source code for this :class:`.Template`."""401 402        return _get_module_info_from_callable(self.callable_).source403 404    @property405    def code(self):406        """Return the module source code for this :class:`.Template`."""407 408        return _get_module_info_from_callable(self.callable_).code409 410    @util.memoized_property411    def cache(self):412        return cache.Cache(self)413 414    @property415    def cache_dir(self):416        return self.cache_args["dir"]417 418    @property419    def cache_url(self):420        return self.cache_args["url"]421 422    @property423    def cache_type(self):424        return self.cache_args["type"]425 426    def render(self, *args, **data):427        """Render the output of this template as a string.428 429        If the template specifies an output encoding, the string430        will be encoded accordingly, else the output is raw (raw431        output uses `StringIO` and can't handle multibyte432        characters). A :class:`.Context` object is created corresponding433        to the given data. Arguments that are explicitly declared434        by this template's internal rendering method are also435        pulled from the given ``*args``, ``**data`` members.436 437        """438        return runtime._render(self, self.callable_, args, data)439 440    def render_unicode(self, *args, **data):441        """Render the output of this template as a unicode object."""442 443        return runtime._render(444            self, self.callable_, args, data, as_unicode=True445        )446 447    def render_context(self, context, *args, **kwargs):448        """Render this :class:`.Template` with the given context.449 450        The data is written to the context's buffer.451 452        """453        if getattr(context, "_with_template", None) is None:454            context._set_with_template(self)455        runtime._render_context(self, self.callable_, context, *args, **kwargs)456 457    def has_def(self, name):458        return hasattr(self.module, "render_%s" % name)459 460    def get_def(self, name):461        """Return a def of this template as a :class:`.DefTemplate`."""462 463        return DefTemplate(self, getattr(self.module, "render_%s" % name))464 465    def list_defs(self):466        """return a list of defs in the template.467 468        .. versionadded:: 1.0.4469 470        """471        return [i[7:] for i in dir(self.module) if i[:7] == "render_"]472 473    def _get_def_callable(self, name):474        return getattr(self.module, "render_%s" % name)475 476    @property477    def last_modified(self):478        return self.module._modified_time479 480 481class ModuleTemplate(Template):482 483    """A Template which is constructed given an existing Python module.484 485    e.g.::486 487         t = Template("this is a template")488         f = file("mymodule.py", "w")489         f.write(t.code)490         f.close()491 492         import mymodule493 494         t = ModuleTemplate(mymodule)495         print(t.render())496 497    """498 499    def __init__(500        self,501        module,502        module_filename=None,503        template=None,504        template_filename=None,505        module_source=None,506        template_source=None,507        output_encoding=None,508        encoding_errors="strict",509        format_exceptions=False,510        error_handler=None,511        lookup=None,512        cache_args=None,513        cache_impl="beaker",514        cache_enabled=True,515        cache_type=None,516        cache_dir=None,517        cache_url=None,518        include_error_handler=None,519    ):520        self.module_id = re.sub(r"\W", "_", module._template_uri)521        self.uri = module._template_uri522        self.input_encoding = module._source_encoding523        self.output_encoding = output_encoding524        self.encoding_errors = encoding_errors525        self.enable_loop = module._enable_loop526 527        self.module = module528        self.filename = template_filename529        ModuleInfo(530            module,531            module_filename,532            self,533            template_filename,534            module_source,535            template_source,536            module._template_uri,537        )538 539        self.callable_ = self.module.render_body540        self.format_exceptions = format_exceptions541        self.error_handler = error_handler542        self.include_error_handler = include_error_handler543        self.lookup = lookup544        self._setup_cache_args(545            cache_impl,546            cache_enabled,547            cache_args,548            cache_type,549            cache_dir,550            cache_url,551        )552 553 554class DefTemplate(Template):555 556    """A :class:`.Template` which represents a callable def in a parent557    template."""558 559    def __init__(self, parent, callable_):560        self.parent = parent561        self.callable_ = callable_562        self.output_encoding = parent.output_encoding563        self.module = parent.module564        self.encoding_errors = parent.encoding_errors565        self.format_exceptions = parent.format_exceptions566        self.error_handler = parent.error_handler567        self.include_error_handler = parent.include_error_handler568        self.enable_loop = parent.enable_loop569        self.lookup = parent.lookup570 571    def get_def(self, name):572        return self.parent.get_def(name)573 574 575class ModuleInfo:576 577    """Stores information about a module currently loaded into578    memory, provides reverse lookups of template source, module579    source code based on a module's identifier.580 581    """582 583    _modules = weakref.WeakValueDictionary()584 585    def __init__(586        self,587        module,588        module_filename,589        template,590        template_filename,591        module_source,592        template_source,593        template_uri,594    ):595        self.module = module596        self.module_filename = module_filename597        self.template_filename = template_filename598        self.module_source = module_source599        self.template_source = template_source600        self.template_uri = template_uri601        self._modules[module.__name__] = template._mmarker = self602        if module_filename:603            self._modules[module_filename] = self604 605    @classmethod606    def get_module_source_metadata(cls, module_source, full_line_map=False):607        source_map = re.search(608            r"__M_BEGIN_METADATA(.+?)__M_END_METADATA", module_source, re.S609        ).group(1)610        source_map = json.loads(source_map)611        source_map["line_map"] = {612            int(k): int(v) for k, v in source_map["line_map"].items()613        }614        if full_line_map:615            f_line_map = source_map["full_line_map"] = []616            line_map = source_map["line_map"]617 618            curr_templ_line = 1619            for mod_line in range(1, max(line_map)):620                if mod_line in line_map:621                    curr_templ_line = line_map[mod_line]622                f_line_map.append(curr_templ_line)623        return source_map624 625    @property626    def code(self):627        if self.module_source is not None:628            return self.module_source629        else:630            return util.read_python_file(self.module_filename)631 632    @property633    def source(self):634        if self.template_source is None:635            data = util.read_file(self.template_filename)636            if self.module._source_encoding:637                return data.decode(self.module._source_encoding)638            else:639                return data640 641        elif self.module._source_encoding and not isinstance(642            self.template_source, str643        ):644            return self.template_source.decode(self.module._source_encoding)645        else:646            return self.template_source647 648 649def _compile(template, text, filename, generate_magic_comment):650    lexer = template.lexer_cls(651        text,652        filename,653        input_encoding=template.input_encoding,654        preprocessor=template.preprocessor,655    )656    node = lexer.parse()657    source = codegen.compile(658        node,659        template.uri,660        filename,661        default_filters=template.default_filters,662        buffer_filters=template.buffer_filters,663        imports=template.imports,664        future_imports=template.future_imports,665        source_encoding=lexer.encoding,666        generate_magic_comment=generate_magic_comment,667        strict_undefined=template.strict_undefined,668        enable_loop=template.enable_loop,669        reserved_names=template.reserved_names,670    )671    return source, lexer672 673 674def _compile_text(template, text, filename):675    identifier = template.module_id676    source, lexer = _compile(677        template, text, filename, generate_magic_comment=False678    )679 680    cid = identifier681    module = types.ModuleType(cid)682    code = compile(source, cid, "exec")683 684    # this exec() works for 2.4->3.3.685    exec(code, module.__dict__, module.__dict__)686    return (source, module)687 688 689def _compile_module_file(template, text, filename, outputpath, module_writer):690    source, lexer = _compile(691        template, text, filename, generate_magic_comment=True692    )693 694    if isinstance(source, str):695        source = source.encode(lexer.encoding or "ascii")696 697    if module_writer:698        module_writer(source, outputpath)699    else:700        # make tempfiles in the same location as the ultimate701        # location.   this ensures they're on the same filesystem,702        # avoiding synchronization issues.703        (dest, name) = tempfile.mkstemp(dir=os.path.dirname(outputpath))704 705        os.write(dest, source)706        os.close(dest)707        shutil.move(name, outputpath)708 709 710def _get_module_info_from_callable(callable_):711    return _get_module_info(callable_.__globals__["__name__"])712 713 714def _get_module_info(filename):715    return ModuleInfo._modules[filename]716 
codekingpro/portable-devtools · Team Ai