`is gt` vs `>` Confusion

Just stumbled across a problem where I didn’t understand the solution, so I figured I’d post looking for understanding if any is to be had.

I have 2 dictionaries with a key who’s value is a list and I was looking to compare the number of items in each list to see which one is larger (they don’t get compared if there’s no difference) using |count which results in an integer… or so I thought.
Ya that’s a long sentance

But I hit an error using is gt, " TypeError: ‘>’ not supported between instances of ‘int’ and ‘list’ "

Here are my findings:

{{ (old.warnings|count is gt new.warnings|count)|iif }} # This throws the error

{{ ((old.warnings|count) is gt new.warnings|count)|iif }} # This throws the error

{{ (old.warnings|count is gt (new.warnings|count))|iif }} # This works

{{ (old.warnings|count > new.warnings|count)|iif }} # And this works, which surprised me

Both new.warnings|count|typeof and old.warnings|count|typeof return int and they are valid numbers that I can add and subtract etc. with reckless abandon.

So WTF don’t I understand about the comparison that makes the second part of this expression not work? And yes I reversed them to see what happens, it’s the second value that fails each time.

Any and all insight or direction greatly appreciated, thanks.

I’m curious, too. Is it because gt() is a function?

“is” is not valid Jinja - read through the linked docs in the previous post.

Oh the many hours I’ve spent there, I’m pretty sure that is is a perfectly valid for applying tests, just like not. How else would you use gt or greaterthan? If I remove the is they just throw “TemplateSyntaxError: expected token ‘)’, got ‘gt’”, it’s the only way they work for me… and until now this has been rock solid.

Both parameters being passed to the function validate as int, only the second parameter seems to ignore the type that count() produces. I don’t understand why the second parameter would be treated differently.

“is” is valid:
Tests

Beside filters, there are also so-called “tests” available. Tests can be used to test a variable against a common expression. To test a variable or expression, you add is plus the name of the test after the variable. For example, to find out if a variable is defined, you can do name is defined, which will then return true or false depending on whether name is defined in the current template context.

Tests can accept arguments, too. If the test only takes one argument, you can leave out the parentheses. For example, the following two expressions do the same thing:

{% if loop.index is divisibleby 3 %}
{% if loop.index is divisibleby(3) %}

Is this:

{{ ['one',2]|count is gt ['one','two']|count }} # TypeError: '>' not supported between instances of 'int' and 'list'

Just an implicit this?

{{ ['one',2]|count is gt(['one','two']) | count }}

Probably right.

I would make the same assumption as @HappyCadaver that “is gt” and “>” should be interchangable. But assumptions are what get you in trouble.

I wouldn’t expect a change at this point. I’m sure there are folks relying on the current behavior.

If you want to use “is gt”, add a couple parenthesis to force proper evaluation.

This works:

{{ ([1,2,3]|count) is gt ([1,2]|count) }}

Where and how did you test this?

Hmm, pretty sure it’s an order of evaluation issue (except for the case where gt is actually used as a function in case 3). If has to do with the count filters.

Got to go, but it can be confirmed like this (for all the variants):

{% set old = {'warnings': [1,1,1,1,1]} %}
{% set new = {'warnings': [1]} %}

{% set x = old.warnings|count %}
{% set y = new.warnings|count %}

{{ (x is gt y)|iif }}

Yup

Yes, is is valid jinja. It’s a keyword to start a test.

gt is a test. Listed under tests in the template documentation.

Tests can be used with is or in generator filters that accept tests.

The reason you’re getting errors in

{{ (old.warnings|count is gt new.warnings|count)|iif }} # This throws the error

{{ ((old.warnings|count) is gt new.warnings|count)|iif }} # This throws the error

is because you aren’t providing arguments to gt. It requires a single arugment. E.g.

y is gt(x)

this works

{{ (old.warnings|count is gt (new.warnings|count))|iif }} # This works

because you are passing an arugment, but it doesn’t look like you are because you put a space between the test and the first (, which is valid code but looks stupid.

Built in tests:

uses:

x is gt(y)

You cannot use gt as a function, it can only be used as a test. The only way to use tests inline is using is where the first argument is before is and the 2nd argument is in the function (if there is a second argument).

As a sidebar, I know y’all want to use iff but many of you are advanced users. You should avoid iff if you know how to write an inline if statement. When you use iff, both the if and else statements resolve, which can result in unwanted errors. If you use a proper in-line if statement, only the passing statement resolves.

e.g.

{{ x if x is defined else 0 }}

will not produce an error where

{{ iif(x is defined, x, 0) }}

will produce an error. See for yourself in the template editor. The 2nd one will produce a warning in the logs

I find the inline if more readable anyway. I avoid iff. I might have one use of it and it’s because Frenck actually wrote that snippet and I never changed it.

That makes sense, I didn’t see it as passing an argument initially because it looked too stupid to identify to me as such :grinning_face_with_smiling_eyes:.

My confusion came from the fact that I can do {{ 5 is gt 4 }} all day long without issue, so I figured if I’m using variables that resolve/render to integers that it would work.

Agreed, it’s one of the reasons I prefer gt over >, it just reads better to me. I started off using greaterthan originally but then lines got too long for my liking.

This is really more of a binding (or operator precedence) issue more than anything else.

You can pass an argument to a test without using parenthesis, the caveat is that the filter operator binds less tightly than the test.

{{ 2 is gt 1 }} is fine
{{ 2 is gt '1'|float }} will result in an error
{{ 2 is gt ('1'|float) }} forces the filter operator precedence and therefore evaluates as you’d expect.

And as a final piece of evidence:
{{ 2 is gt 1|typeof }} evaluates to bool (it is doing true|typeof)
{{ 2 > 1|typeof }} evaluates to an error (it is doing 2>int)

Edit: the difference between an operator and a test, in terms of operator precedence, is probably most dramatic with in which can be used both ways:
{{ 1 is in [1,2]|typeof }} renders as bool because in is a test and the test is performed first.
{{ 1 in [1,2]|typeof }} renders as an error because in is an operator and the filter operator gets precedence.