error handling
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
from html import escape
|
||||
from http import HTTPStatus
|
||||
|
||||
from flask import jsonify, render_template, request
|
||||
@@ -6,20 +5,21 @@ from jinja2 import TemplateNotFound
|
||||
from sqlalchemy.exc import OperationalError
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
from .utils import safe_db_rollback
|
||||
from .extensions import db
|
||||
|
||||
|
||||
JSON_MIMETYPES = ["application/json", "text/html"]
|
||||
def _safe_rollback() -> None:
|
||||
try:
|
||||
db.session.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
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)
|
||||
best = request.accept_mimetypes.best_match(["application/json", "text/html"])
|
||||
if not best:
|
||||
return False
|
||||
|
||||
@@ -30,43 +30,49 @@ def _wants_json_response() -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _status_phrase(status_code: int) -> str:
|
||||
def _get_status_phrase(status_code: int) -> str:
|
||||
try:
|
||||
return HTTPStatus(status_code).phrase
|
||||
except ValueError:
|
||||
return "Blad"
|
||||
|
||||
|
||||
def _status_description(status_code: int) -> str:
|
||||
def _get_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 _error_headers(status_code: int) -> dict[str, str]:
|
||||
headers = {}
|
||||
|
||||
if status_code >= 500:
|
||||
headers.update(
|
||||
{
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0, private",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": "0",
|
||||
"Surrogate-Control": "no-store",
|
||||
}
|
||||
)
|
||||
|
||||
return headers
|
||||
|
||||
|
||||
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}
|
||||
phrase = _get_status_phrase(status_code)
|
||||
description = message or _get_status_description(status_code)
|
||||
headers = _error_headers(status_code)
|
||||
|
||||
payload = {
|
||||
"status": status_code,
|
||||
"error": phrase,
|
||||
"message": description,
|
||||
}
|
||||
|
||||
if _wants_json_response():
|
||||
return jsonify(payload), status_code
|
||||
return jsonify(payload), status_code, headers
|
||||
|
||||
try:
|
||||
return (
|
||||
@@ -77,11 +83,14 @@ def _render_error(status_code: int, message: str | None = None):
|
||||
error_message=description,
|
||||
),
|
||||
status_code,
|
||||
headers,
|
||||
)
|
||||
except TemplateNotFound:
|
||||
return _plain_fallback(status_code, phrase, description)
|
||||
except Exception:
|
||||
return _plain_fallback(status_code, phrase, description)
|
||||
return (
|
||||
f"{status_code} {phrase}: {description}",
|
||||
status_code,
|
||||
headers,
|
||||
)
|
||||
|
||||
|
||||
def register_error_handlers(app):
|
||||
@@ -91,12 +100,15 @@ def register_error_handlers(app):
|
||||
|
||||
@app.errorhandler(OperationalError)
|
||||
def handle_operational_error(exc):
|
||||
safe_db_rollback()
|
||||
_safe_rollback()
|
||||
app.logger.exception("Blad polaczenia z baza danych: %s", exc)
|
||||
return _render_error(503, "Baza danych jest chwilowo niedostepna. Sprobuj ponownie za chwile.")
|
||||
return _render_error(
|
||||
503,
|
||||
"Baza danych jest chwilowo niedostepna. Sprobuj ponownie za chwile.",
|
||||
)
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def handle_unexpected_error(exc):
|
||||
safe_db_rollback()
|
||||
_safe_rollback()
|
||||
app.logger.exception("Nieobsluzony wyjatek: %s", exc)
|
||||
return _render_error(500, "Wystapil nieoczekiwany blad serwera.")
|
||||
return _render_error(500, "Wystapil nieoczekiwany blad serwera.")
|
||||
Reference in New Issue
Block a user