Skip to main content

Known Issues

This page documents known compatibility issues between Python Liquid's default Environment and the reference implementation of Liquid, written in Ruby. We strive to be 100% compatible with the reference implementation. That is, given an equivalent render context, a template rendered with Python Liquid should produce the same output as when rendered with Ruby Liquid.

info

Python Liquid version 1.7.0 introduced liquid.future.Environment as an alternative to the default Environment. This alternative environment is intended to transition Python Liquid towards greater compatibility with Ruby Liquid, without changing template rendering behavior for existing Python Liquid users.

Some of the issues described below have been resolved with liquid.future.Environment. To use it, simply import Environment from liquid.future instead of liquid.

Coercing Strings to Integers Inside Filters

See issue #49

Many filters built in to Liquid will automatically convert a string representation of a number to an integer or float as needed. When converting integers, Ruby Liquid uses Ruby's String.to_i method, which will disregard trailing non-digit characters. In the following example, '7,42' is converted to 7

template:

{{ 3.14 | plus: '7,42' }}
{{ '123abcdef45' | plus: '1,,,,..!@qwerty' }}

output

10.14
124

Python Liquid currently falls back to 0 for any string that can't be converted to an integer in its entirety. As is the case in Ruby Liquid for strings without leading digits.

This does not apply to parsing of integer literals, only converting strings to integers (not floats) inside filters.

Comment Parsing

Python Liquid will raise a LiquidSyntaxError if it finds the string {% endcomment %} inside a comment block. Ruby Liquid, on the other hand, will successfully parse fully-formed nested comment blocks, but will fail to parse a comment block containing either a {% comment %} or {% endcomment %} on its own.

Counters

In Ruby Liquid, the built-in increment and decrement tags can, in some cases, mutate "global" context and keep named counters alive between renders. Although not difficult to implement, I can't quite bring myself to do it.

Cycle Arguments

Python Liquid will accept cycle arguments of any type, including identifiers to be resolved, this behavior is considered "unintended" or "undefined" in Ruby Liquid (see issue #1519). If you need interoperability between Python Liquid and Ruby Liquid, only use strings or numbers as arguments to cycle.

Cycle Groups

See issue #43

Fixed in version 1.7.0 with liquid.future.Environment.

When the cycle tag is given a name, Python Liquid will use that name and all other arguments to distinguish one cycle from another. Ruby Liquid will disregard all other arguments when given a name. For example.

{% cycle a: 1, 2, 3 %}
{% cycle a: "x", "y", "z" %}
{% cycle a: 1, 2, 3 %}

Ruby Liquid Output:

1
y
3

Python Liquid Output:

1
x
2

The Date Filter

The built-in date filter uses dateutil for parsing strings to datetimes, and strftime for formatting. There are likely to be some inconsistencies between this and the reference implementation's equivalent parsing and formatting of dates and times.

Error Handling

Python Liquid might not handle syntax or type errors in the same way as the reference implementation. We might fail earlier or later, and will almost certainly produce a different error message.

Python Liquid does not have a "lax" parser, like Ruby Liquid. Upon finding an error, Python Liquid's lax mode simply discards the current block and continues to parse/render the next block, if one is available. Also, Python Liquid will never inject error messages into an output document. Although this can be achieved by extending BoundTemplate and overriding render_with_context().

Extra Else Blocks

Fixed in version 1.12.1 with liquid.future.Environment.

Shopify/Liquid will silently ignore superfluous {% else %} tag expressions (anything between else and the closing tag delimiter %}) and superfluous {% elsif %} and {% else %} blocks after the first {% else %} block.

Python Liquid's default behavior is to raise a LiquidSyntaxError in such cases.

Floats in Ranges

If a range literal uses a float literal as its start or stop value, the float literal must have something after the decimal point. This is OK (1.0..3). This is not (1...3). Ruby Liquid will accept either, resulting in a sequence of [1,2,3].

The Split Filter

See issue #134

Fixed in version 1.10.2 with liquid.future.Environment.

When given an empty string to split or when the string and the delimiter are equal, we used Python's str.split() behavior of producing one or two element lists containing empty strings. Shopify/Liquid returns an empty list/array in such cases.

Indexable Strings

See issue #90

Fixed in version 1.7.0 with liquid.future.Environment.

The reference implementation does not allow us to access characters in a string by their index. Python Liquid does.

Template

{% assign x = 'foobar' -%}
{{ x[0] }}
{{ x[1] }}
{{ x[-1] }}

Python Liquid output

f
o
r

Shopify/liquid will throw an error (in strict mode) for each attempt at accessing a character by its index.

<Liquid::UndefinedVariable: Liquid error: undefined variable 0>
<Liquid::UndefinedVariable: Liquid error: undefined variable 1>
<Liquid::UndefinedVariable: Liquid error: undefined variable -1>

Iterating Strings

See issue #102

Fixed in version 1.8.0 with liquid.future.Environment.

When looping over strings with the {% for %} tag, the reference implementation of Liquid will iterate over a one element array, where the first and only element is the string. Python Liquid will iterate through characters in the string.

Template:

{% assign foo = 'hello world' %}
{% for x in foo %}{{ x }} / {% endfor %}

Ruby Liquid Output:

hello world /

Python Liquid Output:

h / e / l / l / o /   / w / o / r / l / d /

It appears that this is unintended behavior for Ruby Liquid. Previously, Ruby Liquid would iterate over lines in a string, also not intended behavior. See https://github.com/Shopify/liquid/pull/1667.

Summing Floats

See Shopify/Liquid#1725

When given one or more floats as input, the reference implementation's standard sum filter will return a BigDecimal, which is rendered in scientific notation (or similar). Python Liquid will coerce the result to a float, and render that, without an exponent.

Template:

{% assign a = "0.1,0.2,0.3" | split: "," %}
{{ a | sum }}

Ruby Liquid Output:

0.6e0

Python Liquid Output:

0.6