103 lines
2.9 KiB
Python
103 lines
2.9 KiB
Python
from html import escape
|
|
from http import HTTPStatus
|
|
|
|
from flask import jsonify, render_template, request
|
|
from jinja2 import TemplateNotFound
|
|
from sqlalchemy.exc import OperationalError
|
|
from werkzeug.exceptions import HTTPException
|
|
|
|
from .utils import safe_db_rollback
|
|
|
|
|
|
JSON_MIMETYPES = ["application/json", "text/html"]
|
|
|
|
|
|
def _wants_json_response() -> bool:
|
|
if request.path.startswith("/api/"):
|
|
return True
|
|
|
|
if request.is_json:
|
|
return True
|
|
|
|
best = request.accept_mimetypes.best_match(JSON_MIMETYPES)
|
|
if not best:
|
|
return False
|
|
|
|
return (
|
|
best == "application/json"
|
|
and request.accept_mimetypes["application/json"]
|
|
>= request.accept_mimetypes["text/html"]
|
|
)
|
|
|
|
|
|
def _status_phrase(status_code: int) -> str:
|
|
try:
|
|
return HTTPStatus(status_code).phrase
|
|
except ValueError:
|
|
return "Blad"
|
|
|
|
|
|
def _status_description(status_code: int) -> str:
|
|
try:
|
|
return HTTPStatus(status_code).description
|
|
except ValueError:
|
|
return "Wystapil blad podczas przetwarzania zadania."
|
|
|
|
|
|
def _plain_fallback(status_code: int, phrase: str, description: str):
|
|
html = f"""<!doctype html>
|
|
<html lang=\"pl\">
|
|
<head>
|
|
<meta charset=\"utf-8\">
|
|
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
|
|
<title>{status_code} {escape(phrase)}</title>
|
|
</head>
|
|
<body>
|
|
<h1>{status_code} - {escape(phrase)}</h1>
|
|
<p>{escape(description)}</p>
|
|
</body>
|
|
</html>"""
|
|
return html, status_code
|
|
|
|
|
|
def _render_error(status_code: int, message: str | None = None):
|
|
phrase = _status_phrase(status_code)
|
|
description = message or _status_description(status_code)
|
|
payload = {"status": status_code, "error": phrase, "message": description}
|
|
|
|
if _wants_json_response():
|
|
return jsonify(payload), status_code
|
|
|
|
try:
|
|
return (
|
|
render_template(
|
|
"error.html",
|
|
error_code=status_code,
|
|
error_name=phrase,
|
|
error_message=description,
|
|
),
|
|
status_code,
|
|
)
|
|
except TemplateNotFound:
|
|
return _plain_fallback(status_code, phrase, description)
|
|
except Exception:
|
|
return _plain_fallback(status_code, phrase, description)
|
|
|
|
|
|
def register_error_handlers(app):
|
|
@app.errorhandler(HTTPException)
|
|
def handle_http_exception(exc):
|
|
return _render_error(exc.code or 500, exc.description)
|
|
|
|
@app.errorhandler(OperationalError)
|
|
def handle_operational_error(exc):
|
|
safe_db_rollback()
|
|
app.logger.exception("Blad polaczenia z baza danych: %s", exc)
|
|
return _render_error(503, "Baza danych jest chwilowo niedostepna. Sprobuj ponownie za chwile.")
|
|
|
|
@app.errorhandler(Exception)
|
|
def handle_unexpected_error(exc):
|
|
safe_db_rollback()
|
|
app.logger.exception("Nieobsluzony wyjatek: %s", exc)
|
|
return _render_error(500, "Wystapil nieoczekiwany blad serwera.")
|