3

1. Summary

I can’t find, what is the correct syntax in Jinja2 for expressions like if any() in object/if all() in object.

2. MCVE

  • Live demo on Repl.it:

    """Jinja2 if any() in object MCVE."""
    from jinja2 import Template
    
    KIRA_BLOCK = """
    {% if 14 in range(1,5) or 4 in range(1,5) or 7 in range(1,5) %}
        Kira Goddess!
    {% endif %}
    """
    
    print(Template(KIRA_BLOCK).render())
    
    

How can I get the same result without duplicate code?

3. Not helped

  1. I tried searching for the answer to my question and similar examples in Google, GitHub and Jinja documentation

  2. I didn’t understand how can I use Jinja built-in filters to solve my problem

  3. I tried select() filter, as recommended in this answer on Stack Overflow. Like this:

    KIRA_BLOCK = """
    {% if (14,4,7)|select|first in range(1,5) %}
        Kira Goddess!
    {% endif %}
    """
    
  4. I tried to register a custom any() filter, as recommended in the same answer on Stack Overflow. Like this:

    """Jinja2 if any() in object MCVE."""
    from jinja2 import Environment
    from jinja2 import Template
    
    
    environment = Environment()
    
    environment.filters["any"] = any
    
    KIRA_BLOCK = """
    {% if (14,4,7)|any in range(1,5) %}
        Kira Goddess!
    {% endif %}
    """
    
    
    print(Template(KIRA_BLOCK).render())
    
    

4. Real example

For preventing XY problem:

I use similar templates in static site generator Pelican. Example:

{# [INFO] If any of the classes "attention", "caution" or "warning" exists in my article, I add specific styles #}
{% if "class=\"attenion\"" in article.content or if "class=\"caution\"" in article.content or if "class=\"warning\"" in article.content %}
    <link rel="preload" href="path/to/css/third-party/Admonition/admonition-warning.min.css" as="style" onload="this.rel='stylesheet'">

{# [INFO] Elif all classes "faq", "help" and "question" exists in my article, I add another styles #}
{% elif "class=\"faq\"" in article.content and if "class=\"help\"" in article.content and if "class=\"question\"" in article.content %}
    <link rel="preload" href="path/to/css/third-party/Admonition/admonition-question.min.css" as="style" onload="this.rel='stylesheet'">
{% endif %}

I don’t understand how to remove hard coding from these templates.

Thanks.

Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
Саша Черных
  • 2,561
  • 4
  • 25
  • 71
  • I don't know jinja, but in Python the normal syntax would be `if any(value in range(1,5) for value in (14, 4, 7))` – Peter Wood Sep 10 '22 at 09:20
  • @PeterWood I would say the normal way to do a range check in Python would be `1 <= value < 5` rather than `value in range(1, 5)`. I would expect the former to be more efficient. – Tom Karzes Sep 10 '22 at 10:45
  • @TomKarzes that’s not what was proposed nor what the question is about, the question is to find if all elements of a list are present in another list. – β.εηοιτ.βε Sep 10 '22 at 11:42
  • @TomKarzes it wouldn't be much more efficient. – Peter Wood Sep 10 '22 at 13:10
  • @PeterWood Wrong. I used `timeit` to compare the two. The normal comparison is over 4 times faster. That's *much* more efficient. You should be able to reason this out. One is a pair of direct relational comparisons. The other is a call to a range object. The latter is more general and has a significant amount of overhead, so of course it's significantly slower, as I've shown. – Tom Karzes Sep 10 '22 at 13:32
  • @PeterWood You can try it for yourself. I did: `timeit.timeit("x=3; x in range(1, 5)", number=1000000)` vs. `timeit.timeit("x=3; 1 <= x < 5", number=1000000)`. The former took `4.6` times as long as the latter. – Tom Karzes Sep 10 '22 at 13:36
  • @TomKarzes I expect the creation of the range object has overhead. The main point of my comment was to be as similar as possible to OP's code, and the efficiency isn't that much worse. 10 times worse would be bad in my book, but 4 times... it's not worth complicating the example for OP. – Peter Wood Sep 11 '22 at 07:34

1 Answers1

4

I can’t find, what is the correct syntax in Jinja2 for expressions like if any() in object/if any() in object.

That's not how the any() function works in Python. You might write something like this:

if any(value == 4 for value in mylist):
    ...

The any() function, as shown in the documentation, is equivalent to:

def any(iterable):
    for element in iterable:
        if element:
            return True
    return False

The above unfortunately doesn't translate well into Jinja, because Jinja doesn't support list/tuple comprehensions, without which the any() function is much less useful.


Looking at what you're actually trying to do:

{#
   [INFO] If any of the classes "attention", "caution" or
  "warning" exists in my article, I add specific styles
#}
{% if "class=\"attenion\"" in article.content
     or if "class=\"caution\"" in article.content 
      or if "class=\"warning\"" in article.content %}
    <link rel="preload" href="path/to/css/third-party/Admonition/admonition-warning.min.css" as="style" onload="this.rel='stylesheet'">
{% endif %}

You could register a custom has_class filter that looks like this:

def has_class(content, classnames):
    return any(f'class="{name}"' in content for name in classnames)

And use it like this:

{#
   [INFO] If any of the classes "attention", "caution" or
  "warning" exists in my article, I add specific styles
#}
{% if article.content|has_class(['attention', 'caution', 'warning']) %}
    <link rel="preload" href="path/to/css/third-party/Admonition/admonition-warning.min.css" as="style" onload="this.rel='stylesheet'">
{% endif %}
larsks
  • 277,717
  • 41
  • 399
  • 399