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:
+43
@@ -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/
|
||||
@@ -0,0 +1 @@
|
||||
from .basemodule import BaseModule
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
from .module import TestModule
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user