Application factory.
(db_path=None)
| 83 | |
| 84 | |
| 85 | def create_app(db_path=None): |
| 86 | """Application factory.""" |
| 87 | app = Flask(__name__) |
| 88 | app.config.from_object(config) |
| 89 | |
| 90 | db = db_path or config.DB_PATH |
| 91 | if db: |
| 92 | app.config["DB_PATH"] = db |
| 93 | |
| 94 | from explainshell.web.views import bp, debug_bp |
| 95 | |
| 96 | app.register_blueprint(bp) |
| 97 | if config.DEBUG: |
| 98 | app.register_blueprint(debug_bp) |
| 99 | |
| 100 | @app.teardown_appcontext |
| 101 | def close_request_store(exc: BaseException | None) -> None: |
| 102 | s = g.pop("store", None) |
| 103 | if s is not None: |
| 104 | s.close() |
| 105 | |
| 106 | # Read the DB SHA256 once at startup. The file is computed at Docker |
| 107 | # build time (see Dockerfile); it won't exist in dev unless created |
| 108 | # manually. |
| 109 | sha_path = (app.config.get("DB_PATH") or "") + ".sha256" |
| 110 | if os.path.isfile(sha_path): |
| 111 | with open(sha_path) as f: |
| 112 | db_sha256 = f.read().strip() |
| 113 | logger.info("db sha256: %s", db_sha256) |
| 114 | else: |
| 115 | db_sha256 = "local" |
| 116 | app.config["DB_SHA256"] = db_sha256 |
| 117 | |
| 118 | # APP_VERSION captures the deployed code identity. Combined with |
| 119 | # DB_SHA256 it forms the ETag served on /explain responses, so |
| 120 | # caches invalidate on any code change or DB rebuild. |
| 121 | project_root = os.path.dirname( |
| 122 | os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 123 | ) |
| 124 | git_sha = _get_git_sha(project_root) |
| 125 | logger.info("git sha: %s", git_sha) |
| 126 | # Truncate the hex prefix to 8 chars but preserve any suffix (e.g. |
| 127 | # '-dirty') so a dirty deploy's ETag still differs from a clean one. |
| 128 | hex_part, sep, tail = git_sha.partition("-") |
| 129 | app.config["APP_VERSION"] = hex_part[:8] + (sep + tail if sep else "") |
| 130 | |
| 131 | # Snapshot the distro list at startup. Used by get_distros() and |
| 132 | # /health — both are served from memory, no per-request DB work. |
| 133 | # The DB is read-only and baked into the Docker image, so distros |
| 134 | # only change when a new process boots. |
| 135 | startup_distros: list[tuple[str, str]] = [] |
| 136 | db_path = app.config.get("DB_PATH") |
| 137 | if db_path and os.path.isfile(db_path): |
| 138 | boot_store = store.Store(db_path, read_only=True) |
| 139 | try: |
| 140 | startup_distros = list(boot_store.distros()) |
| 141 | finally: |
| 142 | boot_store.close() |