initial commit. Shared module interface which can be implemented by modules. Those modules are placed in an folder structure which allows the main app to automatically detect and load them

This commit is contained in:
2026-09-01 12:26:36 +02:00
commit e27e1d92ee
10 changed files with 158 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
# Byte-compiled / cache
__pycache__/
*.py[cod]
*$py.class
# Virtual environments
venv/
.venv/
env/
ENV/
# Distribution / packaging
build/
dist/
*.egg-info/
*.egg
# Environment / secrets
.env
.env.*
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db
# Testing
.pytest_cache/
.coverage
htmlcov/
# Flask
instance/
.webassets-cache
# Logs
*.log
# Local storage (your module data — probably don't want to commit this)
storage/
+1
View File
@@ -0,0 +1 @@
from .basemodule import BaseModule
+20
View File
@@ -0,0 +1,20 @@
from flask import jsonify, render_template, Blueprint, app
from abc import abstractmethod, ABC
class BaseModule(ABC):
def __init__(self, app: App, name: str, faIcon: str, blueprint: Blueprint):
self.name: str = name
self.faIcon = faIcon
self.blueprint: Blueprint = blueprint
self.blueprint.add_url_rule("/page", view_func=self.get_page)
app.register_blueprint(self.blueprint, url_prefix=f"/modules/{name}")
@abstractmethod
def get_page(self):
# return render template with modules html and css
pass
@abstractmethod
def sync(self):
# return list of errors (parsing errors etc)
pass
+36
View File
@@ -0,0 +1,36 @@
from .basemodule import BaseModule
import importlib
import pkgutil
from pathlib import Path
# written by ai:
def discover_modules(app: Flask, modules_package: str = "modules") -> list[BaseModule]:
"""
Scans the modules/ directory, imports each submodule's module.py,
instantiates its MODULE_CLASS, and returns the list of live instances.
"""
registry: list[BaseModule] = []
package = importlib.import_module(modules_package)
package_path = Path(package.__file__).parent
for _, name, is_pkg in pkgutil.iter_modules([str(package_path)]):
print(name, is_pkg)
if not is_pkg:
continue # skip stray files, only real module folders count
mod_path = f"{modules_package}.{name}.module"
try:
mod = importlib.import_module(mod_path)
except ModuleNotFoundError:
continue # folder without a module.py -> skip silently, or log a warning
cls = getattr(mod, "MODULE_CLASS", None)
if cls is None:
continue # convention not followed — skip, or raise if you want it strict
instance = cls(app=app)
registry.append(instance)
return registry
+13
View File
@@ -0,0 +1,13 @@
from flask import Flask, render_template
from base.registry import discover_modules
app = Flask(__name__)
modules = discover_modules(app)
print(modules)
@app.route("/")
def main_page():
return render_template()
if __name__ == "__main__":
app.run(debug=True)
View File
+1
View File
@@ -0,0 +1 @@
from .module import TestModule
+29
View File
@@ -0,0 +1,29 @@
from typing import override
from flask import Blueprint, render_template, jsonify, app
from base.basemodule import BaseModule
class TestModule(BaseModule):
def __init__(self, app: app):
name = "testmodule"
faIcon = "fa-vial"
blueprint = Blueprint(
"testmodule", __name__,
template_folder="templates",
static_folder="static"
)
blueprint.add_url_rule("/api/data", view_func=self.status_api)
super().__init__(app=app, name=name, faIcon=faIcon, blueprint=blueprint)
def status_api(self):
return jsonify({"status": "ok", "value": 42})
@override
def get_page(self):
return render_template("_page.html")
@override
def sync(self):
print("syncing")
MODULE_CLASS = TestModule
@@ -0,0 +1,7 @@
document.getElementById("testmodule-refresh").addEventListener("click", async () => {
const res = await fetch("/api/testmodule/data");
const data = await res.json();
document.getElementById("testmodule-output").textContent = JSON.stringify(data, null, 2);
document.getElementById("testmodule-synced").textContent = new Date().toLocaleTimeString();
});
+8
View File
@@ -0,0 +1,8 @@
<div class="module-card" id="testmodule-card">
<h3>Test Module</h3>
<p>Last synced: <span id="testmodule-synced">never</span></p>
<button id="testmodule-refresh">Refresh</button>
<div id="testmodule-output"></div>
</div>
<script src="/modules/testmodule/static/module.js"></script>