From 7f7ad191f3613dc7d5c87a5afb826db1b95588bd Mon Sep 17 00:00:00 2001 From: James Prior Date: Wed, 9 Apr 2025 08:32:04 +0100 Subject: [PATCH 1/2] Fix and test find filters --- CHANGELOG.md | 4 + liquid2/ast.py | 1 - liquid2/builtin/filters/find_filters.py | 54 +- liquid2/exceptions.py | 2 +- tests/liquid2-compliance-test-suite/cts.json | 569 ++++++++++++++++++ .../tests/filters/find.json | 129 ++++ .../tests/filters/find_index.json | 143 +++++ .../tests/filters/has.json | 235 ++++++++ 8 files changed, 1113 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e70ab5..b6a8558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Version 0.4.0 (unreleased) +**Fixes** + +- Fixed some corner cases with `find`, `find_index` and `has` filters. + **Features** - Added the `shorthand_indexes` class variable to `liquid2.Environment`. When `shorthand_indexes` is set to `True` (the default is `False`), array indexes in variable paths need not be surrounded by square brackets. diff --git a/liquid2/ast.py b/liquid2/ast.py index a0fa9d3..d95739e 100644 --- a/liquid2/ast.py +++ b/liquid2/ast.py @@ -33,7 +33,6 @@ class Node(ABC): __slots__ = ("token", "blank") def __init__(self, token: TokenT) -> None: - super().__init__() self.token = token self.blank = True diff --git a/liquid2/builtin/filters/find_filters.py b/liquid2/builtin/filters/find_filters.py index d813689..029ec75 100644 --- a/liquid2/builtin/filters/find_filters.py +++ b/liquid2/builtin/filters/find_filters.py @@ -21,19 +21,22 @@ from liquid2.builtin import KeywordArgument -def _getitem(obj: Any, key: object, default: object = None) -> Any: +def _getitem(sequence: Any, key: object, default: object = None) -> Any: """Helper for the `find` filter. - Same as obj[key], but returns a default value if key does not exist - in obj. + Same as sequence[key], but returns a default value if key does not exist + in sequence, and handles some corner cases so as to mimic Shopify/Liquid + behavior. """ try: - return getitem(obj, key) + return getitem(sequence, key) except (KeyError, IndexError): return default except TypeError: - if not hasattr(obj, "__getitem__"): - raise + if isinstance(sequence, str) and isinstance(key, str) and key in sequence: + return key + if isinstance(sequence, int) and isinstance(key, int): + return sequence == key return default @@ -81,14 +84,13 @@ def __call__( return item elif value is not None and not is_undefined(value): - for item in left: - if _getitem(item, key) == value: - return item + return next((itm for itm in left if _getitem(itm, key) == value), None) else: - for item in left: - if item not in (False, None): - return item + return next( + (itm for itm in left if _getitem(itm, key) not in (False, None)), + None, + ) return None @@ -114,14 +116,20 @@ def __call__( return i elif value is not None and not is_undefined(value): - for i, item in enumerate(left): - if _getitem(item, key) == value: - return i + return next( + (i for i, itm in enumerate(left) if _getitem(itm, key) == value), + None, + ) else: - for i, item in enumerate(left): - if item not in (False, None): - return i + return next( + ( + i + for i, itm in enumerate(left) + if _getitem(itm, key) not in (False, None) + ), + None, + ) return None @@ -146,11 +154,13 @@ def __call__( return True elif value is not None and not is_undefined(value): - for item in left: - if _getitem(item, key) == value: - return True + return any( + (itm for itm in left if _getitem(itm, key) == value), + ) else: - return any(item not in (False, None) for item in left) + return any( + (itm for itm in left if _getitem(itm, key) not in (False, None)), + ) return False diff --git a/liquid2/exceptions.py b/liquid2/exceptions.py index f20603b..8f565d4 100644 --- a/liquid2/exceptions.py +++ b/liquid2/exceptions.py @@ -172,7 +172,7 @@ class DisabledTagError(LiquidError): """Exception raised when an attempt is made to render a disabled tag.""" -class UnknownFilterError(LiquidError): # noqa: N818 +class UnknownFilterError(LiquidError): """Exception raised when a filter lookup fails.""" diff --git a/tests/liquid2-compliance-test-suite/cts.json b/tests/liquid2-compliance-test-suite/cts.json index 34af6de..6cd46dc 100644 --- a/tests/liquid2-compliance-test-suite/cts.json +++ b/tests/liquid2-compliance-test-suite/cts.json @@ -1836,6 +1836,153 @@ }, "result": "not found" }, + { + "name": "filters, find, array of strings, default value", + "template": "{{ a | find: 'z' }}", + "data": { + "a": [ + "x", + "y", + "z" + ] + }, + "result": "z" + }, + { + "name": "filters, find, array of strings, substring match, default value", + "template": "{{ a | find: 'oo' }}", + "data": { + "a": [ + "x", + "y", + "zoo" + ] + }, + "result": "zoo" + }, + { + "name": "filters, find, array of strings, default value, no match", + "template": "{{ a | find: 'foo' }}", + "data": { + "a": [ + "x", + "y", + "zoo" + ] + }, + "result": "" + }, + { + "name": "filters, find, mixed array, default value", + "template": "{{ a | find: 'z' }}", + "data": { + "a": [ + "x", + null, + "z", + false, + true + ] + }, + "result": "z" + }, + { + "name": "filters, find, string input, default value, match", + "template": "{{ a | find: 'z' }}", + "data": { + "a": "zoo" + }, + "result": "z" + }, + { + "name": "filters, find, string input, string value, match", + "template": "{{ a | find: 'z', 'z' }}", + "data": { + "a": "zoo" + }, + "result": "z" + }, + { + "name": "filters, find, string input, string value, no match", + "template": "{{ a | find: 'z', 'y' }}", + "data": { + "a": "zoo" + }, + "result": "" + }, + { + "name": "filters, find, hash input, default value, match", + "template": "{% assign b = a | find: 'z' %}{{ b.z }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "42" + }, + { + "name": "filters, find, hash input, default value, no match", + "template": "{% assign b = a | find: 'foo' %}{{ b }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "" + }, + { + "name": "filters, find, hash input, int value, match", + "template": "{% assign b = a | find: 'z', 42 %}{{ b.z }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "42" + }, + { + "name": "filters, find, hash input, explicit nil, match", + "template": "{% assign b = a | find: 'z', nil %}{{ b.z }}", + "data": { + "a": { + "z": null + } + }, + "result": "" + }, + { + "name": "filters, find, array of hashes, int value, match", + "template": "{% assign b = a | find: 'z', 42 %}{{ b.foo }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42, + "foo": "bar" + } + ] + }, + "result": "bar" + }, + { + "name": "filters, find, array of hashes, with a nil", + "template": "{% assign b = a | find: 'z', 42 %}{{ b.foo }}", + "data": { + "a": [ + { + "x": 99 + }, + null, + { + "z": 42, + "foo": "bar" + } + ] + }, + "result": "bar" + }, { "name": "filters, find index, array of objects", "template": "{% assign x = a | find_index: 'title', 'bar' %}{{ x }}", @@ -1908,6 +2055,167 @@ }, "result": "not found" }, + { + "name": "filters, find index, array of strings, default value", + "template": "{{ a | find_index: 'z' }}", + "data": { + "a": [ + "x", + "y", + "z" + ] + }, + "result": "2" + }, + { + "name": "filters, find index, array of strings, substring match, default value", + "template": "{{ a | find_index: 'oo' }}", + "data": { + "a": [ + "x", + "y", + "zoo" + ] + }, + "result": "2" + }, + { + "name": "filters, find index, array of strings, default value, no match", + "template": "{{ a | find_index: 'foo' }}", + "data": { + "a": [ + "x", + "y", + "zoo" + ] + }, + "result": "" + }, + { + "name": "filters, find index, mixed array, default value", + "template": "{{ a | find_index: 'z' }}", + "data": { + "a": [ + "x", + null, + "z", + false, + true + ] + }, + "result": "2" + }, + { + "name": "filters, find index, string input, default value, match", + "template": "{{ a | find_index: 'z' }}", + "data": { + "a": "zoo" + }, + "result": "0" + }, + { + "name": "filters, find index, string input, string value, match", + "template": "{{ a | find_index: 'z', 'z' }}", + "data": { + "a": "zoo" + }, + "result": "0" + }, + { + "name": "filters, find index, string input, string value, no match", + "template": "{{ a | find_index: 'z', 'y' }}", + "data": { + "a": "zoo" + }, + "result": "" + }, + { + "name": "filters, find index, hash input, default value, match", + "template": "{{ a | find_index: 'z' }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "0" + }, + { + "name": "filters, find index, hash input, default value, no match", + "template": "{{ a | find_index: 'foo' }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "" + }, + { + "name": "filters, find index, hash input, int value, match", + "template": "{{ a | find_index: 'z', 42 }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "0" + }, + { + "name": "filters, find index, hash input, explicit nil, match", + "template": "{{ a | find_index: 'z', nil }}", + "data": { + "a": { + "z": null + } + }, + "result": "" + }, + { + "name": "filters, find index, array of hashes, explicit nil, match", + "template": "{{ a | find_index: 'z', nil }}", + "data": { + "a": [ + "foo", + "bar", + { + "z": null + } + ] + }, + "result": "" + }, + { + "name": "filters, find index, array of hashes, int value, match", + "template": "{{ a | find_index: 'z', 42 }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42, + "foo": "bar" + } + ] + }, + "result": "1" + }, + { + "name": "filters, find index, array of hashes, with a nil", + "template": "{% assign b = a | find_index: 'z', 42 %}{{ a[b].foo }}", + "data": { + "a": [ + { + "x": 99 + }, + null, + { + "z": 42, + "foo": "bar" + } + ] + }, + "result": "bar" + }, { "name": "filters, first, range literal first filter left value", "template": "{{ (1..3) | first }}", @@ -2118,6 +2426,267 @@ }, "result": "false" }, + { + "name": "filters, has, array of strings, default value", + "template": "{{ a | has: 'z' }}", + "data": { + "a": [ + "x", + "y", + "z" + ] + }, + "result": "true" + }, + { + "name": "filters, has, array of strings, default value, substring match", + "template": "{{ a | has: 'z' }}", + "data": { + "a": [ + "x", + "y", + "zoo" + ] + }, + "result": "true" + }, + { + "name": "filters, has, array of strings, default value, no match", + "template": "{{ a | has: ':(' }}", + "data": { + "a": [ + "x", + "y", + "z" + ] + }, + "result": "false" + }, + { + "name": "filters, has, array of ints, default value", + "template": "{{ a | has: 2 }}", + "data": { + "a": [ + 1, + 2, + 3 + ] + }, + "result": "true" + }, + { + "name": "filters, has, array of ints, string argument, default value", + "template": "{{ a | has: '2' }}", + "data": { + "a": [ + 1, + 2, + 3 + ] + }, + "result": "false" + }, + { + "name": "filters, has, mixed array, default value", + "template": "{{ a | has: 'z' }}", + "data": { + "a": [ + "x", + null, + "z", + false, + true + ] + }, + "result": "true" + }, + { + "name": "filters, has, string input, default value, match", + "template": "{{ a | has: 'z' }}", + "data": { + "a": "zoo" + }, + "result": "true" + }, + { + "name": "filters, has, string input, default value, no match", + "template": "{{ a | has: 'z' }}", + "data": { + "a": "foo" + }, + "result": "false" + }, + { + "name": "filters, has, hash input, default value, match", + "template": "{{ a | has: 'z' }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "true" + }, + { + "name": "filters, has, hash input, default value, no match", + "template": "{{ a | has: 'z' }}", + "data": { + "a": { + "x": 42 + } + }, + "result": "false" + }, + { + "name": "filters, has, hash input, int value, match", + "template": "{{ a | has: 'z', 42 }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "true" + }, + { + "name": "filters, has, hash input, int value, no match", + "template": "{{ a | has: 'z', 99 }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "false" + }, + { + "name": "filters, has, hash input, string value, no type coercion", + "template": "{{ a | has: 'z', '42' }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "false" + }, + { + "name": "filters, has, hash input, explicit nil, no match", + "template": "{{ a | has: 'z', nil }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "true" + }, + { + "name": "filters, has, hash input, explicit nil, match", + "template": "{{ a | has: 'z', nil }}", + "data": { + "a": { + "z": null + } + }, + "result": "false" + }, + { + "name": "filters, has, hash input, false value, match", + "template": "{{ a | has: 'z', false }}", + "data": { + "a": { + "z": false + } + }, + "result": "true" + }, + { + "name": "filters, has, array of hashes, int value, match", + "template": "{{ a | has: 'z', 42 }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42 + } + ] + }, + "result": "true" + }, + { + "name": "filters, has, array of hashes, int value, no match", + "template": "{{ a | has: 'z', 7 }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42 + } + ] + }, + "result": "false" + }, + { + "name": "filters, has, array of hashes, with a nil", + "template": "{{ a | has: 'z', 42 }}", + "data": { + "a": [ + { + "x": 99 + }, + null, + { + "z": 42 + } + ] + }, + "result": "true" + }, + { + "name": "filters, has, array of hashes, nil property", + "template": "{{ a | has: nil }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42 + } + ] + }, + "result": "false" + }, + { + "name": "filters, has, array of hashes, int property", + "template": "{{ a | has: 42 }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42 + } + ] + }, + "result": "false" + }, + { + "name": "filters, has, array of hashes, false property", + "template": "{{ a | has: false }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42 + } + ] + }, + "result": "false" + }, { "name": "filters, join, range literal join filter left value", "template": "{{ (1..3) | join: '#' }}", diff --git a/tests/liquid2-compliance-test-suite/tests/filters/find.json b/tests/liquid2-compliance-test-suite/tests/filters/find.json index 8f1609d..6002035 100644 --- a/tests/liquid2-compliance-test-suite/tests/filters/find.json +++ b/tests/liquid2-compliance-test-suite/tests/filters/find.json @@ -68,6 +68,135 @@ ] }, "result": "not found" + }, + { + "name": "array of strings, default value", + "template": "{{ a | find: 'z' }}", + "data": { + "a": ["x", "y", "z"] + }, + "result": "z" + }, + { + "name": "array of strings, substring match, default value", + "template": "{{ a | find: 'oo' }}", + "data": { + "a": ["x", "y", "zoo"] + }, + "result": "zoo" + }, + { + "name": "array of strings, default value, no match", + "template": "{{ a | find: 'foo' }}", + "data": { + "a": ["x", "y", "zoo"] + }, + "result": "" + }, + { + "name": "mixed array, default value", + "template": "{{ a | find: 'z' }}", + "data": { + "a": ["x", null, "z", false, true] + }, + "result": "z" + }, + { + "name": "string input, default value, match", + "template": "{{ a | find: 'z' }}", + "data": { + "a": "zoo" + }, + "result": "z" + }, + { + "name": "string input, string value, match", + "template": "{{ a | find: 'z', 'z' }}", + "data": { + "a": "zoo" + }, + "result": "z" + }, + { + "name": "string input, string value, no match", + "template": "{{ a | find: 'z', 'y' }}", + "data": { + "a": "zoo" + }, + "result": "" + }, + { + "name": "hash input, default value, match", + "template": "{% assign b = a | find: 'z' %}{{ b.z }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "42" + }, + { + "name": "hash input, default value, no match", + "template": "{% assign b = a | find: 'foo' %}{{ b }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "" + }, + { + "name": "hash input, int value, match", + "template": "{% assign b = a | find: 'z', 42 %}{{ b.z }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "42" + }, + { + "name": "hash input, explicit nil, match", + "template": "{% assign b = a | find: 'z', nil %}{{ b.z }}", + "data": { + "a": { + "z": null + } + }, + "result": "" + }, + { + "name": "array of hashes, int value, match", + "template": "{% assign b = a | find: 'z', 42 %}{{ b.foo }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42, + "foo": "bar" + } + ] + }, + "result": "bar" + }, + { + "name": "array of hashes, with a nil", + "template": "{% assign b = a | find: 'z', 42 %}{{ b.foo }}", + "data": { + "a": [ + { + "x": 99 + }, + null, + { + "z": 42, + "foo": "bar" + } + ] + }, + "result": "bar" } ] } diff --git a/tests/liquid2-compliance-test-suite/tests/filters/find_index.json b/tests/liquid2-compliance-test-suite/tests/filters/find_index.json index 5408b76..17c32a1 100644 --- a/tests/liquid2-compliance-test-suite/tests/filters/find_index.json +++ b/tests/liquid2-compliance-test-suite/tests/filters/find_index.json @@ -71,6 +71,149 @@ ] }, "result": "not found" + }, + { + "name": "array of strings, default value", + "template": "{{ a | find_index: 'z' }}", + "data": { + "a": ["x", "y", "z"] + }, + "result": "2" + }, + { + "name": "array of strings, substring match, default value", + "template": "{{ a | find_index: 'oo' }}", + "data": { + "a": ["x", "y", "zoo"] + }, + "result": "2" + }, + { + "name": "array of strings, default value, no match", + "template": "{{ a | find_index: 'foo' }}", + "data": { + "a": ["x", "y", "zoo"] + }, + "result": "" + }, + { + "name": "mixed array, default value", + "template": "{{ a | find_index: 'z' }}", + "data": { + "a": ["x", null, "z", false, true] + }, + "result": "2" + }, + { + "name": "string input, default value, match", + "template": "{{ a | find_index: 'z' }}", + "data": { + "a": "zoo" + }, + "result": "0" + }, + { + "name": "string input, string value, match", + "template": "{{ a | find_index: 'z', 'z' }}", + "data": { + "a": "zoo" + }, + "result": "0" + }, + { + "name": "string input, string value, no match", + "template": "{{ a | find_index: 'z', 'y' }}", + "data": { + "a": "zoo" + }, + "result": "" + }, + { + "name": "hash input, default value, match", + "template": "{{ a | find_index: 'z' }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "0" + }, + { + "name": "hash input, default value, no match", + "template": "{{ a | find_index: 'foo' }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "" + }, + { + "name": "hash input, int value, match", + "template": "{{ a | find_index: 'z', 42 }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "0" + }, + { + "name": "hash input, explicit nil, match", + "template": "{{ a | find_index: 'z', nil }}", + "data": { + "a": { + "z": null + } + }, + "result": "" + }, + { + "name": "array of hashes, explicit nil, match", + "template": "{{ a | find_index: 'z', nil }}", + "data": { + "a": [ + "foo", + "bar", + { + "z": null + } + ] + }, + "result": "" + }, + { + "name": "array of hashes, int value, match", + "template": "{{ a | find_index: 'z', 42 }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42, + "foo": "bar" + } + ] + }, + "result": "1" + }, + { + "name": "array of hashes, with a nil", + "template": "{% assign b = a | find_index: 'z', 42 %}{{ a[b].foo }}", + "data": { + "a": [ + { + "x": 99 + }, + null, + { + "z": 42, + "foo": "bar" + } + ] + }, + "result": "bar" } ] } diff --git a/tests/liquid2-compliance-test-suite/tests/filters/has.json b/tests/liquid2-compliance-test-suite/tests/filters/has.json index 4ba01f2..51f6c47 100644 --- a/tests/liquid2-compliance-test-suite/tests/filters/has.json +++ b/tests/liquid2-compliance-test-suite/tests/filters/has.json @@ -71,6 +71,241 @@ ] }, "result": "false" + }, + { + "name": "array of strings, default value", + "template": "{{ a | has: 'z' }}", + "data": { + "a": ["x", "y", "z"] + }, + "result": "true" + }, + { + "name": "array of strings, default value, substring match", + "template": "{{ a | has: 'z' }}", + "data": { + "a": ["x", "y", "zoo"] + }, + "result": "true" + }, + { + "name": "array of strings, default value, no match", + "template": "{{ a | has: ':(' }}", + "data": { + "a": ["x", "y", "z"] + }, + "result": "false" + }, + { + "name": "array of ints, default value", + "template": "{{ a | has: 2 }}", + "data": { + "a": [1, 2, 3] + }, + "result": "true" + }, + { + "name": "array of ints, string argument, default value", + "template": "{{ a | has: '2' }}", + "data": { + "a": [1, 2, 3] + }, + "result": "false" + }, + { + "name": "mixed array, default value", + "template": "{{ a | has: 'z' }}", + "data": { + "a": ["x", null, "z", false, true] + }, + "result": "true" + }, + { + "name": "string input, default value, match", + "template": "{{ a | has: 'z' }}", + "data": { + "a": "zoo" + }, + "result": "true" + }, + { + "name": "string input, default value, no match", + "template": "{{ a | has: 'z' }}", + "data": { + "a": "foo" + }, + "result": "false" + }, + { + "name": "hash input, default value, match", + "template": "{{ a | has: 'z' }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "true" + }, + { + "name": "hash input, default value, no match", + "template": "{{ a | has: 'z' }}", + "data": { + "a": { + "x": 42 + } + }, + "result": "false" + }, + { + "name": "hash input, int value, match", + "template": "{{ a | has: 'z', 42 }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "true" + }, + { + "name": "hash input, int value, no match", + "template": "{{ a | has: 'z', 99 }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "false" + }, + { + "name": "hash input, string value, no type coercion", + "template": "{{ a | has: 'z', '42' }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "false" + }, + { + "name": "hash input, explicit nil, no match", + "template": "{{ a | has: 'z', nil }}", + "data": { + "a": { + "z": 42 + } + }, + "result": "true" + }, + { + "name": "hash input, explicit nil, match", + "template": "{{ a | has: 'z', nil }}", + "data": { + "a": { + "z": null + } + }, + "result": "false" + }, + { + "name": "hash input, false value, match", + "template": "{{ a | has: 'z', false }}", + "data": { + "a": { + "z": false + } + }, + "result": "true" + }, + { + "name": "array of hashes, int value, match", + "template": "{{ a | has: 'z', 42 }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42 + } + ] + }, + "result": "true" + }, + { + "name": "array of hashes, int value, no match", + "template": "{{ a | has: 'z', 7 }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42 + } + ] + }, + "result": "false" + }, + { + "name": "array of hashes, with a nil", + "template": "{{ a | has: 'z', 42 }}", + "data": { + "a": [ + { + "x": 99 + }, + null, + { + "z": 42 + } + ] + }, + "result": "true" + }, + { + "name": "array of hashes, nil property", + "template": "{{ a | has: nil }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42 + } + ] + }, + "result": "false" + }, + { + "name": "array of hashes, int property", + "template": "{{ a | has: 42 }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42 + } + ] + }, + "result": "false" + }, + { + "name": "array of hashes, false property", + "template": "{{ a | has: false }}", + "data": { + "a": [ + { + "x": 99 + }, + { + "z": 42 + } + ] + }, + "result": "false" } ] } From f7fbe1fca1f9f753a6be67a5808b19a0798876af Mon Sep 17 00:00:00 2001 From: James Prior Date: Wed, 9 Apr 2025 15:38:07 +0100 Subject: [PATCH 2/2] Remove unnecessary zip --- liquid2/builtin/filters/find_filters.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/liquid2/builtin/filters/find_filters.py b/liquid2/builtin/filters/find_filters.py index 029ec75..487b854 100644 --- a/liquid2/builtin/filters/find_filters.py +++ b/liquid2/builtin/filters/find_filters.py @@ -110,8 +110,7 @@ def __call__( left = sequence_arg(left) if isinstance(key, LambdaExpression): - for i, pair in enumerate(zip(left, key.map(context, left), strict=True)): - item, rv = pair + for i, rv in enumerate(key.map(context, left)): if not is_undefined(rv) and is_truthy(rv): return i