Jinja2 sort filter and Python 3

1,171 views
Skip to first unread message

Sergey Baranov

unread,
May 8, 2017, 7:14:35 AM5/8/17
to Ansible Project


Hi everyone!

I'm using this jinja2 macro for xml configs:

{%- macro print_opts(options, indent='  ', count=0) %}
{% for key in options.keys() | sort2 %}
  {% set value = options[key] %}
  {% if value is string %}
    {{- indent*count }}{{ key }} {{ '"' + value.split('\t') | join('" "') + '"' }}
  {% elif value in [true, false] %}
    {{- indent*count }}{{ key }} {{ value | lower }}
  {% elif value is number %}
    {{- indent*count }}{{ key }} {{ value }}
  {% elif value is mapping %}
    {{- indent*count }}<{{ key }}>
     {{ print_opts(value, indent, count+1) -}}
    {{- indent*count }}</{{ key.split()[0] }}>
  {% elif value is sequence %}
    {% for i in value | sort2 -%}
      {{ print_opts({key: i}, indent, count) -}}
    {%- endfor %}
  {% else %}
    {{- indent*count }}{{ key }} "{{ value }}"
  {% endif %}
{% endfor %}
{% endmacro %}

This macro works fine with Python 2, but I need Python 3 support also. The problem is sort filter.
First, I've had the problem with dict sorting in Py3 (unorderable types: dict() < dict()) and created this custom sort filter:

from jinja2._compat import string_types
from operator import itemgetter

def environmentfilter(f):
    """Decorator for marking environment dependent filters.  The current
    :class:`Environment` is passed to the filter as first argument.
    """
    f.environmentfilter = True
    return f

@environmentfilter
def do_sort(environment, value, reverse=False, case_sensitive=False,
           attribute=None):

    if not case_sensitive:
        def sort_func(item):
            if isinstance(item, string_types):
                item = item.lower()
            return item
    else:
        sort_func = None

    if attribute is not None:
        getter = make_attrgetter(environment, attribute)
        def sort_func(item, processor=sort_func or (lambda x: x)):
            return processor(getter(item))

    print(type(value))
    if isinstance(value, list) and isinstance(value[0], dict):
        return sorted(value, key=lambda x:sorted(x.keys()), reverse=reverse)
    else:
        return sorted(value, key=sort_func, reverse=reverse)

class FilterModule(object):
    '''
    custom jinja2 sort filter
    '''

    def filters(self):
        return {
            'sort2': do_sort
        }

Here I am using different sorted key for list of dicts. But then I catch the next problem - list sorting.
With Python 2  print(type(value)) returns <type 'list'> and sorting works as expected
With Python 3 it returns <class 'dict_keys'> and I am getting different sorting per each ansible role running, config file always has changed state.

Please help me resolve this problem. Maybe someone knows a simpler solution to make this macro work on both Python versions.

Thanks
Reply all
Reply to author
Forward
0 new messages