hookspec.py
Markata's hook specification system for plugin development.
Overview
Markata uses pluggy to define hooks that plugins can implement. These hooks allow plugins to modify Markata's behavior at specific points in the build process.
Hook Types
Configuration Hooks
Used to set up plugin configuration and models:
from markata.hookspec import hook_impl, register_attr @hook_impl @register_attr("config") def config_model(markata): """Add plugin-specific config.""" from pydantic import BaseModel class MyConfig(BaseModel): enabled: bool = True output_file: str = "output.html" return {"my_plugin": MyConfig()} @hook_impl @register_attr("my_data") def configure(markata): """Initialize plugin using config.""" if markata.config.my_plugin.enabled: # Set up plugin resources markata.my_data = []
Content Model Hooks
Define how content is structured:
@hook_impl @register_attr("post_models") def post_model(markata): """Add fields to post model.""" from pydantic import BaseModel, Field class MyPostFields(BaseModel): custom_date: str = Field(None, description="Custom date field") tags: list[str] = Field(default_factory=list) return MyPostFields
Content Processing Hooks
Handle content transformation:
@hook_impl(trylast=True) # Run after other render hooks def render(markata): """Process each article.""" for article in markata.filter("not skip"): # Add custom processing if article.tags: article.tag_links = [f"<a href='/tags/{tag}'>{tag}</a>" for tag in article.tags]
Output Generation Hooks
Control how content is saved:
@hook_impl def save(markata): """Save processed content.""" output_dir = Path(markata.config.output_dir) # Save custom index if markata.config.my_plugin.enabled: index = generate_custom_index(markata.articles) (output_dir / "custom.html").write_text(index)
Hook Ordering
Control execution order with decorators:
# Run first in configure stage @hook_impl(tryfirst=True) def configure(markata): ... # Run in middle (default) @hook_impl def configure(markata): ... # Run last in configure stage @hook_impl(trylast=True) def configure(markata): ...
Attribute Registration
Register data on the Markata instance:
# Single attribute @register_attr("articles") def my_hook(markata): markata.articles = [] # Multiple attributes @register_attr("articles", "tags", "categories") def my_hook(markata): markata.articles = [] markata.tags = {} markata.categories = {} # Access in other hooks @hook_impl def render(markata): print(markata.articles) # Access registered data
Complex Example
Here's a complete plugin example combining multiple hooks:
from pathlib import Path from typing import List, Optional from pydantic import BaseModel, Field from markata.hookspec import hook_impl, register_attr class TagConfig(BaseModel): """Configuration for tag handling.""" enabled: bool = True min_posts: int = 2 output_dir: str = "tags" class TaggedPost(BaseModel): """Add tag fields to posts.""" tags: List[str] = Field(default_factory=list) tag_links: Optional[str] = None @hook_impl @register_attr("config_models") def config_model(markata): """Add tag configuration.""" markata.config_models.append(TagConfig) @hook_impl @register_attr("post_models") def post_model(markata): """Add tag fields to posts.""" markata.post_models.append(TaggedPost) @hook_impl(trylast=True) def render(markata): """Add tag links to articles.""" if not markata.config.tags.enabled: return for article in markata.filter("not skip"): article.tag_links = " ".join( f"<a href='/tags/{tag}'>{tag}</a>" for tag in article.tags ) @hook_impl def save(markata): """Generate tag pages.""" if not markata.config.tags.enabled: return output_dir = Path(markata.config.output_dir) tag_dir = output_dir / markata.config.tags.output_dir tag_dir.mkdir(exist_ok=True) for tag, articles in markata.tags.items(): if len(articles) >= markata.config.tags.min_posts: content = generate_tag_page(tag, articles) (tag_dir / f"{tag}.html").write_text(content)
This example shows:
- Configuration definition
- Model extension
- Data processing
- Content generation
- Output handling
See markata/lifecycle for the exact order hooks are executed.
Class
MarkataSpecs class
Namespace that defines all specifications for Load hooks.
configure -> glob -> load -> render -> save
MarkataSpecs source
class MarkataSpecs: """ Namespace that defines all specifications for Load hooks. configure -> glob -> load -> render -> save """
Function
cli_lifecycle_method function
A Markata lifecycle methos that includes a typer app used for cli's
cli_lifecycle_method source
def cli_lifecycle_method(markata: "Markata", app: "typer.Typer") -> Any: "A Markata lifecycle methos that includes a typer app used for cli's"