I’ve just released a groundbreaking version of my website theme Seafoam; I believe this is the first Pelican theme designed to configure itself!
The root of this magic is that the theme has actually been packaged as a namespace plugin for Pelican. If you’re running a sufficiently recent version of Pelican (i.e. v4.5 or newer), Pelican will automatically load the Seafoam plugin; if you’re using an older version of Pelican (or other, non-namespace plugins), you’ll need to add the Seafoam plugin to you configuration:
# pelicanconf.py PLUGINS = [ "pelican.plugins.seafoam", # others, as needed/desired ] # the rest of your configuration file
Unless you need to change Seafoam‘s behaviour from its defaults, you shouldn’t need do any further configuration!
Shiny! How Do I Do This For My Theme?
Start With a Namespace Plugin
A namespace plugin works its magic by have the code files in the right folder (i.e. “namespace”). For Pelican, we want those code files under
pelican/plugins. So our file layout looks like this:
<project root> +- pelican | `- plugins | `- my_theme | `- __init__.py `- setup.py
Of particular note is that both the
plugins directories have no Python code directly in them.
Add Your Theme Files
For this article, I’m going to treat the actual creation of a theme as out-of-scope and assume you already have a theme. Your theme will consist of a
templates folder, containing the Jinja21 templates with
.html file extensions, and possibly a
<project root> +- pelican | `- plugins | `- my_theme | + static | | +- css | | +- js | | `- images | +- templates | | +- index.html | | `- <other templates> | `- __init__.py `- setup.py
Find Our Templates
Next, we’re going to add a function to our theme’s
__init__.py file that will point to the location of our theme:
# pelican/plugins/my_theme/__init__.py from pathlib import Path def get_path(): # Theme directory is defined as our parent directory return str(Path(__file__).resolve().parent)
If we didn’t do anything more, in our
pelicanconf.py, we could:
# pelicanconf.py from pelican.plugins import my_theme THEME = my_theme.get_path()
(And this is exactly how Seafoam worked previously.)
Automatically Activate Our Theme
Now we get into the real magic, to which there are two parts: 1) a function to set the theme, and 2) hook that function into Pelican’s signals.
The function to set the theme is rather simple, but note that we set
pelican_obj.theme directly rather than
pelican_obj.settings["THEME"], as this setting appears to already have been read.
# pelican/plugins/my_theme/__init__.py def set_theme(pelican_obj): pelican_obj.theme = get_path()
If we wanted to, we could extend the above function to massage Pelican’s settings (this is where Seafoam sets its defaults).
For signals, these are “hooks” that allow you to run custom code as Pelican goes through the process of generating your site. We’re going to hook into the very first signal that Pelican offers:
# pelican/plugins/my_theme/__init__.py from pelican import signals def register(): signals.initialized.connect(set_theme)
Complete Python Code
Adding a couple of niceties (logging, a version number) and above, we end up with something like this:
# pelican/plugins/my_theme/__init__.py import logging from pathlib import Path from pelican import signals __version__ = "1.0.0" logger = logging.get_logger(__name__) def get_path(): return str(Path(__file__).resolve().parent) def set_theme(pelican_obj): pelican_obj.theme = get_path() logger.debug("[My Theme] Theme set!") def register(): signals.initialized.connect(set_theme)
Tips for Publishing
When I went to publish the updated package to PyPI, I found I had to switch from
setuptools.find_namespace_packages() in my theme’s
setup.py. I also had to add the non-Python files to my
# MANIFEST.in recursive-include pelican/plugins/seafoam/static *.* recursive-include pelican/plugins/seafoam/templates *.*
Finally, be sure to include the
Framework :: Pelican :: Themes PyPI classifier so others can find your work!
If I missed something in this mini-tutorial, but sure to leave a comment or send me an email so I can expand/fix it.
Upgrading should is straight forward; I haven’t broken anything on purpose since v2.0.0 came out. With this release several previously mandatory settings are now optional to specify and so you can remove them if you want, but keeping them in place shouldn’t cause any problems.
To install or to upgrade, you can use pip:
pip install seafoam --upgrade
If you’re already running Pelican v4.5 (or newer) and only using namespace plugins, then the required plugins will automatically load. However, most will have to update your
pelicanconf.py to point to the new plugin names:
# pelicanconf.py PLUGINS = [ "pelican.plugins.seafoam", "pelican.plugins.jinja_filters", # <-- optional now "pelican.plugins.image_process", # <-- optional now # others, as desired... ]
All other settings, if it was just to apply the default settings for Seafoam, can now be removed.
To be clear, Seafoam still supports Pelican 3 (i.e. you don’t need to upgrade to Pelican 4.5 quite yet).
This is just a section where I make some notes on the issues I ran into while working on the code.
PEP517 & PEP518 are accepted extensions to Python dealing with the ability to specify a “build backend”, i.e. how does a given module package itself up for distribution. The way to do this is to add a few lines to your
pyproject.toml file (the new place to collect settings and metadata about your Python project) like so:
# pyproject.toml [build-system] requires = ["setuptools >= 40.6.0", "wheel"] build-backend = "setuptools.build_meta"
I figured I would do this to this latest version of Seafoam as this is the way of the future there really wasn’t any obvious downside. However, I discovered a downside: when you go to build such a package from source, it wants to create a isolated environment for doing so, and as part of that, it wants to download setuptools and wheel (really, whatever you specify in the
build-system key) and doesn’t seem to rely on either the system libraries or the system’s pip download cache. Normally, this wouldn’t be an issue, but if you try and install such a package from the Test PyPI server, it also tries to download and install the build requirements from there too! And many packages, including setuptools are never posted to the Test PyPI server.
There may be a workaround, but I have somewhat limited internet at the moment so I haven’t been able to research the issue. I should probably raise an issue, somewhere….
Summary: Source distributions with a PEP517/518 setuptools build system are unable to be installed from the Test PyPI server.
Update (July 7th): The answer is to call pip with
--no-build-isolation. This relies on the environment being installed into to provide the needed packages to actually do the install, and as a byproduct allows offline installs and installs when the “main” package is hosted on the Test PyPI server.
This release actually resolves mosts of the future plans I’d written for the v2.5 release. At this point, further development will likely be tied to my website demands directly.
One thing I thought about doing was to make the tag pages (called “labels” on this site) more powerful by adding the ability to add a custom text header to them. For example, the current “homepage” link for Seafoam is given as such a tag page (http://blog.minchin.ca/label/seafoam/) and this would be handy to do for all my Python projects, as most of the project history is already collected here as blog posts. I haven’t begun to look at this, so I’m not sure if it could be done from the Jinja templates alone, or if it would require adjusting the Pelican output with an extention, either as part of Seafoam or a separate plugin.
Also, as I wrote previously, there are long term plans to upgrade to Bootstrap 5, but nothing started there.
See my previous post for earlier changelog entries.
Version 2.6.0 — July 5, 2021
- feature add internal plugin. This will allow the theme to automatically configure and activate itself. Should significantly reduced installation complexity. You may be able to completely remove the configuration you have in place for the plugin.
- feature include Seafoam version in source HTML of generated sites
- bug adjust HTML to add the
.tableclass where needed, rather than applying the formatting to all HTML tables. Effectively a re-work of v.2.4.7.
- support add screenshots. See issues #1 and #18.
- support updated
setup.py. Include tempalate and static files at new location.
- support no longer include raw LESS files in distributions or in generated sites.
- support now also requires
Jinja version 3 has now been released, so what does this package go by now? Jinja3? Jinja2 v3? plain old Jinja? In any case,
jinja2seems to install version 3 off of PyPI. ↩