<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: patterns</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/patterns.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2020-09-02T23:09:41+00:00</updated><author><name>Simon Willison</name></author><entry><title>The "await me maybe" pattern for Python asyncio</title><link href="https://simonwillison.net/2020/Sep/2/await-me-maybe/#atom-tag" rel="alternate"/><published>2020-09-02T23:09:41+00:00</published><updated>2020-09-02T23:09:41+00:00</updated><id>https://simonwillison.net/2020/Sep/2/await-me-maybe/#atom-tag</id><summary type="html">
    &lt;p&gt;I've identified a pattern for handling potentially-asynchronous callback functions in Python which I'm calling the "await me maybe" pattern. It works by letting you return a value, a callable function that returns a value OR an awaitable function that returns that value.&lt;/p&gt;
&lt;h4 id="await-me-maybe-background"&gt;Background&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://docs.datasette.io/"&gt;Datasette&lt;/a&gt; has been built on top of Python 3 &lt;a href="https://docs.python.org/3/library/asyncio.html"&gt;asyncio&lt;/a&gt; from the very start - initially using &lt;a href="https://sanic.readthedocs.io/"&gt;Sanic&lt;/a&gt;, and as-of &lt;a href="https://docs.datasette.io/en/stable/changelog.html#v0-29"&gt;Datasette 0.29&lt;/a&gt; using a custom mini-framework on top of ASGI 3, usually running under Uvicorn.&lt;/p&gt;
&lt;p&gt;Datasette also has a plugin system, built on top of &lt;a href="https://pypi.org/project/pluggy/"&gt;Pluggy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pluggy is a beautifully designed mechanism for plugins. It works based on decorated functions, which are called at various points by Datasette itself.&lt;/p&gt;
&lt;p&gt;A simple plugin that injects a new JavaScript file into a page coud look like this:&lt;/p&gt;

&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;datasette&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;hookimpl&lt;/span&gt;

&lt;span class="pl-en"&gt;@&lt;span class="pl-s1"&gt;hookimpl&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;extra_js_urls&lt;/span&gt;():
    &lt;span class="pl-k"&gt;return&lt;/span&gt; [
        &lt;span class="pl-s"&gt;"https://code.jquery.com/jquery-3.5.1.min.js"&lt;/span&gt;
    ]&lt;/pre&gt;
&lt;p&gt;Datasette can then gather together all of the extra JavaScript URLs that should be injected into a page by running this code:&lt;/p&gt;

&lt;pre&gt;&lt;span class="pl-s1"&gt;urls&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; []
&lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s1"&gt;url&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-s1"&gt;pm&lt;/span&gt;.&lt;span class="pl-s1"&gt;hook&lt;/span&gt;.&lt;span class="pl-en"&gt;extra_js_urls&lt;/span&gt;(
    &lt;span class="pl-s1"&gt;template&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;template&lt;/span&gt;.&lt;span class="pl-s1"&gt;name&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;datasette&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;datasette&lt;/span&gt;,
):
    &lt;span class="pl-s1"&gt;urls&lt;/span&gt;.&lt;span class="pl-en"&gt;extend&lt;/span&gt;(&lt;span class="pl-s1"&gt;url&lt;/span&gt;)&lt;/pre&gt;
&lt;p&gt;What's up with the &lt;code&gt;template=&lt;/code&gt; and &lt;code&gt;datasette=&lt;/code&gt; parameters that are passed here?&lt;/p&gt;
&lt;p&gt;Pluggy implements a form of dependency injection, where plugin hook functions can optionally list additional parameters that they would like to have access to.&lt;/p&gt;
&lt;p&gt;The above simple example didn't need any extra information. But imagine a plugin that only wants to inject jQuery on the &lt;code&gt;table.html&lt;/code&gt; template page:&lt;/p&gt;

&lt;pre&gt;&lt;span class="pl-en"&gt;@&lt;span class="pl-s1"&gt;hookimpl&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;extra_js_urls&lt;/span&gt;(&lt;span class="pl-s1"&gt;template&lt;/span&gt;):
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;template&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-s"&gt;"table.html"&lt;/span&gt;:
        &lt;span class="pl-k"&gt;return&lt;/span&gt; [
            &lt;span class="pl-s"&gt;"https://code.jquery.com/jquery-3.5.1.min.js"&lt;/span&gt;
        ]&lt;/pre&gt;
&lt;p&gt;Datasette actually provides several more optional argument for these plugin functions - see &lt;a href="https://docs.datasette.io/en/stable/plugin_hooks.html#extra-template-vars-template-database-table-columns-view-name-request-datasette"&gt;the plugin hooks documentation&lt;/a&gt; for full details.&lt;/p&gt;
&lt;h4&gt;What if we need to await something?&lt;/h4&gt;
&lt;p&gt;The &lt;a href="https://docs.datasette.io/en/stable/internals.html#datasette-class"&gt;datasette object&lt;/a&gt; that can be passed to plugin hooks is special: it provides an object that can be used for the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Executing SQL against databases connected to Datasette&lt;/li&gt;
&lt;li&gt;Looking up Datasette metadata and configuration settings, including plugin configuration&lt;/li&gt;
&lt;li&gt;Rendering templates using the template environment configured by Datasette&lt;/li&gt;
&lt;li&gt;Performing checks against the Datasette &lt;a href="https://docs.datasette.io/en/stable/authentication.html#permissions"&gt;permissions system&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's the problem: many of those methods on Datasette are awaitable - &lt;code&gt;await datasette.render_template(...)&lt;/code&gt; for example. But Pluggy is built around regular non-awaitable Python functions.&lt;/p&gt;
&lt;p&gt;If my &lt;code&gt;def extra_js_urls()&lt;/code&gt; plugin function needs to execute a SQL query to decide what JavaScript to include, it won't be able to - because you can't use &lt;code&gt;await&lt;/code&gt; inside a regular Python function.&lt;/p&gt;
&lt;p&gt;That's where the "await me maybe" pattern comes in.&lt;/p&gt;
&lt;p&gt;The basic idea is that a function can return a value, OR a function-that-returns-a-value, OR an awaitable-function-that-returns-a-value.&lt;/p&gt;
&lt;p&gt;If we want our &lt;code&gt;extra_js_urls(datasette)&lt;/code&gt; hook to execute a SQL query in order to decide what URLs to return, it can look like this:&lt;/p&gt;

&lt;pre&gt;&lt;span class="pl-en"&gt;@&lt;span class="pl-s1"&gt;hookimpl&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;extra_js_urls&lt;/span&gt;(&lt;span class="pl-s1"&gt;datasette&lt;/span&gt;):
    &lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;inner&lt;/span&gt;():
        &lt;span class="pl-s1"&gt;db&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;datasette&lt;/span&gt;.&lt;span class="pl-en"&gt;get_database&lt;/span&gt;()
        &lt;span class="pl-s1"&gt;results&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-s1"&gt;db&lt;/span&gt;.&lt;span class="pl-en"&gt;execute&lt;/span&gt;(&lt;span class="pl-s"&gt;"select url from js_files"&lt;/span&gt;)
        &lt;span class="pl-k"&gt;return&lt;/span&gt; [&lt;span class="pl-s1"&gt;r&lt;/span&gt;[&lt;span class="pl-c1"&gt;0&lt;/span&gt;] &lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s1"&gt;r&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-s1"&gt;results&lt;/span&gt;]

    &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-s1"&gt;inner&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;Note that Python lets you define an &lt;code&gt;async def inner()&lt;/code&gt; function inside the body of a regular function, which is what we're doing here.&lt;/p&gt;
&lt;p&gt;The code that calls the plugin hook in Datasette can then look like this:&lt;/p&gt;

&lt;pre&gt;&lt;span class="pl-s1"&gt;urls&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; []
&lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s1"&gt;url&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-s1"&gt;pm&lt;/span&gt;.&lt;span class="pl-s1"&gt;hook&lt;/span&gt;.&lt;span class="pl-en"&gt;extra_js_urls&lt;/span&gt;(
    &lt;span class="pl-s1"&gt;template&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;template&lt;/span&gt;.&lt;span class="pl-s1"&gt;name&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;datasette&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;datasette&lt;/span&gt;,
):
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-en"&gt;callable&lt;/span&gt;(&lt;span class="pl-s1"&gt;url&lt;/span&gt;):
        &lt;span class="pl-s1"&gt;url&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;url&lt;/span&gt;()
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;asyncio&lt;/span&gt;.&lt;span class="pl-en"&gt;iscoroutine&lt;/span&gt;(&lt;span class="pl-s1"&gt;url&lt;/span&gt;):
        &lt;span class="pl-s1"&gt;url&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-s1"&gt;url&lt;/span&gt;
    &lt;span class="pl-s1"&gt;urls&lt;/span&gt;.&lt;span class="pl-en"&gt;append&lt;/span&gt;(&lt;span class="pl-s1"&gt;url&lt;/span&gt;)&lt;/pre&gt;
&lt;p&gt;I use this pattern in a bunch of different places in Datasette, so today I refactored that into &lt;a href="https://github.com/simonw/datasette/blob/26b2922f177caa4e147aaee28be0cff37a457802/datasette/utils/__init__.py#L55-L60"&gt;a utility function&lt;/a&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;asyncio&lt;/span&gt;

&lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;await_me_maybe&lt;/span&gt;(&lt;span class="pl-s1"&gt;value&lt;/span&gt;):
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-en"&gt;callable&lt;/span&gt;(&lt;span class="pl-s1"&gt;value&lt;/span&gt;):
        &lt;span class="pl-s1"&gt;value&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;value&lt;/span&gt;()
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;asyncio&lt;/span&gt;.&lt;span class="pl-en"&gt;iscoroutine&lt;/span&gt;(&lt;span class="pl-s1"&gt;value&lt;/span&gt;):
        &lt;span class="pl-s1"&gt;value&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-s1"&gt;value&lt;/span&gt;
    &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-s1"&gt;value&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://github.com/simonw/datasette/commit/26b2922f177caa4e147aaee28be0cff37a457802"&gt;This commit&lt;/a&gt; includes a bunch of examples where this function is called, for example &lt;a href="https://github.com/simonw/datasette/blob/26b2922f177caa4e147aaee28be0cff37a457802/datasette/app.py#L702-L714"&gt;this code&lt;/a&gt; which gathers extra body scripts to be included at the bottom of the page:&lt;/p&gt;

&lt;pre&gt;&lt;span class="pl-s1"&gt;body_scripts&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; []
&lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s1"&gt;extra_script&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-s1"&gt;pm&lt;/span&gt;.&lt;span class="pl-s1"&gt;hook&lt;/span&gt;.&lt;span class="pl-en"&gt;extra_body_script&lt;/span&gt;(
    &lt;span class="pl-s1"&gt;template&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;template&lt;/span&gt;.&lt;span class="pl-s1"&gt;name&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;database&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;context&lt;/span&gt;.&lt;span class="pl-en"&gt;get&lt;/span&gt;(&lt;span class="pl-s"&gt;"database"&lt;/span&gt;),
    &lt;span class="pl-s1"&gt;table&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;context&lt;/span&gt;.&lt;span class="pl-en"&gt;get&lt;/span&gt;(&lt;span class="pl-s"&gt;"table"&lt;/span&gt;),
    &lt;span class="pl-s1"&gt;columns&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;context&lt;/span&gt;.&lt;span class="pl-en"&gt;get&lt;/span&gt;(&lt;span class="pl-s"&gt;"columns"&lt;/span&gt;),
    &lt;span class="pl-s1"&gt;view_name&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;view_name&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;request&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;request&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;datasette&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;self&lt;/span&gt;,
):
    &lt;span class="pl-s1"&gt;extra_script&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-en"&gt;await_me_maybe&lt;/span&gt;(&lt;span class="pl-s1"&gt;extra_script&lt;/span&gt;)
    &lt;span class="pl-s1"&gt;body_scripts&lt;/span&gt;.&lt;span class="pl-en"&gt;append&lt;/span&gt;(&lt;span class="pl-v"&gt;Markup&lt;/span&gt;(&lt;span class="pl-s1"&gt;extra_script&lt;/span&gt;))&lt;/pre&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/async"&gt;async&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/patterns"&gt;patterns&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/plugins"&gt;plugins&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="async"/><category term="patterns"/><category term="plugins"/><category term="python"/><category term="datasette"/></entry><entry><title>Serverless Microservice Patterns for AWS</title><link href="https://simonwillison.net/2019/Jun/12/serverless-microservice-patterns-aws/#atom-tag" rel="alternate"/><published>2019-06-12T00:13:38+00:00</published><updated>2019-06-12T00:13:38+00:00</updated><id>https://simonwillison.net/2019/Jun/12/serverless-microservice-patterns-aws/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.jeremydaly.com/serverless-microservice-patterns-for-aws/"&gt;Serverless Microservice Patterns for AWS&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
A handy collection of 19 architectural patterns for AWS Lambda collected by Jeremy Daly.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/PaulDJohnston/status/1138483302468374534"&gt;@PaulDJohnston&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/aws"&gt;aws&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/lambda"&gt;lambda&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/patterns"&gt;patterns&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/software-architecture"&gt;software-architecture&lt;/a&gt;&lt;/p&gt;



</summary><category term="aws"/><category term="lambda"/><category term="patterns"/><category term="software-architecture"/></entry><entry><title>Large Problems in Django, Mostly Solved: Search</title><link href="https://simonwillison.net/2009/Nov/3/search/#atom-tag" rel="alternate"/><published>2009-11-03T10:42:33+00:00</published><updated>2009-11-03T10:42:33+00:00</updated><id>https://simonwillison.net/2009/Nov/3/search/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://ericholscher.com/blog/2009/nov/2/large-problems-django-mostly-solved/"&gt;Large Problems in Django, Mostly Solved: Search&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Eric Holscher shows how Haystack uses a number of common Django patterns (object registration, pluggable backends, QuerySet-style chaining and class-based views) to great effect in creating a powerful search application for Django. Makes me wonder if more of those patterns should be promoted to first class concepts within Django.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/classbasedviews"&gt;classbasedviews&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/eric-holscher"&gt;eric-holscher&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/haystack"&gt;haystack&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/patterns"&gt;patterns&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/search"&gt;search&lt;/a&gt;&lt;/p&gt;



</summary><category term="classbasedviews"/><category term="django"/><category term="eric-holscher"/><category term="haystack"/><category term="patterns"/><category term="python"/><category term="search"/></entry><entry><title>Collection: Search Patterns</title><link href="https://simonwillison.net/2009/Jul/30/collection/#atom-tag" rel="alternate"/><published>2009-07-30T12:35:29+00:00</published><updated>2009-07-30T12:35:29+00:00</updated><id>https://simonwillison.net/2009/Jul/30/collection/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.flickr.com/photos/morville/collections/72157603785835882/"&gt;Collection: Search Patterns&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Peter Morville’s enormous collection of screenshots of search engine interfaces.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/design"&gt;design&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/patterns"&gt;patterns&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/peter-morville"&gt;peter-morville&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/search"&gt;search&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ui"&gt;ui&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/usability"&gt;usability&lt;/a&gt;&lt;/p&gt;



</summary><category term="design"/><category term="patterns"/><category term="peter-morville"/><category term="search"/><category term="ui"/><category term="usability"/></entry><entry><title>Wikipatterns</title><link href="https://simonwillison.net/2007/Feb/17/wikipatterns/#atom-tag" rel="alternate"/><published>2007-02-17T00:51:48+00:00</published><updated>2007-02-17T00:51:48+00:00</updated><id>https://simonwillison.net/2007/Feb/17/wikipatterns/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.wikipatterns.com/display/wikipatterns/Wikipatterns"&gt;Wikipatterns&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Great idea this: a wiki documenting patterns for successfully growing your own wiki.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/atlassian"&gt;atlassian&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/patterns"&gt;patterns&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/wiki"&gt;wiki&lt;/a&gt;&lt;/p&gt;



</summary><category term="atlassian"/><category term="patterns"/><category term="wiki"/></entry></feed>