diff --git a/schilder2000/helpers.py b/schilder2000/helpers.py
index b7be6615d617a29744418c96b8af8ad33c7f0df0..1a6a5752bd1ba66d9adb2708a4848347af227971 100644
--- a/schilder2000/helpers.py
+++ b/schilder2000/helpers.py
@@ -1,6 +1,11 @@
 import typing as t
 
-from flask import Flask as _Flask, Blueprint as FlaskBlueprint, render_template
+from flask import (
+    Flask as _Flask,
+    Blueprint as FlaskBlueprint,
+    current_app,
+    render_template,
+)
 
 from jinja2 import BaseLoader, ChoiceLoader, PrefixLoader, Template
 
@@ -43,3 +48,41 @@ class Flask(_Flask):
             self.jinja_env.loader.loaders[0].mapping[blueprint.name] = (
                 blueprint.jinja_loader
             )
+
+
+_sentinel = object()
+
+
+def get_template_attribute(
+    template_name: str,
+    attribute: str,
+    default: t.Any = _sentinel,
+    vars: t.Dict[str, t.Any] | None = None,
+    shared: bool = False,
+    locals: t.Mapping[str, t.Any] | None = None,
+) -> t.Any:
+    """Loads a macro (or variable) a template exports.  This can be used to
+    invoke a macro from within Python code.  If you for example have a
+    template named :file:`_cider.html` with the following contents:
+
+    .. sourcecode:: html+jinja
+
+       {% macro hello(name) %}Hello {{ name }}!{% endmacro %}
+
+    You can access this from Python code like this::
+
+        hello = get_template_attribute('_cider.html', 'hello')
+        return hello('World')
+
+    .. versionadded:: 0.2
+
+    :param template_name: the name of the template
+    :param attribute: the name of the variable of macro to access
+    """
+    mod = current_app.jinja_env.get_template(template_name).make_module(
+        vars, shared, locals
+    )
+    if default is _sentinel:
+        return getattr(mod, attribute)
+    else:
+        return getattr(mod, attribute, default)