Files
natureinpots_community/app/__init__.py
2025-06-06 02:00:05 -05:00

116 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# app/__init__.py
import os
import json
import glob
import importlib
import importlib.util
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_wtf.csrf import CSRFProtect
from dotenv import load_dotenv
# Load environment variables from .env or system
load_dotenv()
# ─── Initialize core extensions ─────────────────────────────────────────────────
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
csrf = CSRFProtect()
def create_app():
app = Flask(__name__)
app.config.from_object('app.config.Config')
# ─── Initialize extensions with the app ───────────────────────────────────────
csrf.init_app(app)
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
login_manager.login_view = 'auth.login'
# ─── Register user_loader for Flask-Login ───────────────────────────────────
from plugins.auth.models import User
@login_manager.user_loader
def load_user(user_id):
try:
return User.query.get(int(user_id))
except Exception:
return None
# ─── Register error handlers ─────────────────────────────────────────────────
from .errors import bp as errors_bp
app.register_blueprint(errors_bp)
# ─── 1) Autoimport plugin models by their package names ─────────────────────
# This ensures that every plugins/<plugin>/models.py is imported exactly once
plugin_model_paths = glob.glob(
os.path.join(os.path.dirname(__file__), '..', 'plugins', '*', 'models.py')
)
for path in plugin_model_paths:
# path looks like ".../plugins/plant/models.py"
rel = path.split(os.sep)[-2] # e.g. "plant"
pkg = f"plugins.{rel}.models" # e.g. "plugins.plant.models"
try:
importlib.import_module(pkg)
print(f"✅ (Startup) Loaded: {pkg}")
except Exception as e:
print(f"❌ (Startup) Failed to load {pkg}: {e}")
# ─── 2) Autodiscover & register plugin routes, CLI, entrypoints ────────────
plugin_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'plugins'))
for plugin in os.listdir(plugin_path):
if plugin.endswith('.noload'):
print(f"[⏭] Skipping plugin '{plugin}' (marked as .noload)")
continue
plugin_dir = os.path.join(plugin_path, plugin)
if not os.path.isdir(plugin_dir):
continue
# (a) Register routes.py
route_file = os.path.join(plugin_dir, 'routes.py')
if os.path.isfile(route_file):
try:
spec = importlib.util.spec_from_file_location(f"plugins.{plugin}.routes", route_file)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
if hasattr(mod, 'bp'):
app.register_blueprint(mod.bp, strict_slashes=False)
print(f"✔️ Registered routes for plugin '{plugin}'")
except Exception as e:
print(f"❌ Failed to load routes from plugin '{plugin}': {e}")
# (b) Register CLI and entrypoint
init_file = os.path.join(plugin_dir, '__init__.py')
plugin_json = os.path.join(plugin_dir, 'plugin.json')
if os.path.isfile(init_file):
try:
cli_module = importlib.import_module(f"plugins.{plugin}")
if hasattr(cli_module, 'register_cli'):
cli_module.register_cli(app)
print(f"✔️ Registered CLI for plugin '{plugin}'")
if os.path.isfile(plugin_json):
with open(plugin_json, 'r') as f:
meta = json.load(f)
entry = meta.get('entry_point')
if entry and hasattr(cli_module, entry):
getattr(cli_module, entry)(app)
print(f"✔️ Ran entry point '{entry}' for plugin '{plugin}'")
except Exception as e:
print(f"❌ Failed to load CLI for plugin '{plugin}': {e}")
# ─── Inject current year into templates ────────────────────────────────────────
@app.context_processor
def inject_current_year():
from datetime import datetime
return {'current_year': datetime.now().year}
return app