Finally - Pelican with Algolia Search

TL;DR While looking for a search functionality for our blog I stumbled over Algolia - this post describes the birth of my first Pelican plugin.

Algolia is the most reliable platform for building search into your business.

Since there seems to be no Pelican plugin available I decided to get my hands dirty with Python. Another dusty task on my list :)

After a few minutes on How to create plugins and several hours looking at other Pelican plugins:

and the Algolia Search API Client for Python plus official Guide > Indexing Import & Synchronize Your Data - Python the blog was searchable...hooray!

As a plugin developer you can register for Pelican signals. We want to index articles and pages once they are finalized:

def register():
    signals.article_generator_finalized.connect(createArticleIndex)
    signals.page_generator_finalized.connect(createPageIndex)

Pushing data to Algolia is straight forward and doesn't require much from a developers point of view:

algoliaIndexName = generator.settings.get('ALGOLIA_INDEX_NAME')
print ("Generating Algolia index '%s' for %d articles..." % (algoliaIndexName, len(generator.articles)))
algoliaAppId = generator.settings.get('ALGOLIA_APP_ID')
algoliaAdminApiKey = generator.settings.get('ALGOLIA_ADMIN_API_KEY')

client = algoliasearch.Client(algoliaAppId, algoliaAdminApiKey)
index = client.init_index(algoliaIndexName)

We store the credentials in pelicanconf.py as we do for other plugins

ALGOLIA_APP_ID = '<APP_ID>'
ALGOLIA_SEARCH_API_KEY = '<SEARCH_API_KEY>'
ALGOLIA_INDEX_NAME = '<INDEX_NAME>'
ALGOLIA_ADMIN_API_KEY = '<ADMIN_API_KEY>'

The main loop isn't spectacular, too. We simply iterate over all articles/pages and call index.add_object and automagically they appear in our search index.

for article in generator.articles:
    print ("Indexing article: '%s'" % article.title)
    data = {}
    data['title'] = article.title
    data['slug'] = article.slug
    data['url'] = article.url
    data['tags'] = []
    for tag in getattr(article, 'tags', []):
        data['tags'].append(tag.name)
    data['content'] = article.content
    data['category'] = article.category
    print ("Adding Algolia object...")
    objectId = hashlib.sha256(str(article.slug).encode('utf-8')).hexdigest()
    index.add_object(data, objectId)

The only tricky part was to generate a stable ObjectID for Algolia. I decided to use the slug as basis.

With the index available I added a search field:

<div class="aa-input-container" id="aa-input-container">
    <input type="search" id="aa-search-input" class="aa-input-search" placeholder="Search this site..." name="search" autocomplete="off" />
</div>

and a JavaScript snippet using the autocomplete module provided by Algolia:

<!-- Include AlgoliaSearch JS Client and autocomplete.js library -->
<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.min.js"></script>
<!-- Initialize autocomplete menu -->
<script>
var client = algoliasearch("{{ALGOLIA_APP_ID}}", "{{ALGOLIA_SEARCH_API_KEY}}");
var index = client.initIndex('{{ALGOLIA_INDEX_NAME}}');
//initialize autocomplete on search input (ID selector must match)
autocomplete('#aa-search-input',
{ hint: false }, {
    source: autocomplete.sources.hits(index, {hitsPerPage: 4}),
    //value to be displayed in input control after user's suggestion selection
    displayKey: 'title',
    //hash of templates used when rendering dataset
    templates: {
        //'suggestion' templating function used to render a single suggestion
        suggestion: function(suggestion) {
          return '<span><a href="' + suggestion.url + '">'
              + suggestion._highlightResult.title.value
            + '</a></span>'
            + '<span><a href="' + suggestion.url + '">'
              + suggestion._highlightResult.category.value
            + '</a></span>';
        }
    }
});
</script>

After a day of programming fun we finally have a search at devops.datenkollektiv...

What's next?

In case others are interested to enhance Pelican blogs with Algolia search I will polish this draft and push it to Github.

During the migration to the new responsive design Old Wine in a new Bottle I wasn't able to keep the tag cloud in the first run... maybe I'll grab this one when I've got some free cycles again.