Search code examples
pythonjinja2

Iterate over a list of lists, assert multiple conditions and render when true in Jinja2


I have the following variables to use in my Jinja template:

list_python_version = [3, 9]
all_python_version = [
  [3, 8],
  [3, 9],
  [3, 10],
  [3, 11],
  [3, 12]
]

Is there a way to use a combination of Jinja filters and tests so it iterates over all_python_version, checks that both the first and second elements of the list are greater or equal than the elements of list_python_version, and when the conditions are met, it generates the string for MAJOR.MINOR version and joins all that are valid in a single string?

This way, considering the variables above, the rendered template should give 3.9, 3.10, 3.11, 3.12?

I have tried the following expression:

{{
  all_python_version |
  select('[0] >= minimal_python_version[0] and [1] >= minimal_python_version[1]') |
  join('.') |
  join(', ')
}}

But it will fail since the select filter asks for a function, and so far I have not found in Jinja's documentation any hint as to how we can use conditionals to filter values inside an expression.

Alternatively, the solution could encompass a for loop in Jinja, but I need the string to be rendered in one line, and if we do:

{% for version in all_python_version %}
{% if version[0] >= list_python_version[0] and version[1] >= list_python_version[1] %}
{{ version[0] ~ '.' ~ version[1] ~ ',' }}
{% endif %}
{% endfor %}

Each version will render in its own line, with the last , also being rendered.

Is there a way to do get the versions to be rendered in a single line in pure Jinja and its plugins?


Solution

  • Conveniently enough, since Jinja allows you to access elements of a list via both list[0] and list.0, this means that you can actually use a dictionary filter on a list.

    And selectattr is just the filter we need here, since it allows to select items out of a list of dictionaries based on a property of those dictionaries.

    Still, we cannot properly fit a logical and in there, so we'll need to split the logic in two:

    • all Python versions where the major version is strictly equal to our comparison point and the minor version is greater or equal
    • all Python versions where the major version is greater than our comparison point, regardless of the minor version

    So, we end up with a quite lengthy:

    {{ 
      all_python_version 
        | selectattr(0, '==', list_python_version.0) 
        | selectattr(1, '>=', list_python_version.1) | list 
      + all_python_version | selectattr(0, '>', list_python_version.0) | list 
    }}
    

    The snippet

    {%- set list_python_version = [3, 9] -%}
    {%- set all_python_version = [
      [3, 8], [3, 9], [3, 10], [3, 11], [3, 12],
    ] -%}
    
    {{ 
      all_python_version 
        | selectattr(0, '==', list_python_version.0) 
        | selectattr(1, '>=', list_python_version.1) | list 
      + all_python_version | selectattr(0, '>', list_python_version.0) | list 
    }}
    

    Would yield:

    [[3, 9], [3, 10], [3, 11], [3, 12]]
    

    Then to join everything together, use a combination of map and join in the list of lists and a simple join in the resulting list:

    {{ 
      (
        all_python_version 
          | selectattr(0, '==', list_python_version.0) 
          | selectattr(1, '>=', list_python_version.1) | list 
        + all_python_version | selectattr(0, '>', list_python_version.0) | list
      ) | map('join', '.') | join(', ')
    }}
    

    Giving

    3.9, 3.10, 3.11, 3.12