- Added the
squishfilter.{{ x | squish }}is equivalent to{{ x | strip | split | join }}. See #195. - Added
BoundTemplate.comments()andBoundTemplate.docs()for statically retrieving{% comment %},{% # inline comment %}and{% doc %}nodes. - Added an experimental
{% snippet %}tag. Shopify/liquid released then quickly removed{% snippet %}. We're calling it "experimental" and keeping it disabled by default, pending more activity from Shopify/liquid. See #191 and #193. - Improved static analysis of partial templates. Previously we would visit a partial template only once, regardless of how many times it is rendered with
{% render %}. Now we visit partial templates once for each distinct set of arguments passed to{% render %}, potentially reporting "global" variables that we'd previously missed. - Changed the
string_filterdecorator to coerceNoneto an empty string instead of"None". This is what Shopify/liquid does withnil.to_s.
Features
- Added the
escapejsfilter for escaping characters for use in JavaScript string literals. Whereas the standardescapefilter replaces&,<,>,'and"with their equivalent HTML escape sequence,escapejsreplaces control characters and potentially dangerous symbols with their corresponding Unicode escape sequences.
Docs
- Improved documentation for HTML auto escaping and the
escapefilter.
- Fixed static analysis of filters in ternary expressions. See #180.
- Fixed static analysis of macro blocks. Previously
argsandkwargswere considered "global". See #181. - Fixed looping over non-iterable objects with the
{% for %}tag. We were raising aLiquidTypeErrorwhen we should have been defaulting to an empty iterable, as Shopify/liquid does.
- Fixed bad imports from
typing_extensions.
This is a major release with several breaking changes. As well as API changes listed below, we:
- Drop support for Python version 3.7 and 3.8.
- Promote rendering behavior from
liquid.future.Environmentto be the default, so as to improve Shopify/liquid compatibility by default. - Fix variable/identifier/path parsing described in issue #39.
- Improve Liquid syntax error messages and exposes source index, line numbers and column numbers through methods on Liquid exceptions. See #53.
- Change comment tag parsing to better match Shopify/Liquid. See #133.
- Remove
BoundTemplate.analyze_with_context(). Shout if you need contextual analysis and we'll restore this feature. - Remove the
cache_sizeargument toliquid.Environmentandliquid.Template. Template caching is now handled by template loaders. - Remove the
expression_cache_sizeargument toliquid.Environmentandliquid.Template. Environment-level expression caching is no longer available as it does not play nicely with detailed error messages. If you need to cache parsing of Liquid expressions, it is now recommended to implement a cache per tag, where it makes sense to do so for your use case. - Make markupsafe a dependency. Previously markupsafe was an optional dependency. Version 3 of markupsafe brings some subtle changes to the
replace,replace_firstandreplace_lastfilters when they receive a "safe" string wrapped inMarkup(). - Add new filters
reject,has,findandfind_index. See Shopify/liquid #1869. - Add the new
doctag. See Shopify/liquid #1895.
Also see the migration guide.
- Added
liquid.parse(source),liquid.render(source, **data)andliquid.render_async(source, **data). These are shorthand functions that useliquid.DEFAULT_ENVIRONMENT. - Renamed
liquid.Environment.parsetoliquid.Environment._parse, which returns a list of nodes, not a template. - Aliased
liquid.Environment.from_stringasliquid.Environment.parse. - Added
liquid.Environment.render(source, **data)andliquid.Environment.render_async(source, **data). These are convenience methods equivalent toliquid.Environment.from_string(source).render(**data). - Renamed
liquid.Contexttoliquid.RenderContext. - Change the
liquid.RenderContextconstructor (previouslyliquid.Context) to require an instance ofBoundTemplateas its only positional argument instead of an instance ofEnvironment. All other arguments are now keyword only. - Renamed
liquid.exceptions.Errortoliquid.exceptions.LiquidError. - Renamed
liquid.exceptions.TemplateNotFoundtoliquid.exceptions.TemplateNotFoundError. - Renamed
liquid.exceptions.NoSuchFilterFunctoliquid.exceptions.UnknownFilterError.
- Changed
BaseLoader.get_sourceandBaseLoader.get_source_asyncto accept and optionalcontextkeyword argument and arbitrary keyword arguments as "load context". - Removed
BaseLoader.get_source_with_argsandBaseLoader.get_source_with_context, and their async equivalents.BaseLoader.get_sourcenow accepts optional context and load context arguments. - Changed
TemplateSource(a named tuple) to be (text, name, uptodate, matter). It used to be (source, filename, uptodate, matter)
- Removed
liquid.expression.*. Now built-in expressions live inliquid.builtin.expressions. - Renamed
IdentifiertoPath. - Removed
IdentifierPathElement. Path segments are nowlist[str | int | Path]]. - Removed constant versions of
True,False,Nil,EmptyandBlank. Each of these primitive expressions now require a token, so they can't be constant.
- Changed
liquid.token.Tokento be a named tuple of (kind, value, index, source). It used to be (linenum, type, value). - Removed legacy expression parsing functions. If you're importing anything from
liquid.parsefor your custom tags, you'll need to use functions/methods fromliquid.builtin.expressionsinstead. - Removed
liquid.parse.expect()andliquid.parse.expect_peek()in favour ofTokenStream.expect()andTokenStream.expect_peek(). - Removed
liquid.expressions.TokenStream. Now there's only oneTokenStreamclass,liquid.stream.TokenStream, reexported asliquid.TokenStream. - All tokens are now named tuples. Previously functions in
liquid.expressionswould generate and use plain tuples internally. - Added the
TOKEN_RANGE_LITERALtoken kind. The opening parenthesis of a range expression will use this kind to differentiate logical grouping parentheses from range expressions. - Split tokens with kind
TOKEN_OUTPUTin to two tokens,TOKEN_OUTPUTandTOKEN_EXPRESSION. Previously the value associated withTOKEN_OUTPUTwould be the expression, now the expression follows in the next token, just likeTOKEN_TAG.
Here's a summary mapping from old expression parsing functions to the recommended new parsing functions/methods.
| Old | New |
|---|---|
tokenize_common_expression(str, linenum) |
liquid.builtin.expressions.tokenize(source, parent_token) |
*.tokenize(source, linenum) |
liquid.builtin.expressions.tokenize(source, parent_token) |
parse_common_expression(stream) |
liquid.builtin.expressions.parse_primitive(env, stream) |
parse_keyword_arguments(expr, linenum) |
liquid.builtin.expressions.KeywordArgument.parse(env, stream) |
parse_identifier(stream) |
liquid.builtin.expressions.Path.parse(env, stream) |
parse_unchained_identifier(stream) |
liquid.builtin.expressions.parse_identifier(env, stream) |
parse_string_or_identifier |
liquid.builtin.expressions.parse_string_or_path(env, stream) |
parse_unchained_identifier |
liquid.builtin.expressions.parse_name(env, stream) |
parse_boolean |
liquid.builtin.expressions.parse_primitive(env, stream) |
parse_nil |
liquid.builtin.expressions.parse_primitive(env, stream) |
parse_empty |
liquid.builtin.expressions.parse_primitive(env, stream) |
parse_blank |
liquid.builtin.expressions.parse_primitive(env, stream) |
parse_string_literal |
liquid.builtin.expressions.parse_primitive(env, stream) |
parse_integer_literal |
liquid.builtin.expressions.parse_primitive(env, stream) |
parse_float_literal |
liquid.builtin.expressions.parse_primitive(env, stream) |
Environment.parse_boolean_expression |
liquid.builtin.expressions.BooleanExpression.parse(env, stream) |
Environment.parse_filtered_expression |
liquid.builtin.expressions.FilteredExpression.parse(env, stream) |
Environment.parse_loop_expression |
liquid.builtin.expressions.LoopExpression.parse(env, stream) |
- Added methods
variables(),variable_paths(),variable_segments(),global_variables(),global_variable_paths(),global_variable_segments()and their async equivalents toliquid.BoundTemplate. These are convenience methods for reporting variables using static analysis. - Added methods
filter_names(),tag_names()and their async equivalents toliquid.BoundTemplate. - Changed
liquid.ast.Node.children()and addedexpressions(),template_scope(),block_scope()andpartial_scope()methods. See docs.
Features
- Added a
shorthand_indexesclass variable toliquid.Environment. Whenshorthand_indexesis set toTrue(default isFalse), array indexes in variable paths need not be surrounded by square brackets. See #165.
Fixes
- Fixed
{% case %}/{% when %}behavior. When usingliquid.future.Environment, we now render any number of{% else %}blocks and allow{% when %}tags to appear after{% else %}tags. The defaultEnvironmentcontinues to raise aLiquidSyntaxErrorin such cases. - Fixed line numbers in some error messages. When parsing some Liquid expressions, we were always getting a line number of
1in the event of a syntax error. See issue #162.
Changed
- Changed
{% break %}and{% continue %}tag handling when they appear inside a{% tablerow %}tag. Now, when usingliquid.future.Environment, interrupts follow Shopify/Liquid behavior introduced in #1818. Python Liquid's default environment is unchanged.
Fixes
- Fixed handling of
{% else %}tags that include text betweenelseand the closing tag delimiter (%}). Previously we were treating such text as part of the{% else %}block. Now the default behavior is to raise aLiquidSyntaxError. When usingliquid.future.Environment, we follow Shopify/Liquid behavior of silently ignoring{% else %}tag expressions, even in strict mode. See #150. liquid.future.Environmentnow silently ignores superfluous{% else %}and{% elsif %}blocks. The default environment continues to raise aLiquidSyntaxErrorif{% else %}or{% elsif %}appear after the first{% else %}tag. See #151.
Fixes
- Fixed a bug with the LRU cache. We now raise a
ValueErrorat construction time if a caching template loader is given a cache size less than 1. Previously, in such cases, anIndexErrorwould have been raised when attempting to write to the cache. See #148.
Features
- Added
make_choice_loader(), a factory function that returns aChoiceLoaderorCachingChoiceLoaderdepending on its arguments. (docs, source) - Added
make_file_system_loader(), a factory function that returns aFileSystemLoader,FileExtensionLoaderorCachingFileSystemLoaderdepending on its arguments. (docs, source)
Fixes
- Fixed comparing strings with
<,<=,>and>=in boolean expressions ({% if %}and{% unless %}). Previously we were raising aLiquidTypeError, now we return the result of comparing two string by their lexicographical order. See #141.
Features
- Added
CachingChoiceLoader, a template loader that chooses between a list of template loaders and caches parsed templates in memory. (docs, source) - Added
PackageLoader, a template loader that reads templates from Python packages. (docs, source)
Dependencies
- Python Liquid now depends on importlib-resources >= 5.10. This is that backport of importlib.resources from Python's standard library. We use it in
PackageLoader.
Fixes
- Added an additional implementation of the
splitfilter, which resolves some compatibility issues between Python Liquid and the reference implementation. Previously, when given an empty string to split or when the string and the delimiter were equal, we used Python'sstr.split()behavior of producing one or two element lists containing empty strings. We now match Shopify/Liquid in returning an empty list for such cases. The newsplitfilter will be enabled by default when usingliquid.future.Environment, and can optionally be registered withliquid.Environmentfor those that don't mind the behavioral change. See #135. - Fixed unexpected errors from the
datefilter when it's given an invalid format string. Previously we were raising aliquid.exceptions.Errorin response to aValueErrorfrom strftime. We now raise aFilterArgumentErrorwith its__cause__set to theValueError. - Fixed handling of
"%s"date filter format strings. We now fall back to a string representation ofdatetime.timestamp()for platforms that don't support%s. Note that this is for"%s"exactly. We don't handle the more general case of%sappearing in a longer format string.
Version 1.10.1 was accidentally skipped and not released.
Features
- Optionally disable automatic suppression of whitespace only blocks with the
Environmentclass attributerender_whitespace_only_blocks. (docs). - All built-in and included "extra" tags now have a
node_classclass attribute specifying theNodetype the tag contributes to a templates AST. This is done for easier customization throughTagandNodesubclassing.
Fixes
- Fixed async loading of templates with the
{% extends %}tag. Previously templates were being loaded synchronously, even when usingrender_async(). See #124. - Fixed handling of recursive
{% extends %}tags during async static analysis. See #125.
Fixes
- Removed
is_up_to_datefromliquid.BoundTemplate.__repr__. It was causingRuntimeWarnings with Python 3.11 when using an async template loader. Specifically warnings about coroutines that were never awaited. - Fixed the
mapfilter. If given a nested array-like input, it now flattens it automatically. See #119. - Fixed the behavior of the
liquidtag when other liquid tags appear within it. See #123.
Features
- Added the
sumfilter, which will return the sum of all numeric values in its input sequence. (docs, source). - Added
Environment.setup_tags_and_filters()for easier tag and filter registration when subclassingEnvironment. See #122.
Fixes
- Fixed a bug where a class-based filter defining
filter_asyncand settingwith_contextorwith_environmenttoTruewould not be awaited. See #117.
Build
- Fixed some package build issues since moving to hatch. Both version 1.9.0 and 1.9.1 inadvertently included
.mypy_cachefolders, making the distribution files significantly larger.
Fixes
- Force the "wheel" build target to include
py.typed. - Restore
liquid.__version__.
Fixes
- Changed
FileSystemLoaderso that loaded templates can be pickled. See #107. - Fixed the
liquid.Environmenttemplate cache. Now, when given acache_sizeof0, the cache is disabled. See #108.
Features
- Added context-aware template loader bootstrapping methods. See #109.
- Added support for async class-based filters. See #115.
- Added
CachingFileSystemLoader, a template loader that handles its own cache rather than relying on theEnvironmenttemplate cache, which can't handle context-aware loaders. See #116.
Fixes
- Removed unnecessary wrapping of inline conditional expressions in
BooleanExpression. - Fixed async evaluation of macro arguments. Previously they were always being evaluated synchronously.
- Allow
macronames to be quoted or unquoted. Quoted and unquoted macro names are now equivalent when defining and/or calling a macro.
Compatibility
- Support bracketed variables without a leading identifier. See Shopify/liquid #1680.
- Allow whitespace control from
rawtags. See Shopify/liquid #1683.
Features
- Added
{% extends %}and{% block %}tags for template inheritance. These are extra tags that need to be registered with aliquid.Environmentexplicitly. (docs, source) - Added a new
sort_numericfilter.sort_numericreturns a new list with items from the input sequence sorted by any integers and/or floats found in the string representation of each item. (docs, source)
Fixes
- Fixed a bug with the
cycletag when usingliquid.future.Environment. We were misinterpreting unquoted cycle group names as strings rather than variables to be resolved, and not Liquid stringifying some cycled items before output. We've also rolled back changes toCycleNode.children()from version 1.7.0. - Fixed a regression bug that lead to some erroneous filtered expressions tokens to be silently ignored. Specifically any tokens that appear after a valid left value and the first filter or end of expression. We now raise a
LiquidSyntaxErrorin such cases. See #103. - Fixed parenthesized conditional expression syntax error reporting. We now raise a
LiquidSyntaxErrorwhen given unbalanced parentheses. See #101.
Compatibility
- The
{% for %}tag now accepts a string literal as its iterable. Unlike Shopify/liquid, whether a string literal or a variable resolving to a string, the defaultEnvironmentwill iterate over characters in the string.liquid.future.Environmentis now consistent with Shopify/liquid, in that it iterates over an "array" where the first an only item is the string. See #102. - The
roundfilter is now consistent with Shopify/liquid and Ruby 3 when given non-integer arguments. See Shopify/liquid#1590.
Fixes
- Fixed counting of "local" variables (those created with
assign,capture, etc.) in templates rendered with the{% render %}tag during contextual analysis. Previously these variables were not reported in the results ofBoundTemplate.analyze_with_context(). See #92. - Both #43 and #90 have been fixed with the introduction of
liquid.future.Environment, an environment that aims for maximum compatibility with Ruby Liquid, without concern for backwards incompatible changes to template rendering behavior.liquid.Environmentshould be considered the most stable "standard" configuration,liquid.future.Environmentsacrifices stability for improved compatibility with Ruby Liquid. See the known issues page. - Fixed a bug with AST traversal of
cyclenodes. Previously,CycleNode.children()erroneously included acycle group nameexpression, if available.
Features
- Report template filter usage as well as variable usage with
BoundTemplate.analyze()andBoundTemplate.analyze_with_context(). See #91 and docs. - Report template tag usage when statically analyzing a template with
BoundTemplate.analyze(). See #97 and docs. - Analyze template tags using
Environment.analyze_tags(). This form of tag analysis happens before a template is fully parsed, giving us the opportunity to find unknown, unexpected and unbalanced tags that might cause the parser to raise an exception or skip template blocks. See #98 and docs.
Fixes
- Fixed static template analysis fails with
{% break %}and{% continue %}. See #89.
Fixes
- Fixed the string representation of
liquid.expression.Identifier, which is exposed in the results ofliquid.BoundTemplate.analyze(). We now represent variable path elements containing a.as quoted strings inside square brackets. See #87.
Features
- The dictionaries returned by
liquid.BoundTemplate.analyze()now use instances ofReferencedVariablefor their keys.ReferencedVariableis astrsubclass that adds apartsproperty, being a tuple representation of the variable. See #87.
Fixes
- Fixed
caseandwhentag expression parsing.whenexpressions no longer fail when presented with a string containing a comma. Handling of comma andorseparated "sub-expressions" is now consistent with the reference implementation. See #86.
Features
The following non-standard tags and filters are reimplementations of those found in the Python Liquid Extra project, which is now receiving bugfix updates only. Unlike standard tags and filters, which are registered for you automatically, extra tags and filters must be explicitly registered with an Environment`. See https://jg-rp.github.io/liquid/extra/introduction.
-
Added an
iftag that supports a logicalnotoperator and grouping terms with parentheses. (docs, source) -
Added drop-in replacements for the standard output statement,
assigntag andechotag that support inline conditional expressions. (docs, source) -
Added
macroandcalltags that define parameterized Liquid snippets for reuse. (docs, source) -
Added the
withtag that extends the local namespace with block scoped variables. (docs, source) -
Added the
json,index,script_tagandstylesheet_tagfilters. (docs, source)
Compatibility
fortag arguments can now be separated by commas as well as whitespace. See Shopify/liquid#1658
Hot fix
- Fixed a bug where use of a local namespace limit would raise a
TypeErrorwhen unhashable types were found in a render context's local namespace. See #79.
Fixes
- The
tablerowloopdrop now exposes itsrowproperty. See #77. forandtablerowtag arguments can now be string representations of integers as well as integer literals and variables that resolve to integers. See #78.
Compatibility
- The
truncatewordsfilter no longer raises aFilterArgumentErrorif its argument is greater than2147483648and the number of words in the input string is less than the target number of words. This is inline with recent changes committed to the reference implementation of Liquid. - The
slicefilter now clamps its arguments to between-9223372036854775808and9223372036854775807, as does the reference implementation of Liquid.
Hot fix
- Fixed a bug where boolean expressions and the default filter would treat
0.0anddecimal.Decimal("0")asFalse. Python considers these values to be falsy, Liquid does not. See #74. - Future-proof str to int conversion limit. We will now use
sys.get_int_max_str_digitsif it is available andLIQUIDINTMAXSTRDIGITSis not set. Note thatsys.get_int_max_str_digitsis called once at startup, so Liquid's limit will change withsys.set_int_max_str_digits.
Fixes
- Keep comment text for later static analysis when parsing
{% comment %}block tags. See #70. - Guard against DoS by very large str to int conversion. See python/cpython#95778. (docs)
Fixes
- Updated the built-in
datefilter to support parsing UNIX timestamps from integers and string representations of integers. For consistency with the reference implementation of Liquid,datenow returns the input string unchanged if it can not be parsed. See #67. - Fixed an issue with the "loop iteration" resource limit. It was failing to carry an existing loop counter more than one level deep when using
Context.copy(). See #68.
Fixes
- Fixed a potential memory leak from using
functools.lru_cacheon a class method. See #63. - Fixed a bug with the
defaultfilter. Liquid zero should not be equal toFalse. Thedefaultfilter now returns0if its left value is zero. Before it would have return its default value. See #62. - Fixed a bug where boolean expressions would consider Liquid
0andfalseto be equal and0to be falsy. Python Liquid is now consistent with the reference implementation when comparing integers to booleans. See #65.
Hot fix
- Fixed a bug with the
StrictDefaultFilter. It was failing to be strict when accessed by some filter decorators and helpers. Now thedefaultfilter will immediately return its default value if its left value defines aforce_liquid_defaultproperty and that property is truthy. See #62.
Features
StrictDefaultUndefined, an undefined type that plays nicely with thedefaultfilter, is now built in. (docs)- Configure resource limits with class attributes set on a Liquid
Environment. Those class attributes arecontext_depth_limit,loop_iteration_limit,local_namespace_limitandoutput_stream_limit. (docs)
Fixes
- Fixed a bug in
StrictUndefinedthat, when extended, stopped if from looking at its ownmsgproperty. See #57.
Features
- Allow render context customization by subclassing
ContextandBoundTemplate. - Contextual template analysis with
BoundTemplate.analyze_with_context(). Complementing static template analysis, released in version 1.2.0, contextual template analysis performs a templaterender, capturing information about template variable usage as it goes. (docs)
- Add
typing-extensionsdependency.
Features
- New inline comment tag
{% # .. %}. See Shopify Liquid PR #1498 - Template static analysis.
BoundTemplate.analyze()andBoundTemplate.analyze_async()traverse a template's abstract syntax tree and report template variable usage. Static tree traversal (without rendering or evaluating expressions) is supported by the new, optionalchildren()methods ofliquid.expression.Expressionandliquid.ast.Node. (docs)
Fixes
- Fixed a bug where the lexer would incorrectly calculate an expression's line number if there were one or more newlines between a tag name and it's expression. Most notable with
liquidtags where it is common to put a newline immediately after "liquid". - More robust syntax error handling when parsing Liquid expression filters. The refactored expression lexers from version 1.1.3 failed to account for some classes of syntax error.
- Fixed a bug where double pipe characters (
||) in a filtered expression would cause anIndexError. ALiquidSyntaxErroris now raised in such cases, including the line number of the offending error. - Changed
Environment.fromStringto catch unexpected parsing errors. A LiquidErrorwill now be raised with a message of "unexpected liquid parsing error" and its__cause__set to the offending exception.
- Fixed a bug where the
wherefilter would incorrectly ignore an explicitfalsegiven as the target value. See #51.
- Prioritise object properties and keys named
size,firstandlastover the special built-in properties of the same names. See #46. - Fixed a bug with the
uniqfilter. It no longer raises an exception when given a key argument and a sequence containing objects that don't have that key/property. See #47. - The
strip_htmlfilter now removesstyleandscripttag blocks in their entirety, including everything in between. See #45. - Added
remove_lastandreplace_lastfilters.
- Lazy
forloophelper variables. Don't calculateindex,rindexetc. unless accessed. - Implemented
forloop.name, as per the reference implementation.forloop.nameis the concatenation of the loop variable identifier and the target iterable identifier, or a string representation of a range literal, separated by a hyphen. - Fixed a bug with the
divided_byfilter. Given a float value and integer argument, it was incorrectly doing integer division. - Simplified
tablerowloopandtablerowHTML generation.
- Refactored expression lexers. New, subtly different, tag expression tokenizers are now in
liquid.expressions. Built-in tags use these lexers indirectly via new specialized expression parsers. Older expression lexers and parsers will be maintained until at least Python Liquid version 2.0 for those that use them in custom tags. See #42. - Specialized expression parsers. Each of the three built-in expression types now have a dedicated parser defined in
liquid.expressions, whereas before all expression parsing went throughliquid.parse.ExpressionParser.parse_expression(). Built-in tags now use these new parsers. The more general parser will be maintained until at least Python Liquid Version 2.0. See #42. liquid.parse.Parser.parse_block()now accepts any container as itsendargument. Benchmarks show that using afrozensetforendinstead of a tuple gives a small performance improvement.- Fixed an incompatibility with the reference implementation where Python Liquid would not recognize identifiers with a trailing question mark. This seems to be a common idiom in Ruby to indicate something returns a Boolean value.
- Added
get_source_with_context()andget_source_with_context_async()toliquid.loaders.BaseLoader. Custom loaders can now use the active render context to dynamically modify their search space when used fromincludeorrender, or any custom tag usingContext.get_template_with_context().Context.get_template_with_context()also accepts arbitrary keyword arguments that are passed along toget_source_with_context(). The build-inincludeandrendertags add atagargument with their tag name, so custom loaders can modify their search space depending on which tag was used. See the Custom Loaders documentation for examples.
- Fixed a bug where a for loop's limit would be incorrect when using
offset: continuemultiple times (three or morefortags looping over the same sequence). See #41.
- Fixed a bug where blocks that contain whitespace only were being suppressed when the whitespace was explicitly output. Automatic whitespace suppression now only occurs in
if,unlesssandforblocks that don't contain an output statement orechotag, even if the output itself is whitespace. See #38. - Fixed a bug where the behavior of the special
.firstand.lastproperties did not match that of thefirstandlastfilters. Now, if given a string,.firstand.lastwill return an undefined, and thefirstandlastfilters will returnNone. See #34.
- Added new comment syntax. Disabled by default, enable shorthand comments with the
template_commentsargument toliquid.Templateorliquid.Environment. WhenTrue, anything between{#and#}will be considered a comment. - New expression cache. Distinct from the existing template cache, optionally cache common Liquid expression types (conditions, loops and filtered expressions) to avoid lexing and parsing the same expression multiple times.
- Fixed a bug where, in some cases,
forloop.lengthwould be incorrect when usingoffsset: continuein a loop expression.
- A range literal will now use a default of
0rather than raising aLiquidTypeErrorif either its start or stop values can't be cast to an integer. - Gracefully handle
liquidtags that are empty or only contain whitespace. - Gracefully handle empty
echotags.
- Explicit re-export
- Changed
Context._tag_namespacetoContext.tag_namespace.
- Fixed manifest error.
- Added
py.typed
Version bump. First stable release.
- Template loaders can now include additional template meta data using the
matterargument ofliquid.loaders.TemplateSource. See the exampleFrontMatterFileSystemLoaderin the README. See #32. - Implemented
ChoiceLoader, a loader that tries to load a template from a list of loaders. - Added a
FileExtensionLoader, a template loader that inherits fromFileSystemLoaderand automatically appends a file extension if one is missing. - The built-in
datefilter now accepts the special input value of "today" as well as "now". - The built-in
truncatefilter now has a default length of 50. - The built-in
truncatewordsfilter now has a default number of words of 15. - Fixed a bug with the
slicefilter where it would return an empty string when presented with a negative start index and length that would have exceeded the length of the sequence. See #35. - Drops can now define safe HTML string representations using a
__html__method. - Removed
liquid.mode.error()in favour ofliquid.Environment.error()
The following behavioral changes are the result of feedback gained from exporting Python
Liquid's "golden" test cases, and running them against Ruby Liquid (the reference
implementation). Both Python Liquid version 0.11.0 and Ruby Liquid version 5.1.0 pass
all tests currently defined in liquid/golden/.
- Added support for comma separated
whenexpressions. See #31. - The built-in
join,concat,where,uniqandcompactfilters now use the newsequence_filterdecorator.sequence_filtercoerces filter left values to array-like objects.sequence_filterwill also flatten nested array-like objects, just like the reference implementation. - The built-in
first,lastandmapfilters now operate on any array-like objects. Previously they were limited to lists and tuples. Strings still don't work. - The built-in
uniqandcompactfilters now accept an optional argument. If an argument is provided, it should be the name of a property and the left value should be a sequence of objects. - The
sizefilter now returns a default of0if its left value does not have a__len__method. - The
replaceandreplace_firstfilters now treat undefined arguments as an empty string. - The
slicefilter now works on lists, tuples and ranges, as well as strings. - Fixed a bug where the
math_filterdecorator would cast strings representations of negative integers to a float rather than an int. - Added golden test cases for all filters.
- Moved and organized "render" test cases into importable "golden" test cases.
- Change
RangeLiteralstring representation to match the reference implementation. - Add newlines to
tablerowoutput. As per the reference implementation.
- Range literals can now be assigned, compared and passed as arguments to
includeorrendertags. They can also be filtered as if they were an array. - Range literals will accept a float value for start and/or stop values. If a float is given for start or stop, it will be cast to an integer.
- Fixed a bug where the token stream would return the wrong token when peeking immediately after a push.
- Changed named counter (
incrementanddecrement) scoping. Unless a named counter is shadowed by anassignorcapture, the counter will be in scope for all subsequent Liquid expressions. - Changed
{% increment %}to be a post-increment operation.{% decrement %}remains a pre-decrement operation. - Added
forloop.parentloop. Access parentforloopobjects from nested loops.
unlessblocks can now containelseandelsifblocks.- Added support for array item access with negative indices. Closes #27.
- Improved error messages for context lookups that resulting an
IndexErrororKeyError.
- Fixed a bug where arguments to
Template()where not being passed to the implicit environment properly (again). - Fixed a bug where some errors from the
sortandmapfilters were being ignored. Those filters can now raise aFilterError. - Removed depreciated class-based filters.
- Removed
@abstractmethodfromliquid.loaders.BaseLoader.get_source. Custom loaders are now free to implement eitherget_sourceorget_source_asyncor both. TheBaseLoaderimplementation ofget_sourcesimply raises aNotImplementedError. liquid.loaders.TemplateSource.uptodate(as returned byget_sourceandget_source_async) can now be a coroutine function. This means async loaders can check a template's source for changes asynchronously.- Added the
cache_sizeargument toEnvironmentandTemplatefor controlling the capacity of the default template cache. - Easier subclassing of
liquid.parser.ExpressionParserwithEND_EXPRESSION.
Version bump. Last release before removing depreciated class-based filters.
- The
defaultfilter now uses__liquid__, if available, when testing an object for truthy-ness.
- Recursive use of the "render" tag now raises a
ContextDepthErrorifMAX_CONTEXT_DEPTHis exceeded. This is now consistent with recursive "include". - Drops (custom classes in a render context) can now mimic primitive Liquid values when used as array indexes or hash keys, or in conditional expressions (including
case/whenandunless). If defined, the result of calling a drop's__liquid__method will be used in those scenarios. - Added
base64_encode,base64_decode,base64_url_safe_encodeandbase64_url_safe_decodefilters. - Added asynchronous template loading and rendering. When
Template.render_asyncis awaited,renderandincludetags will load templates asynchronously. Custom loaders should implementget_source_async. - Added support for asynchronous drops. If a class implements
__getitem_async__, which is assumed to be an async version of__getitem__, it will be awaited instead of calling__getitem__.
- Class-based filters are now depreciated in favour of decorated filter functions. Abstract filter classes (such as
liquid.builtin.filters.string.StringFilter) will be removed in Liquid 0.9. - All built-in filters are now implemented as decorated functions. Legacy, class-based filters are no longer registered automatically and will be removed in Liquid 0.9.
- Legacy filter "helpers" are now depreciated and will be removed in Liquid 0.9. Use the new decorators, like
liquid.filter.string_filterandliquid.filter.with_context, instead. - The
blockargument to theliquid.ast.ConditionalBlockNodeconstructor is no longer optional.
- Auto reload. Disable automatic reloading of templates by passing
auto_reload=FalsetoEnvironmentorTemplate. For deployments where template sources don't change between service reloads, setting auto_reload toFalsecan yield an increase in performance by avoiding calls touptodate. - Fixed a bug where, when using the
TemplateAPI,undefined,strict_filters, andautoescapewhere not always passed through to the implicit environment correctly. - Added support for continuing a for loop using
offset: continue. See #14.
.. _MarkupSafe: https://github.com/pallets/markupsafe
- HTML auto-escaping. If
MarkupSafe_ is installed and theautoescapeargument toEnvironmentorTemplateisTrue, context variables will be HTML escaped automatically. LiquidSyntaxErrorexceptions now include the offending template source in thesourceproperty.
- The built-in
FileSystemLoadernow accepts a list of paths to search, in order, returning the first template source found. Thesearch_pathargument can be a string or path-like, or an iterable of strings or path-like objects. - Added the
encodingargument toFileSystemLoader. Files will be opened with the given encoding. Defaults to"utf-8". FileSystemLoaderwill raise aTemplateNotFoundexception if..appears in a template name.
- Undefined variables are now represented by the
Undefinedtype, or a subclass ofUndefined.Undefinedbehaves likenil, but can also be iterated over and indexed without error. - Attempting to loop over an undefined variable no longer raises a
LiquidTypeError. - Optionally pass
liquid.StrictUndefinedas theundefinedargument toTemplate()orEnvironment()to render instrict variablesmode. All operations on an instance ofStrictUndefinedraise anUndefinedError. - Filters can now raise
FilterValueErrorin addition toFilterArgumentError. Where aFilterValueErrorrefers to an issue with the left value a filter is applied to. - Applying a built-in filter to an undefined variable no longer raises a
FilterArgumentErrorin most cases. - Added the
strict_filtersargument to theEnvironmentandTemplateconstructors. WhenTrue, the default, undefined filters raise aNoSuchFilterFuncexception at render time. WhenFalse, undefined filters are silently ignored. - The
joinfilter now forces items in its left value to strings before joining them. - The
joinfilter's argument is now optional, defaulting to a string containing a single space.
- New
TemplateAPI. Create templates from strings without anEnvironment. - The
templateobject is no longer included in every render context automatically.
- Implemented the
blankkeyword. Empty string and strings containing only whitespace are equal toblank. - Implemented the
nullkeyword.nullis an alias fornil. - Implemented the
ifchangedtag.
- Refactored the standard expression parser. It's now possible to extend
ExpressionParserfor use with custom tags. - Decoupled boolean expression parsing for easier
if,unlessandcasetag subclassing.
- Added support for Python 3.7 and PyPy3.7.
- Added support for named filter parameters.
- The
defaultfilter now accepts the named parameterallow_false. - The
truncate_wordsfilter now forces a minimum number of words to 1. - The
newline_to_brfilter now replaces\nand\r\nwith<br />\n. - The
strip_newlinesfilter strips\r\n.