lektor-website/plugins/lektor-limit-dependencies/index.html

295 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="../../static/styles.css?h=dff0aaad">
<link rel="stylesheet" href="../../static/pygments.css">
<link rel="shortcut icon" href="../../static/favicon.png?h=fa09bedd">
<title> | Lektor Static Content Management System</title>
</head>
<body class="default">
<nav class="navbar navbar-inverse navbar-static-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed"
data-toggle="collapse" data-target="#navbar"
aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="../../">Lektor</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="../../downloads/">Download</a></li>
<li><a href="../../docs/">Documentation</a></li>
<li><a href="../../showcase/">Showcase</a></li>
<li class="active"><a href="../">Plugins</a></li>
<li><a href="../../community/">Community</a></li>
<li><a href="../../blog/">Blog</a></li>
</ul>
</div>
</div>
</nav>
<div class="body-wrapper">
<div class="page-banner page-banner-300" style="background-image: url(../header.jpg)"></div>
<div class="container">
<!-- Place this tag in your head or just before your close body tag. -->
<div class="plugin">
<div class="row">
<div class="col-sm-12">
<h1>Plugin &ndash; lektor-limit-dependencies 1.0.0</h1>
</div>
</div>
<div class="row">
<div class="col-sm-1"></div>
<div class="col-sm-11">
<p>Lektor plugin to limit dependencies created by queries<p>
</div>
</div>
<div class="row">
<div class="col-sm-3 plugin-margin">
<h4>Project links</h4>
<ul class="tree-nav">
<li><a href="https://pypi.org/project/lektor-limit-dependencies/" class="ext">Homepage</a></li>
</ul>
<div class="separator">
<h4>Meta</h4>
</div>
<p><strong>Version:</strong> 1.0.0</p>
<p><strong>Author:</strong>
<a href="mailto:dairiki@dairiki.org">Jeff Dairiki</a>
</p>
<div class="separator">
<h4>Tags</h4>
</div>
<a href="../tag/build-optimization/">build optimization</a>,
<a href="../tag/Jinja-filters/">Jinja filters</a>,
and
<a href="../tag/setup-env/">setup-env</a>
<p>
View <a href="../tags/" class="ref">all tags</a>.
</p>
</div>
<div class="col-sm-9 doc-styling">
<h2>Project Description</h2>
<h1>Lektor Limit Dependencies</h1>
<p><a href="https://pypi.org/project/lektor-limit-dependencies/" rel="nofollow"><img src="https://img.shields.io/pypi/v/lektor-limit-dependencies.svg" alt="PyPI version"></a>
<a href="https://pypi.python.org/pypi/lektor-limit-dependencies/" rel="nofollow"><img src="https://img.shields.io/pypi/pyversions/lektor-limit-dependencies.svg" alt="PyPI Supported Python Versions"></a>
<a href="https://github.com/dairiki/lektor-limit-dependencies/blob/master/LICENSE" rel="nofollow"><img src="https://img.shields.io/github/license/dairiki/lektor-limit-dependencies" alt="GitHub license"></a>
<a href="https://github.com/dairiki/lektor-limit-dependencies" rel="nofollow"><img src="https://github.com/dairiki/lektor-limit-dependencies/workflows/Tests/badge.svg" alt="GitHub Actions (Tests)"></a></p>
<p>This is an experimental <a href="https://www.getlektor.com/" rel="nofollow">Lektor</a> plugin which aims to provide tools (or,
at least, a tool) to help keep Lektors dependency tracking under
control.</p>
<h2>Introduction</h2>
<h3>Motivating Example</h3>
<p>Suppose that you would like to list the three most recent blog posts
in the sidebar of your Lektor-based site. This can be done by adding
something like to your site base template:</p>
<pre lang="html+jinja"><span class="p">&lt;</span><span class="nt">h3</span><span class="p">&gt;</span>Recent Posts<span class="p">&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
<span class="cp">{%</span> <span class="k">for</span> <span class="nv">post</span> <span class="k">in</span> <span class="nv">site.query</span><span class="o">(</span><span class="s1">'/blog'</span><span class="o">)</span><span class="nv">.order_by</span><span class="o">(</span><span class="s1">'-pub_date'</span><span class="o">)</span><span class="nv">.limit</span><span class="o">(</span><span class="m">3</span><span class="o">)</span> <span class="cp">%}</span>
<span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">post</span><span class="o">|</span><span class="nf">url</span> <span class="cp">}}</span><span class="s">"</span><span class="p">&gt;</span><span class="cp">{{</span> <span class="nv">post.title</span> <span class="cp">}}</span><span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
<span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span>
<span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</pre>
<p>This is not without drawbacks, however. To sort the post query by
date, Lektor iterates through <em><strong>all</strong></em> of the blogs posts, then sorts
them. In so doing, it records all of the blog posts as dependencies
<em>of every page on which this most-recent-post query is used</em>. If this
is in the sidebar of every page on your site, <em>now every page on your
site will be rebuilt whenever any blog post at all</em> (not just one of
the three most recent posts) <em>is edited</em>.</p>
<p>Technically, it is true that all pages now depend on all posts. You
might well edit the <code>pub_date</code> of one of your older posts, such that
it should now appear in the most-recent listing. However, it is not
true that all pages need to be rebuilt for <em>any</em> edit of any post.
Unfortunately, Lektors dependency tracking system is not elaborate
enough to be able to express details about <em>how</em> pages are
dependent on other pages; it only records that they <em>are</em>
dependent, so Lektor has no option but to rebuild everything.</p>
<h3>A Solution?</h3>
<p>This plugin introduces a Jinja filter, <code>limit_dependencies</code>. It
expects, as input, a Lektor <a href="https://www.getlektor.com/docs/api/db/query/" title="Lektor documentation on the Query class" rel="nofollow">query</a> instance. It iterates through the
input query, and returns a new query instance which will yield the
same results. While it is doing its iteration, it — essentially —
monkey-patches Lektors dependency tracking machinery to prevent it
from recording any dependencies.</p>
<p>At the end, <code>limit_dependencies</code> records one dependency on a <a href="https://www.getlektor.com/docs/api/db/obj/#virtual-source-objects" title="Lektor documentation on Virtual Source Objects" rel="nofollow">virtual
source object</a> which depends only on the sequence of the identities
of the records in the query result. (Lektor provides a means by which
virtual source objects can report checksums. The
dependency tracking mechanism records those checksums, and will
trigger a rebuild should the checksum change. <code>Limit_dependencies</code>
generates a virtual source object whose checksum depends on the
sequence of identities in the query result.)</p>
<p>In the above example, this is exactly what we want. We only want to
trigger a rebuild if the order or composition of the most-recent three
posts changes. (Or if any of their titles change. Note that this
gets covered, too, since when the resulting query is iterated over in
the <code>{% for %}</code> loop, dependencies will be recorded on the three
most-recent posts.)</p>
<p>Thus, the example above, if replaced by:</p>
<pre lang="html+jinja"><span class="p">&lt;</span><span class="nt">h3</span><span class="p">&gt;</span>Recent Posts<span class="p">&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
<span class="cp">{%</span> <span class="k">for</span> <span class="nv">post</span> <span class="k">in</span> <span class="nv">site.query</span><span class="o">(</span><span class="s1">'/blog'</span><span class="o">)</span><span class="nv">.order_by</span><span class="o">(</span><span class="s1">'-pub_date'</span><span class="o">)</span><span class="nv">.limit</span><span class="o">(</span><span class="m">3</span><span class="o">)|</span><span class="nf">limit_dependencies</span> <span class="cp">%}</span>
<span class="p">&lt;</span><span class="nt">li</span><span class="p">&gt;&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">post</span><span class="o">|</span><span class="nf">url</span> <span class="cp">}}</span><span class="s">"</span><span class="p">&gt;</span><span class="cp">{{</span> <span class="nv">post.title</span> <span class="cp">}}</span><span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
<span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span>
<span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</pre>
<p>will work in a much more efficient and sane manner. Pages will be
rebuilt only if there are changes in the order, composition or content
of the three most recent posts.</p>
<h2>Installation</h2>
<p>Add lektor-limit-dependencies to your project from command line:</p>
<pre><code>lektor plugins add lektor-limit-dependencies
</code></pre>
<p>See <a href="https://www.getlektor.com/docs/plugins/" rel="nofollow">the Lektor plugin documentation</a> for more information.</p>
<h2>Author</h2>
<p>Jeff Dairiki <a href="mailto:dairiki@dairiki.org" rel="nofollow">dairiki@dairiki.org</a></p>
<h2>Changelog</h2>
<h3>Release 1.0.0 (2023-01-31)</h3>
<p>No code changes from 1.0.0b1.</p>
<p>Test under python 3.11.</p>
<h3>Release 1.0.0b1 (2021-03-29)</h3>
<p>Drop support for python&lt;3.7 and lektor&lt;3.3.</p>
<h3>Release 0.1 (2021-02-05)</h3>
<p>No code changes.</p>
<p>Update development status classifier to "stable".</p>
<p>Test under python 3.9. Stop testing under 3.5.</p>
<h3>Release 0.1a1 (2020-05-19)</h3>
<p>Initial release.</p>
<div class="comment-box">
<h2>Comments</h2>
<div id="disqus_thread"></div>
<script>
var disqus_config = function() { this.page.identifier = "/plugins/lektor-limit-dependencies"; this.page.url = "https://www.getlektor.com/plugins/lektor-limit-dependencies/"; };
(function() {
var d = document, s = d.createElement('script');
s.src = '//lektordocumentation.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>
Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript"
rel="nofollow">comments powered by Disqus.</a>
</noscript>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="bottomsummary">
<div class="container">
</div>
</div>
<footer>
<div class="container">
<div class="row">
<div class="col-sm-4 icon-bar">
<a href="https://github.com/lektor/lektor/" title="Lektor on GitHub"
><i class="fa fa-github"></i></a>
<a href="https://github.com/lektor/lektor/issues/" title="Report Issues for Lektor"
><i class="fa fa-bug"></i></a>
<a href="https://twitter.com/getlektor" title="Find Lektor on Twitter"
><i class="fa fa-twitter"></i></a>
<a href="https://gitter.im/lektor/lektor" title="Chat on Gitter"
><i class="fa fa-comment"></i></a>
<a href="https://github.com/lektor/lektor-website/tree/master/content/plugins/lektor-limit-dependencies/contents.lr" title="View source for this page"><i class="fa fa-code"></i></a>
</div>
<div class="col-sm-8">
<a href="../../license/">License & Copyright</a>
<a href="../../contact/">Contact</a>
Made with <i class="fa fa-fw fa-heart" title="Heart"><span hidden>Heart</span></i> in Carinthia
</div>
</div>
</div>
</footer>
<script type=text/javascript src="../../static/app.js?h=250c2aed" charset="utf-8"></script>
<script>
((window.gitter = {}).chat = {}).options = {
room: 'lektor/lektor',
activationElement: null
};
document.write('<button class="js-gitter-toggle-chat-button">Toggle Chat</button>');
var dnt = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
if (dnt != "1" && dnt != "yes") {
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-70822533-1', 'auto');
ga('set', 'anonymizeIp', true);
ga('send', 'pageview');
} else {
console.debug("Respecting Do-Not-Track, not running analytics.");
}
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<script async defer id="github-bjs" src="https://buttons.github.io/buttons.js"></script>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
<script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer></script>
</body>
</html>