""" extensions.py ============= Shared Flask extension instances (SQLAlchemy db + AppLogger). All Blueprints import from here to avoid circular imports. Initialization order (enforced in app.py): 1. db = SQLAlchemy() -- created here at module level 2. app.py configures Flask app 3. db.init_app(app) -- called in app.py 4. set_db(db) -- unpacks model classes 5. init_logger(app, db) -- binds logger_handler here 6. Blueprints are registered """ from flask_sqlalchemy import SQLAlchemy from logger_handler import AppLogger # --------------------------------------------------------------------------- # Database — single shared instance # --------------------------------------------------------------------------- db = SQLAlchemy() # --------------------------------------------------------------------------- # Application-level logger # --------------------------------------------------------------------------- # `logger_handler` is a PROXY, not the AppLogger itself. # # Modules imported before create_app() calls init_logger() — utils/helpers.py, # utils/template_helpers.py, utils/geocoding.py — bind whatever object this name # holds at import time, and `from extensions import logger_handler` copies the # reference. While that was a plain None, any later call such as # `logger_handler.logger.warning(...)` raised # AttributeError: 'NoneType' object has no attribute 'logger' # (Sept 16 2026: a blocked project manager got a 500 instead of a redirect, # because the access-denied path logs a warning.) # # The proxy forwards to the real AppLogger once init_logger() has built it, and # to a plain stdlib logger before that, so logging can never break a request. import logging as _logging _logger_instance = None # the real AppLogger, set by init_logger() class _FallbackLogger: """Stand-in used before init_logger() has run: logs, never raises.""" def __init__(self): self.logger = _logging.getLogger('qr_attendance_app') def __getattr__(self, name): def _noop(*args, **kwargs): self.logger.debug(f"{name}() called before init_logger()") return _noop _fallback_logger = _FallbackLogger() class _LoggerHandlerProxy: """Forwards every attribute to the real AppLogger, or to _FallbackLogger.""" def __getattr__(self, name): return getattr(_logger_instance or _fallback_logger, name) def __repr__(self): return f" {_logger_instance or _fallback_logger!r}>" logger_handler = _LoggerHandlerProxy() def init_logger(app, database) -> AppLogger: """ Instantiate AppLogger and bind it behind the module-level ``logger_handler`` proxy, so every module that did ``from extensions import logger_handler`` — whenever it was imported — ends up using this fully-initialized instance. """ global _logger_instance _logger_instance = AppLogger(app, database) return _logger_instance