Extending Hatch With MkDocs
Context
I recently have been using the project hatch
for my Python project management.
I love the tool so far. One command that I have been using a lot is the hatch new
command.
This command allows you to quickly create a Python project with all the essentials like a README.md
,
a pyproject.toml
, linting, etc.
I have been trying to be more consistent about writing documentation for my projects.
One task I have been manually doing a lot is adding MkDocs to my Python projects.
I thought it would be interesting to automatically add MkDocs to the hatch new
templates.
Creating The Plugin
I initialized a new project with hatch new -i
.
After completing the prompts, I have a hatch-mkdocs-template
project.
Hatch uses Pluggy as its plugin manager. It defines the following interface for Template plugins:
# src/hatch/template/plugin/interface.py
class TemplateInterface:
PLUGIN_NAME = ''
PRIORITY = 100
def __init__(self, plugin_config: dict, cache_dir, creation_time):
self.plugin_config = plugin_config
self.cache_dir = cache_dir
self.creation_time = creation_time
def initialize_config(self, config):
"""
Allow modification of the configuration passed to every file for new projects
before the list of files are determined.
"""
def get_files(self, config): # noqa: ARG002, PLR6301
"""Add to the list of files for new projects that are written to the file system."""
return []
def finalize_files(self, config, files):
"""Allow modification of files for new projects before they are written to the file system."""
I wrote the following hooks.py
, plugin.py
, and file_templates.py
:
# hooks.py
from hatchling.plugin import hookimpl
from .plugin import MkdocsTemplate
@hookimpl
def hatch_register_template():
print("Registering!")
return MkdocsTemplate
# plugin.py
from hatch.template.plugin.interface import TemplateInterface
from hatch.template import find_template_files
from . import file_templates
class MkdocsTemplate(TemplateInterface):
PLUGIN_NAME = 'mkdocs-template'
def get_files(self, config):
return list(find_template_files(file_templates))
# file_templates.py
from hatch.template import File
from hatch.utils.fs import Path
class MkdocsYaml(File):
TEMPLATE = """\
site_name: {project_name} Docs
nav:
- Home: 'index.md'
theme: {theme}
"""
def __init__(self, template_config: dict, plugin_config: dict):
# defaults
theme = "readthedocs"
if "theme" in plugin_config:
theme = plugin_config["theme"]
super().__init__(Path("mkdocs.yaml"), self.TEMPLATE.format(theme=theme,**template_config))
class IndexMd(File):
TEMPLATE = """\
# {project_name}
{proj_description}
## Features
- Awesome Feature!
"""
def __init__(self, template_config: dict, plugin_config: dict):
#defaults
proj_description = "This is a super cool project."
if template_config["description"] != "":
proj_description = template_config["description"]
super().__init__(Path("docs/index.md"), self.TEMPLATE.format(proj_description=proj_description,**template_config))
I registered the plugin in the pyproject.toml
:
[project.entry-points.hatch]
mkdocs = "hatch_mkdocs_template.hooks"
That is pretty much the whole plugin. For more information about creating plugins, look at
these hatch
docs.
Thoughts After Creating The Plugin
- I really like the
File
abstraction thathatch
uses for templates. I previously wrote a similar abstraction in a project, and I chose to make all templates use Jinja2. However, forcing the user to use Jinja2 templates feels like the wrong choice now. It is much more flexible to take a string and allow the user to use whatever templates they want.
# src/hatch/template/__init__.py
class File:
def __init__(self, path: Path | None, contents: str = ''):
self.path = path
self.contents = contents
self.feature = None
def write(self, root):
if self.path is None: # no cov
return
path = root / self.path
path.ensure_parent_dir_exists()
path.write_text(self.contents, encoding='utf-8')
-
One thing I disliked is that in
TemplateInterface
,template_config
andplugin_config
variables are dictionaries. I found myself glancing at the code to understand what variables they contained. I feel like I would appreciate them being a data class or something. However, I do realize the dict allows for lots of flexibility, so I feel conflicted. -
The Template plugin is not documented in the
hatch
docs currently. After glancing at the discussions, I learned that the developer is not satisfied with the API yet, so they haven’t documented it. They felt like it was not very scalable yet. I look forward to seeing how the interface changes and writing more template plugins in the future.
You can track the discussion here.
Closing Thoughts
It was really easy to write this plugin.
I love the hatch
project.
If you haven’t checked out the code base, you definitely should.
The maintainer is doing a great job.
This project is the first example of Python plugin architecture I have been exposed to and extending it was great.
I’m looking forward to seeing how the project grows.
My example plugin can be found here.