111 lines
4.1 KiB
Python
111 lines
4.1 KiB
Python
"""MiroFish backend Flask application factory."""
|
|
|
|
import os
|
|
import warnings
|
|
|
|
# Silence multiprocessing.resource_tracker warnings emitted by some third-party
|
|
# libraries (e.g. transformers); must run before those modules are imported.
|
|
warnings.filterwarnings("ignore", message=".*resource_tracker.*")
|
|
|
|
from flask import Flask, request
|
|
from flask_cors import CORS
|
|
|
|
from .config import Config
|
|
from .utils.logger import setup_logger, get_logger
|
|
from .utils.locale import t
|
|
|
|
|
|
def create_app(config_class=Config):
|
|
"""Flask application factory."""
|
|
app = Flask(__name__)
|
|
app.config.from_object(config_class)
|
|
|
|
# Configure JSON encoding so non-ASCII characters render literally
|
|
# rather than as \uXXXX escape sequences. Flask >= 2.3 exposes
|
|
# ``app.json.ensure_ascii``; older versions use ``JSON_AS_ASCII``.
|
|
if hasattr(app, 'json') and hasattr(app.json, 'ensure_ascii'):
|
|
app.json.ensure_ascii = False
|
|
|
|
# Configure logging.
|
|
logger = setup_logger('mirofish')
|
|
|
|
# Only print startup banners in the reloader child process to avoid
|
|
# double-printing in debug mode.
|
|
is_reloader_process = os.environ.get('WERKZEUG_RUN_MAIN') == 'true'
|
|
debug_mode = app.config.get('DEBUG', False)
|
|
should_log_startup = not debug_mode or is_reloader_process
|
|
|
|
if should_log_startup:
|
|
logger.info("=" * 50)
|
|
logger.info(t("log.bootstrap.m001"))
|
|
logger.info("=" * 50)
|
|
|
|
# Enable CORS.
|
|
CORS(app, resources={r"/api/*": {"origins": "*"}})
|
|
|
|
# Register simulation-process cleanup so all child processes are torn down
|
|
# when the Flask server shuts down.
|
|
from .services.simulation_runner import SimulationRunner
|
|
SimulationRunner.register_cleanup()
|
|
if should_log_startup:
|
|
logger.info(t("log.bootstrap.m002"))
|
|
|
|
# Request-logging middleware.
|
|
@app.before_request
|
|
def log_request():
|
|
logger = get_logger('mirofish.request')
|
|
logger.debug(t("log.bootstrap.m003", request=request.method, request_2=request.path))
|
|
if request.content_type and 'json' in request.content_type:
|
|
logger.debug(t("log.bootstrap.m004", request=request.get_json(silent=True)))
|
|
|
|
@app.after_request
|
|
def log_response(response):
|
|
logger = get_logger('mirofish.request')
|
|
logger.debug(t("log.bootstrap.m005", response=response.status_code))
|
|
return response
|
|
|
|
# Register API blueprints.
|
|
from .api import graph_bp, simulation_bp, report_bp
|
|
app.register_blueprint(graph_bp, url_prefix='/api/graph')
|
|
app.register_blueprint(simulation_bp, url_prefix='/api/simulation')
|
|
app.register_blueprint(report_bp, url_prefix='/api/report')
|
|
|
|
# Health-check endpoint.
|
|
@app.route('/health')
|
|
def health():
|
|
return {'status': 'ok', 'service': 'MiroFish Backend'}
|
|
|
|
# On startup: recover any projects stuck in graph_building (task was killed by restart)
|
|
if should_log_startup:
|
|
_recover_stuck_projects()
|
|
|
|
if should_log_startup:
|
|
logger.info(t("log.bootstrap.m006"))
|
|
|
|
return app
|
|
|
|
|
|
def _recover_stuck_projects():
|
|
"""Mark graph_building projects as completed if Neo4j already has their data."""
|
|
from .models.project import ProjectManager, ProjectStatus
|
|
from .utils.logger import get_logger as _get_logger
|
|
_log = _get_logger('mirofish.startup')
|
|
try:
|
|
for p in ProjectManager.list_projects():
|
|
if p.status == ProjectStatus.GRAPH_BUILDING and p.graph_id:
|
|
from .services.graphiti_adapter import _get_graphiti, _run, _neo4j_query
|
|
g = _get_graphiti()
|
|
r = _run(_neo4j_query(g,
|
|
'MATCH (n:Entity {group_id: $gid}) RETURN count(n) AS n',
|
|
{'gid': p.graph_id}
|
|
))
|
|
node_count = int(r[0]['n']) if r else 0
|
|
if node_count > 0:
|
|
p.status = ProjectStatus.GRAPH_COMPLETED
|
|
p.graph_build_task_id = None
|
|
ProjectManager.save_project(p)
|
|
_log.info(f"Recovered stuck project {p.project_id}: {node_count} nodes found, marked graph_completed")
|
|
except Exception as e:
|
|
_get_logger('mirofish.startup').warning(f"Startup recovery failed: {e}")
|
|
|