← 메인 화면프로젝트 자료·코드
← 자료 목록

cc/startup_app/app.py

프로그램·코드 · 4.7 KB원본 다운로드

원본 내용을 읽기 전용으로 표시합니다.

  1. """세종 상권 살펴보기 API. 운영 환경에서는 waitress와 HTTPS 프록시 사용."""
  2. import logging
  3. import math
  4. import os
  5. from pathlib import Path
  6. from functools import lru_cache
  7. for key in ("OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS"):
  8. os.environ.setdefault(key, "1")
  9. import numpy as np
  10. from flask import Flask, jsonify, render_template, request
  11. from werkzeug.exceptions import HTTPException
  12. try:
  13. from .engine import 엔진
  14. except ImportError:
  15. from engine import 엔진
  16. def 숫자정리(value):
  17. if isinstance(value, dict):
  18. return {key: 숫자정리(item) for key, item in value.items()}
  19. if isinstance(value, (list, tuple)):
  20. return [숫자정리(item) for item in value]
  21. if isinstance(value, np.generic):
  22. value = value.item()
  23. return None if isinstance(value, float) and not math.isfinite(value) else value
  24. @lru_cache(maxsize=1)
  25. def default_engine():
  26. return 엔진()
  27. def create_app(engine=None):
  28. app = Flask(__name__)
  29. from .materials import bp
  30. app.config['MATERIALS_DIR'] = Path(__file__).resolve().parents[1] / 'public_materials'
  31. app.register_blueprint(bp)
  32. app.json.ensure_ascii = False
  33. app.config["MAX_CONTENT_LENGTH"] = 16 * 1024
  34. if engine is not None:
  35. app.extensions["sejong_engine"] = engine
  36. def model():
  37. return app.extensions.get("sejong_engine") or default_engine()
  38. def parameters(category_required=False):
  39. data = request.get_json(silent=True)
  40. if not isinstance(data, dict) or isinstance(data.get("lat"), bool) or isinstance(data.get("lon"), bool):
  41. return None
  42. try:
  43. lat, lon = float(data["lat"]), float(data["lon"])
  44. except (KeyError, ValueError, TypeError, OverflowError):
  45. return None
  46. if not model().위치확인(lat, lon):
  47. return None
  48. category = data.get("mid")
  49. if category_required and (not isinstance(category, str) or category not in model().mid_to_cat):
  50. return None
  51. return round(lat, 6), round(lon, 6), category
  52. @app.get("/")
  53. def index():
  54. return render_template("index.html")
  55. @app.get("/healthz")
  56. def health():
  57. current = model()
  58. return jsonify({"status": "ok", "rows": len(current.now), "model_count": len(current.모델), "version": "2"})
  59. @app.get("/api/meta")
  60. def metadata():
  61. response = jsonify(숫자정리(model().메타()))
  62. response.headers["Cache-Control"] = "public, max-age=300"
  63. return response
  64. @app.get("/api/boundary")
  65. def boundary():
  66. return jsonify(model().boundary or {"type": "FeatureCollection", "features": []})
  67. @app.post("/api/analyze")
  68. def analyze():
  69. values = parameters(True)
  70. if values is None:
  71. return jsonify({"오류": "세종시 경계 안의 위치와 목록에 있는 업종을 골라 주세요."}), 400
  72. return jsonify(숫자정리(model().분석(*values)))
  73. @app.post("/api/recommend")
  74. def compare_categories():
  75. values = parameters()
  76. if values is None:
  77. return jsonify({"오류": "세종시 경계 안의 위치를 골라 주세요."}), 400
  78. return jsonify(숫자정리(model().업종추천(values[0], values[1])))
  79. @app.after_request
  80. def headers(response):
  81. response.headers.update({"X-Content-Type-Options": "nosniff", "Referrer-Policy": "strict-origin-when-cross-origin", "X-Frame-Options": "SAMEORIGIN"})
  82. response.headers.setdefault("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://*.tile.openstreetmap.org https://*.tile.openstreetmap.fr; connect-src 'self'; frame-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'")
  83. if request.path.startswith("/api/") and request.method == "POST":
  84. response.headers["Cache-Control"] = "no-store"
  85. return response
  86. @app.errorhandler(HTTPException)
  87. def http_error(error):
  88. messages = {404: "요청한 주소가 없습니다.", 405: "지원하지 않는 요청 방식입니다.", 413: "요청 내용이 너무 큽니다."}
  89. return jsonify({"오류": messages.get(error.code, "요청을 처리하지 못했습니다.")}), error.code
  90. @app.errorhandler(Exception)
  91. def unexpected(error):
  92. logging.getLogger(__name__).exception("요청 처리 실패")
  93. return jsonify({"오류": "일시적으로 분석하지 못했습니다. 잠시 후 다시 시도해 주세요."}), 500
  94. return app
  95. app = create_app()
  96. if __name__ == "__main__":
  97. from waitress import serve
  98. logging.basicConfig(level=logging.INFO)
  99. default_engine()
  100. serve(app, host=os.environ.get("HOST", "127.0.0.1"), port=int(os.environ.get("PORT", "5000")), threads=4)
파일 확인 정보

SHA-256: 93264d6286c5e7a0559efa06b35c6b587c47564f1ff3c966a671aab3a969f133