@@ -143,6 +161,9 @@
/>} {props.label}
+
+ { calc_per_second(props.data[props.data.length-1], props.data[props.data.length-2]) }
+
{ props.data.length < 2 ? :
diff --git a/mkdocs.rtd.yml b/mkdocs.rtd.yml
index b6570b8..727b7fe 100644
--- a/mkdocs.rtd.yml
+++ b/mkdocs.rtd.yml
@@ -40,6 +40,7 @@ nav:
- Plugins:
- Packaged: packaged_plugins.md
- Custom: custom_plugins.md
+ - Contributed: contrib_plugins.md
- Configuration:
- Broker: references/broker_config.md
- Client: references/client_config.md
@@ -139,7 +140,7 @@ plugins:
ignore_init_summary: true
docstring_section_style: list
filters: ["!^_"]
- heading_level: 1
+ heading_level: 2
inherited_members: true
merge_init_into_class: true
parameter_headings: true
diff --git a/pyproject.toml b/pyproject.toml
index 4a2b35f..d568aa7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,7 @@ classifiers = [
"Programming Language :: Python :: 3.13"
]
-version = "0.11.1"
+version = "0.11.3-rc.1"
requires-python = ">=3.10.0"
readme = "README.md"
license = { text = "MIT" }
diff --git a/samples/broker_custom_plugin.py b/samples/broker_custom_plugin.py
new file mode 100644
index 0000000..82850a8
--- /dev/null
+++ b/samples/broker_custom_plugin.py
@@ -0,0 +1,85 @@
+import asyncio
+import logging
+import os
+from dataclasses import dataclass
+from pathlib import Path
+
+from amqtt.broker import Broker
+from amqtt.plugins.base import BasePlugin
+from amqtt.session import Session
+
+"""
+This sample shows how to run a broker without stacktraces on keyboard interrupt
+"""
+
+logger = logging.getLogger(__name__)
+
+
+class RemoteInfoPlugin(BasePlugin):
+
+ async def on_broker_client_connected(self, *, client_id:str, client_session:Session) -> None:
+ display_port_str = f"on port '{client_session.remote_port}'" if self.config.display_port else ''
+
+ logger.info(f"client '{client_id}' connected from"
+ f" '{client_session.remote_address}' {display_port_str}")
+
+ @dataclass
+ class Config:
+ display_port: bool = False
+
+config = {
+ "listeners": {
+ "default": {
+ "type": "tcp",
+ "bind": "0.0.0.0:1883",
+ },
+ "ws-mqtt": {
+ "bind": "127.0.0.1:8080",
+ "type": "ws",
+ "max_connections": 10,
+ },
+ },
+ "plugins": {
+ 'amqtt.plugins.authentication.AnonymousAuthPlugin': { 'allow_anonymous': True},
+ 'samples.broker_custom_plugin.RemoteInfoPlugin': { 'display_port': True },
+ }
+}
+
+async def main_loop():
+ broker = Broker(config)
+ try:
+ await broker.start()
+ while True:
+ await asyncio.sleep(1)
+ except asyncio.CancelledError:
+ await broker.shutdown()
+
+async def main():
+ t = asyncio.create_task(main_loop())
+ try:
+ await t
+ except asyncio.CancelledError:
+ pass
+
+def __main__():
+
+ formatter = "[%(asctime)s] :: %(levelname)s :: %(name)s :: %(message)s"
+ logging.basicConfig(level=logging.INFO, format=formatter)
+
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ task = loop.create_task(main())
+
+ try:
+ loop.run_until_complete(task)
+ except KeyboardInterrupt:
+ logger.info("KeyboardInterrupt received. Stopping server...")
+ task.cancel()
+ loop.run_until_complete(task) # Ensure task finishes cleanup
+ finally:
+ logger.info("Server stopped.")
+ loop.close()
+
+if __name__ == "__main__":
+ __main__()
diff --git a/tests/contrib/test_contrib.py b/tests/contrib/test_contrib.py
new file mode 100644
index 0000000..e69de29
diff --git a/uv.lock b/uv.lock
index fd26736..6c38fc2 100644
--- a/uv.lock
+++ b/uv.lock
@@ -9,7 +9,7 @@ resolution-markers = [
[[package]]
name = "amqtt"
-version = "0.11.1"
+version = "0.11.3rc1"
source = { editable = "." }
dependencies = [
{ name = "dacite" },