|
| 1 | +[Filters](tag_reference.md#filters) are usually implemented as simple Python functions. When rendered, Liquid will find the function in [`Environment.filters`](api/environment.md#liquid.Environment.filters), then call it, passing the input value as the first argument, followed by positional and keyword arguments given by the template author. The function's return value then becomes the result of the _filtered expression_. |
| 2 | + |
| 3 | +Filters can actually be any Python [callable](https://docs.python.org/3/glossary.html#term-callable). Implementing a filter as a class with a `__call__` method or as a closure can be useful if you want to configure the filter before registering it with an [`Environment`](environment.md). |
| 4 | + |
| 5 | +Also see the [filter helpers](api/filter.md) API documentation. |
| 6 | + |
| 7 | +!!! tip |
| 8 | + |
| 9 | + See [liquid/builtin/filters](https://github.com/jg-rp/liquid/tree/main/liquid/builtin/filters) for lots of examples. |
| 10 | + |
| 11 | +## Add a filter |
| 12 | + |
| 13 | +To add a filter, add an item to [`Environment.filters`](api/environment.md#liquid.Environment.filters). It's a regular dictionary mapping filter names to callables. |
| 14 | + |
| 15 | +In this example we add `ends_with`, a filter that delegates to Python's `str.endswith`. The `@string_filter` decorator coerces the input value to a string, if it is not one already. |
| 16 | + |
| 17 | +```python |
| 18 | +from liquid import Environment |
| 19 | +from liquid.filter import string_filter |
| 20 | + |
| 21 | + |
| 22 | +@string_filter |
| 23 | +def ends_with(left: str, val: str) -> bool: |
| 24 | + return left.endswith(val) |
| 25 | + |
| 26 | + |
| 27 | +env = Environment() |
| 28 | +env.filters["ends_with"] = ends_with |
| 29 | + |
| 30 | +source = """\ |
| 31 | +{% assign foo = "foobar" | ends_with: "bar" %} |
| 32 | +{% if foo %} |
| 33 | + do something |
| 34 | +{% endif %}""" |
| 35 | + |
| 36 | +template = env.from_string(source) |
| 37 | +print(template.render()) |
| 38 | +``` |
| 39 | + |
| 40 | +### With context |
| 41 | + |
| 42 | +Sometimes a filter will need access to the current [render context](render_context.md). Use the `@with_context` decorator to have an instance of [`RenderContext`](api/render_context.md) passed to your filter callable as a keyword argument named `context`. |
| 43 | + |
| 44 | +Here we use the render context to resolve a variable called "handle". |
| 45 | + |
| 46 | +```python |
| 47 | +from liquid import Environment |
| 48 | +from liquid.filter import string_filter |
| 49 | +from liquid.filter import with_context |
| 50 | + |
| 51 | + |
| 52 | +@string_filter |
| 53 | +@with_context |
| 54 | +def link_to_tag(label, tag, *, context): |
| 55 | + handle = context.resolve("handle", default="") |
| 56 | + return ( |
| 57 | + f'<a title="Show tag {tag}" href="/collections/{handle}/{tag}">{label}</a>' |
| 58 | + ) |
| 59 | + |
| 60 | +class MyEnvironment(Environment): |
| 61 | + def register_tags_and_filters(self): |
| 62 | + super().register_tags_and_filters() |
| 63 | + self.filters["link_to_tag"] = link_to_tag |
| 64 | + |
| 65 | +env = MyEnvironment() |
| 66 | +# ... |
| 67 | +``` |
| 68 | + |
| 69 | +### With environment |
| 70 | + |
| 71 | +Use the `@with_environment` decorator to have the current [`Environment`](api/environment.md) passed to your filter callable as a keyword argument named `environment`. |
| 72 | + |
| 73 | +```python |
| 74 | +import re |
| 75 | + |
| 76 | +from markupsafe import Markup |
| 77 | +from markupsafe import escape as markupsafe_escape |
| 78 | + |
| 79 | +from liquid import Environment |
| 80 | +from liquid.filter import string_filter |
| 81 | +from liquid.filter import with_environment |
| 82 | + |
| 83 | +RE_LINETERM = re.compile(r"\r?\n") |
| 84 | + |
| 85 | + |
| 86 | +@with_environment |
| 87 | +@string_filter |
| 88 | +def strip_newlines(val: str, *, environment: Environment) -> str: |
| 89 | + if environment.autoescape: |
| 90 | + val = markupsafe_escape(val) |
| 91 | + return Markup(RE_LINETERM.sub("", val)) |
| 92 | + return RE_LINETERM.sub("", val) |
| 93 | + |
| 94 | +# ... |
| 95 | +``` |
| 96 | + |
| 97 | +## Replace a filter |
| 98 | + |
| 99 | +To replace a default filter implementation with your own, simply update the [`filters`](api/environment.md#liquid.Environment.filters) dictionary on your Liquid [Environment](environment.md). |
| 100 | + |
| 101 | +Here we replace the default `slice` filter with one which uses start and stop values instead of start and length, and is a bit more forgiving in terms of allowed inputs. |
| 102 | + |
| 103 | +```python |
| 104 | +from liquid import Environment |
| 105 | +from liquid.filter import int_arg |
| 106 | +from liquid.filter import sequence_filter |
| 107 | + |
| 108 | +@sequence_filter |
| 109 | +def myslice(val, start, stop=None): |
| 110 | + start = int_arg(start) |
| 111 | + |
| 112 | + if stop is None: |
| 113 | + return val[start] |
| 114 | + |
| 115 | + stop = int_arg(stop) |
| 116 | + return val[start:stop] |
| 117 | + |
| 118 | + |
| 119 | +env = Environment() |
| 120 | +env.filters["slice"] = myslice |
| 121 | +# ... |
| 122 | +``` |
| 123 | + |
| 124 | +## Remove a filter |
| 125 | + |
| 126 | +Remove a built-in filter by deleting it from your [environment's](environment.md) [`filters`](api/environment.md#liquid.Environment.filters) dictionary. |
| 127 | + |
| 128 | +```python |
| 129 | +from liquid import Environment |
| 130 | + |
| 131 | +env = Environment() |
| 132 | +del env.filters["safe"] |
| 133 | + |
| 134 | +# ... |
| 135 | +``` |
| 136 | + |
| 137 | +!!! tip |
| 138 | + |
| 139 | + You can add, remove and replace filters on `liquid.DEFAULT_ENVIRONMENT` too. Convenience functions [`parse()`](api/convenience.md#liquid.parse) and [`render()`](api/convenience.md#liquid.render) use `DEFAULT_ENVIRONMENT` |
0 commit comments