Attributes

Description

An attribute plugin is simply an AbstractAttributePlugin subclass designed to implement a Jinja filter.

Requirements

Here is a commented minimal plugin implementation that won’t do anything.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Required to subclass AbstractAttributePlugin
from lib.plugins import AbstractAttributePlugin

# Required to use the Jinja Undefined state
from jinja2 import Undefined

# Required for type hints
from typing import Any

# Required to indicate to hermes which class it has to instantiate
HERMES_PLUGIN_CLASSNAME = "MyPluginClassName"

class MyPluginClassName(AbstractAttributePlugin):
    def __init__(self, settings: dict[str, any]):
        # Instantiate new plugin and store a copy of its settings dict in self._settings
        super().__init__(settings)
        # ... plugin init code

    def filter(self, value: Any | None | Undefined) -> Any:
        # Filter that does nothing
        return value

filter method

You should consider reading the official Jinja documentation about custom filters.

The filter() method always takes at least one value parameter, and may have some other.

Its generic prototype is:

def filter(self, value: Any | None | Undefined, *args: Any, **kwds: Any) -> Any:

In Jinja, it is called with:

"{{ value | filter }}"
"{{ value | filter(otherarg1, otherarg2) }}"
"{{ value | filter(otherarg1=otherarg1_value, otherarg2=otherarg2_value) }}"

The above expressions are replaced by the filter return value.

Example: the datetime_format attribute plugin

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Required to subclass AbstractAttributePlugin
from lib.plugins import AbstractAttributePlugin

# Required to use the Jinja Undefined state
from jinja2 import Undefined

# Required for type hints
from typing import Any

from datetime import datetime

# Required to indicate to hermes which class it has to instantiate
HERMES_PLUGIN_CLASSNAME = "DatetimeFormatPlugin"

class DatetimeFormatPlugin(AbstractAttributePlugin):
    def filter(self, value:Any, format:str="%H:%M %d-%m-%y") -> str | Undefined:
        if isinstance(value, Undefined):
            return value

        if not isinstance(value, datetime):
            raise TypeError(f"""Invalid type '{type(value)}' for datetime_format value: must be a datetime""")

        return value.strftime(format)

This filter can now be called with:

"{{ a_datetime_attribute | datetime_format }}"
"{{ a_datetime_attribute | datetime_format('%m/%d/%Y, %H:%M:%S') }}"
"{{ a_datetime_attribute | datetime_format(format='%m/%d/%Y') }}"