Search code examples
pythonmako

Lookup escape filter method


Mako's Filtering and Buffering talks all about filters and how to use them. But is it possible to access the method used by a filter itself?

I want to properly escape a value in an attribute that will conditionally be rendered. Essentially I want to do this:

<element ${'attribute="${value | h}"' if value else ''}>

Which is obviously invalid because you can't nest expressions in strings. So I tried:

<element ${'attribute="{}"'.format(h(value)) if value else ''}>

However, this fails with a NameError: 'h' is not defined. I would like to avoid doing:

<element
    % if value:
        attribute="${value | h}"
    % endif
    >

Or:

<element ${'attribute="' if value else ''}${value or '' | h}${'"' if value else ''}>

I know I could use one of the multiple different methods available for escaping HTML attributes: cgi.escape(value, True), xml.sax.saxutils.quoteattr(value) (except it determines the quotes for you), or markupsafe.escape(value) (third-party). I would prefer to use the method used by Mako if at all possible. Is there a way to access Mako's build-in h using a lookup facility of some sort?


Solution

  • Short answer

    The h escape filter is mapped to mako.filters.html_escape(). E.g.,

    from mako.filters import html_escape as h
    

    Long answer

    The escape filters are mapped to the dotted names of functions in mako.filters.DEFAULT_ESCAPES:

    DEFAULT_ESCAPES = {
        'x': 'filters.xml_escape',
        'h': 'filters.html_escape',
        'u': 'filters.url_escape',
        'trim': 'filters.trim',
        'entity': 'filters.html_entities_escape',
        'unicode': 'unicode', # str in python 3
        'decode': 'decode',
        'str': 'str',
        'n': 'n'
    }
    

    The n and decode filters are special conditions and do not map to a specific function. The others are either built-in functions or functions from mako.filters. These can be evaluated in 2 ways:

    1. The potentially dangerous way:

      def lookup_escape(name):
          from mako import filters
          return eval(filters.DEFAULT_ESCAPES[name])
      
    2. The safer way:

      import functools
      try:
          import builtins
      except ImportError:
          import __builtin__ as builtins
      import mako.filters
      
      def lookup_escape(name):
          path_parts = mako.filters.DEFAULT_ESCAPES[name].split('.')
          for module in (mako, builtins):
              try:
                  return functools.reduce(getattr, path_parts, module)
              except AttributeError:
                  pass
          raise LookupError(name)
      

    Finally, the desired escape filter can be gotten with:

    h = lookup_escape('h')