Plugins¶
Added in version 1.2.0.
Platzky includes an extensible plugin system that allows you to add custom functionality to your application. Plugins can add notifiers, content transformers, shortcodes, login methods, CMS modules, health checks, dynamic content, and more.
Overview¶
Plugins are ordinary Python packages installed into the same environment as Platzky.
They advertise themselves via the platzky.plugins entry-point group and are
discovered automatically at startup.
Since 1.5.0, plugins are built around capability base classes. Pick the one that matches what your plugin does:
Base class |
When to use |
|---|---|
|
Send notifications (email, Slack, SMS, …) |
|
Same as above, plus handle file attachments |
|
Transform post/page/comment content; register shortcodes |
|
Any other engine customisation (CMS modules, health checks, …) |
All capability classes are importable directly from platzky:
from platzky import (
PluginBase,
NotifierPluginBase,
AttachmentNotifierPluginBase,
ContentTransformerPluginBase,
)
Notifier Plugins¶
Added in version 1.5.0.
Subclass NotifierPluginBase to send notifications. Declare which topics your
plugin handles via accepted_topics; the engine routes notifications to matching
plugins only.
The three built-in topics are "security", "content", and "general".
from typing import Any
from platzky import NotifierPluginBase, NotificationTopic
class SlackNotifier(NotifierPluginBase):
"""Send notifications to a Slack channel."""
accepted_topics: frozenset[NotificationTopic] = frozenset({"general", "security"})
def __init__(self, config: dict[str, Any]) -> None:
super().__init__(config)
self._webhook = config.get("webhook_url", "")
def notify(self, message: str, topic: NotificationTopic, receiver: str = "") -> None:
# post to self._webhook …
pass
With attachments
Subclass AttachmentNotifierPluginBase instead when your plugin needs to handle
files. Implement notify_with_attachments; the base class delegates plain
notify calls to it with an empty attachment list automatically.
from collections.abc import Sequence
from typing import Any
from platzky import AttachmentNotifierPluginBase, NotificationTopic
from platzky.attachment import AttachmentProtocol
class MailNotifier(AttachmentNotifierPluginBase):
"""Email notifier with attachment support."""
accepted_topics: frozenset[NotificationTopic] = frozenset({"content"})
def __init__(self, config: dict[str, Any]) -> None:
super().__init__(config)
self._to = config.get("recipient", "")
def notify_with_attachments(
self,
message: str,
topic: NotificationTopic,
attachments: Sequence[AttachmentProtocol],
receiver: str = "",
) -> None:
# send email …
pass
Content Transformer Plugins¶
Added in version 1.5.0.
Subclass ContentTransformerPluginBase to modify post, page, or comment content
before rendering. Declare which content types to process via accepted_content_types.
The three content types are "post", "page", and "comment".
from typing import Any
from platzky import ContentTransformerPluginBase, ContentType
class EmojiPlugin(ContentTransformerPluginBase):
"""Replace :smile: tokens with emoji in posts and pages."""
accepted_content_types: frozenset[ContentType] = frozenset({"post", "page"})
def transform_text(self, text: str) -> str:
return text.replace(":smile:", "😊")
Override transform_text to apply plain-text transformations. The framework
guarantees that shortcode tags are excluded from the text passed here and
re-inserted after transformation. transform_content is @final and must
not be overridden.
Shortcodes¶
Content transformer plugins can also register shortcodes — bracket-style tags that content authors embed in posts and pages.
Syntax
[tagname attr="val"] # void (no inner content)
[tagname attr="val"]content[/tagname] # block
Declare shortcodes as a class variable:
from typing import ClassVar
from markupsafe import Markup, escape
from platzky import ContentTransformerPluginBase, ContentType
from platzky.shortcodes import Shortcode, ShortcodeAttrs, ShortcodeAttr
class _AlertShortcode(Shortcode):
name = "alert"
description = "Render content inside a Bootstrap alert box."
attributes = ShortcodeAttrs([
ShortcodeAttr("type", "Alert type: info, warning, danger", required=False),
])
example = '[alert type="warning"]Watch out![/alert]'
def render(self, attrs: ShortcodeAttrs, content: str) -> str:
kind = attrs.type or "info"
return str(Markup('<div class="alert alert-{}">{}</div>').format(escape(kind), escape(content)))
class AlertPlugin(ContentTransformerPluginBase):
"""Adds an [alert] shortcode for Bootstrap alert boxes."""
accepted_content_types: frozenset[ContentType] = frozenset({"post", "page"})
shortcodes: ClassVar[dict[str, Shortcode]] = {"alert": _AlertShortcode()}
Built-in shortcodes
Platzky ships two shortcodes that are always available:
[image url="…" alt="…" width="…" height="…"]Embeds an
<img>tag.urlis required.[link url="…" target="…"]text[/link]Creates an
<a>tag.urlis required;target="_blank"automatically addsrel="noopener noreferrer".
Both reject non-HTTP/HTTPS external URLs and relative paths not starting with /.
Shortcodes are documented for content authors on the admin Help page
(/admin/help).
Other Engine Extensions¶
For capabilities that don’t yet have a dedicated class — CMS modules, login methods,
health checks, or injecting dynamic HTML — override process() on a plain
PluginBase subclass:
from typing import Any
from platzky.plugin.plugin import PluginBase
from platzky.engine import Engine
class AnalyticsPlugin(PluginBase):
"""Injects the analytics script and registers a health check."""
def __init__(self, config: dict[str, Any]) -> None:
super().__init__(config)
self._tag = config.get("script_tag", "")
def process(self, app: Engine) -> Engine:
app.add_dynamic_body(self._tag)
app.add_health_check("analytics", lambda: None)
return app
Available Engine extension points:
add_cms_module(module)Add a CMS module that appears in the admin panel.
add_login_method(login_method)Register an additional admin login method.
add_dynamic_body(html)Append HTML to every page’s
<body>(scripts, widgets).add_dynamic_head(html)Append HTML to every page’s
<head>(stylesheets, meta tags).add_health_check(name, check_fn)Register a check included in the
/health/readinessendpoint. The function should raise on failure.is_enabled(flag)Check whether a feature flag is enabled.
Note
process() is deprecated since 1.5.0 and will be removed in 2.0.0. Where
possible, use the capability subclasses above. Engine extension points for CMS
modules, health checks, and dynamic HTML will gain dedicated capability classes
in a future release.
Packaging a Plugin¶
Plugins are discovered via the platzky.plugins entry-point group. Declare your
plugin class in pyproject.toml:
[tool.poetry.plugins."platzky.plugins"]
my_plugin = "platzky_my_plugin:MyPlugin"
The key (my_plugin) is the name used in the database configuration.
Plugin Configuration¶
After the package is installed, activate the plugin by adding it to the plugins
list in your database:
{
"plugins": [
{
"name": "my_plugin",
"config": { "api_key": "abc123" }
}
]
}
The config object is passed as a dict[str, Any] to the plugin’s __init__.
For notifier plugins you can restrict which topics the plugin receives:
{
"name": "slack_notifier",
"config": { "webhook_url": "https://hooks.slack.com/…" },
"allowed_topics": ["security", "general"]
}
For content transformer plugins you can restrict which content types are processed:
{
"name": "alert_plugin",
"config": {},
"allowed_content_types": ["post", "page"]
}
Admin Help Page¶
Loaded plugins and their shortcodes are listed on the admin Help page
(/admin/help). Override get_info() to provide a user-facing name and
description:
from platzky.plugin.plugin import PluginBase, PluginInfo
class MyPlugin(PluginBase):
def get_info(self) -> PluginInfo:
return PluginInfo(name="My Plugin", description="Does something useful.")
Listing Installed Plugins¶
discover_plugins() returns all plugins installed in the current environment,
regardless of which ones are active in the database:
from platzky import discover_plugins
for name, cls in discover_plugins().items():
print(name, cls)
Translation Support¶
Plugins can provide their own translation files. Place them under a locale/
directory inside your plugin package:
platzky_myplugin/
__init__.py
plugin.py
locale/
en/
LC_MESSAGES/
messages.po
messages.mo
pl/
LC_MESSAGES/
messages.po
messages.mo
PluginBase.get_locale_dir() discovers the directory automatically. Platzky
registers it with Flask-Babel during plugin loading.
Legacy Plugins¶
Deprecated since version 1.2.0: Module-style legacy plugins are deprecated and will be removed in 2.0.0. Use a class-based capability subclass instead.
Legacy plugins are plain modules with a process function:
def process(app, config):
return app
This style does not support configuration validation, translation discovery, or capability routing. Migrate to a class-based subclass.