I have created a simple flask app that and I’m reading the response from python as:
response = requests.post(url,data=json.dumps(data), headers=headers )
data = json.loads(response.text)
Now my issue is that under certain conditions I want to return a 400 or 500 message response. So far I’m doing it like this:
abort(400, 'Record not found')
#or
abort(500, 'Some error...')
This does print the message on the terminal:
But in the API response I kept getting a 500 error response:
The structure of the code is as follows:
|--my_app
|--server.py
|--main.py
|--swagger.yml
Where server.py
has this code:
from flask import render_template
import connexion
# Create the application instance
app = connexion.App(__name__, specification_dir="./")
# read the swagger.yml file to configure the endpoints
app.add_api("swagger.yml")
# Create a URL route in our application for "/"
@app.route("/")
def home():
"""
This function just responds to the browser URL
localhost:5000/
:return: the rendered template "home.html"
"""
return render_template("home.html")
if __name__ == "__main__":
app.run(host="0.0.0.0", port="33")
And main.py
has all the function I’m using for the API endpoints.
E.G:
def my_funct():
abort(400, 'Record not found')
When my_funct
is called, I get the Record not found
printed on the terminal, but not in the response from the API itself, where I always get the 500 message error.
Ответ сервера
Flask предлагает три варианта для создания ответа:
- В виде строки или с помощью шаблонизатора
- Объекта ответа
- Кортежа в формате (
response, status, headers
) или (response, headers
)
Далее о каждом поподробнее.
Создание ответа в виде строки
@app.route('/books/')
def books(genre):
return "All Books in {} category".format(genre)
До сих пор этот способ использовался, чтобы отправлять ответ клиенту. Когда Flask видит, что из функции представления возвращается строка, он автоматически конвертирует ее в объект ответа (с помощью метода make_response()
) со строкой, содержащей тело ответа, статус-код HTTP 200 и значение text/html
в заголовке content-type
. В большинстве случаев это — все, что нужно. Но иногда необходимы дополнительные заголовки перед отправлением ответа клиенту. Для этого создавать ответ нужно с помощью функции make_response()
.
Создание ответа с помощью make_response()
Синтаксис make_response()
следующий:
res_obj = make_response(res_body, status_code=200)
res_body
— обязательный аргумент, представляющий собой тело ответа, а status_code
— опциональный, по умолчанию его значение равно 200.
Следующий код показывает, как добавить дополнительные заголовки с помощью функции make_response()
.
from flask import Flask, make_response,
@app.route('/books/')
def books(genre):
res = make_response("All Books in {} category".format(genre))
res.headers['Content-Type'] = 'text/plain'
res.headers['Server'] = 'Foobar'
return res
Следующий — демонстрирует, как вернуть ошибку 404 с помощью make_response()
.
@app.route('/')
def http_404_handler():
return make_response("404 Error
", 400)
Настройка куки — еще одна базовая задача для любого веб-приложения. Функция make_response()
максимально ее упрощает. Следующий код устанавливает два куки в клиентском браузере.
@app.route('/set-cookie')
def set_cookie():
res = make_response("Cookie setter")
res.set_cookie("favorite-color", "skyblue")
res.set_cookie("favorite-font", "sans-serif")
return res
Примечание: куки обсуждаются подробно в уроке «Куки во Flask».
Куки, заданные в вышеуказанном коде, будут активны до конца сессии в браузере. Можно указать собственную дату истечения их срока, передав в качестве третьего аргумента в методе set_cookie()
количество секунд. Например:
@app.route('/set-cookie')
def set_cookie():
res = make_response("Cookie setter")
res.set_cookie("favorite-color", "skyblue", 60*60*24*15)
res.set_cookie("favorite-font", "sans-serif", 60*60*24*15)
return res
Здесь, у куки будут храниться 15 дней.
Создание ответов с помощью кортежей
Последний способ создать ответ — использовать кортежи в одном из следующих форматов:
(response, status, headers)
(response, headers)
(response, status)
response
— строка, представляющая собой тело ответа, status
— код состояния HTTP, который может быть указан в виде целого числа или строки, а headers
— словарь со значениями заголовков.
@app.route('/')
def http_500_handler():
return ("500 Error
", 500)
Функция представления вернет ошибку HTTP 500 Internal Server Error. Поскольку при создании кортежей можно не писать скобки, вышеуказанный код можно переписать следующим образом:
@app.route('/')
def http_500_handler():
return "500 Error
", 500
Следующий код демонстрирует, как указать заголовки с помощью кортежей:
@app.route('/')
def render_markdown():
return "## Heading", 200, {'Content-Type': 'text/markdown'}
Сможете догадаться, что делает следующая функция?
@app.route('/transfer')
def transfer():
return "", 302, {'location': 'https://localhost:5000/login'}
Функция представления перенаправляет пользователя на https://localhost:5000/login
с помощью ответа 302 (временное перенаправление). Перенаправление пользователей — настолько распространенная практика, что во Flask для этого есть даже отдельная функция redirect()
.
from flask import Flask, redirect
@app.route('/transfer')
def transfer():
return redirect("https://localhost:5000/login")
По умолчанию, redirect()
осуществляет 302 редиректы. Чтобы использовать 301, нужно указать это в функции redirect()
:
from flask import Flask, redirect
@app.route('/transfer')
def transfer():
return redirect("https://localhost:5000/login", code=301)
Перехват запросов
В веб-приложениях часто нужно исполнить определенный код до или после запроса. Например, нужно вывести весь список IP-адресов пользователей, которые используют приложение или авторизовать пользователя до того как показывать ему скрытые страницы. Вместе того чтобы копировать один и тот же код внутри каждой функции представления, Flask предлагает следующие декораторы:
before_first_request
: этот декоратор выполняет функцию еще до обработки первого запросаbefore_request
: выполняет функцию до обработки запросаafter_request
: выполняет функцию после обработки запроса. Такая функция не будет вызвана при возникновении исключений в обработчике запросов. Она должна принять объект ответа и вернуть тот же или новый ответ.teardown_request
: этот декоратор похож наafter_request
. Но вызванная функция всегда будет выполняться вне зависимости от того, возвращает ли обработчик исключение или нет.
Стоит отметить, что если функция в before_request
возвращает ответ, тогда обработчик запросов не вызывается.
Следующий код демонстрирует, как использовать эти точки перехвата во Flask. Нужно создать новый файл hooks.py
с таким кодом:
from flask import Flask, request, g
app = Flask(__name__)
@app.before_first_request
def before_first_request():
print("before_first_request() called")
@app.before_request
def before_request():
print("before_request() called")
@app.after_request
def after_request(response):
print("after_request() called")
return response
@app.route("/")
def index():
print("index() called")
return 'Testings Request Hooks
'
if __name__ == "__main__":
app.run(debug=True)
После этого необходимо запустить сервер и сделать первый запрос, перейдя на страницу https://localhost:5000/
. В консоли, где был запущен сервер, должен появиться следующий вывод:
before_first_request() called
before_request() called
index() called
after_request() called
Примечание: записи о запросах к серверу опущены для краткости.
После перезагрузки страницы отобразится следующий вывод.
before_request() called
index() called
after_request() called
Поскольку это второй запрос, функция before_first_request()
не будет вызываться.
Отмена запроса с помощью abort()
Flask предлагает функцию abort()
для отмены запроса с конкретным типом ошибки: 404, 500 и так далее. Например:
from flask import Flask, abort
@app.route('/')
def index():
abort(404)
# код после выполнения abort() не выполняется
Эта функция представления вернет стандартную страницу ошибки 404, которая выглядит вот так:
abort()
покажет похожие страницы для других типов ошибок. Если нужно изменить внешний вид страниц с ошибками, необходимо использовать декоратор errorhandler
.
Изменение страниц ошибок
Декоратор errorhandler
используется для создания пользовательских страниц с ошибками. Он принимает один аргумент — ошибку HTTP, — для которой создается страница. Откроем файл hooks.py
для создания кастомных страниц ошибок 404 и 500 с помощью декоратора:
from flask import Flask, request, g, abort
#...
#...
@app.after_request
def after_request(response):
print("after_request() called")
return response
@app.errorhandler(404)
def http_404_handler(error):
return "HTTP 404 Error Encountered
", 404
@app.errorhandler(500)
def http_500_handler(error):
return "HTTP 500 Error Encountered
", 500
@app.route("/")
def index():
# print("index() called")
# return 'Testings Request Hooks
'
abort(404)
if __name__ == "__main__":
#...
Стоит отметить, что оба обработчика ошибок принимают один аргумент error
, который содержит дополнительную информацию о типе ошибки.
Если сейчас посетить корневой URL, отобразится следующий ответ:
Abhilash
Grokking the Behavioral Interview
Many candidates are rejected or down-leveled in technical interviews due to poor performance in behavioral or cultural fit interviews. Ace your interviews with this free course, where you will practice confidently tackling behavioral interview questions.
What is Flask?
Flask is a lightweight Python framework for quick and rapid web application development.
What are status codes in HTTP?
HTTP response status codes are used in web development to determine whether a particular HTTP request has been completed successfully.
Status code in a tuple
The view
function can return the status code as one of a tuple’s elements. A response object is automatically created from the return result of a view
function. It is possible to get a tuple back that contains additional information. The tuple with the status code to be returned can be in any of the following formats:
(response, status_code)
(response, status_code, headers)
Code
from flask import Flask, make_response, request app = Flask(__name__) @app.route("/userDetails", methods=["GET", "POST"]) def user_details(): if request.method == "POST": username = request.form.get("username") firstname = request.form.get("firstname") lastname = request.form["lastname"] return "Success", 201 @app.route("/userSignUp", methods=["POST"]) def sign_up(): if request.method == "POST": username = request.form.get("username") password = request.form.get("password") return "Success", 200, {"Access-Control-Allow-Origin": "*"}
Explanation
In the code above, the important lines to focus on are the following:
- Line 14: We are returning the response as a tuple with the response body as
Success
and the status code of201
. - Line 24: We are returning the response as a tuple with the response body as
Success
, status code of200
, and some headers as a dictionary.
Use the following curl
commands to test the userDetails
endpoint.
curl -X POST http://localhost:5000/userDetails -H "Content-Type: application/x-www-form-urlencoded" -d "username=sam&firstname=john&lastname=king"
Use the following curl
commands to test the userSignUp
endpoint.
curl -X POST http://localhost:5000/userSignUp -H "Content-Type: application/x-www-form-urlencoded" -d "username=sam&firstname=john"
The make_response()
method
The make_response
method from Flask returns a Response
object that can be used to send custom headers and change the properties such as status_code, mimetype, and so on.
We can set the status code using the make_response
method in two ways:
- Pass the status code as a constructor parameter.
- Use the property
status_code
to set the status code. Here, the value set has to be an integer.
Code
from flask import Flask, make_response, request app = Flask(__name__) @app.route("/userDetails", methods=["GET", "POST"]) def user_details(): if request.method == "POST": username = request.form.get("username") firstname = request.form.get("firstname") lastname = request.form["lastname"] response = make_response("<h1>Success</h1>", 201) return response @app.route("/userSignUp", methods=["POST"]) def sign_up(): if request.method == "POST": username = request.form.get("username") password = request.form.get("password") response = make_response("<h1>Success</h1>") response.status_code = 200 return response
Explanation
In the code above, the important lines to focus on are the following:
- Line 14: We use the
make_response
method to create an instance of theResponse
class. We pass the status code as a constructor parameter. - Lines 25–26: We use the
make_response
method to create an instance of theResponse
class. We set the status code by explicitly setting thestatus_code
property of the response object.
Use the following curl
commands to test the userDetails
endpoint.
curl -X POST http://localhost:5000/userDetails -H "Content-Type: application/x-www-form-urlencoded" -d "username=sam&firstname=john&lastname=king"
Use the following curl
commands to test the userSignUp
endpoint.
curl -X POST http://localhost:5000/userSignUp -H "Content-Type: application/x-www-form-urlencoded" -d "username=sam&firstname=john"
CONTRIBUTOR
Abhilash
Copyright ©2023 Educative, Inc. All rights reserved
Время на прочтение
14 мин
Количество просмотров 52K
blog.miguelgrinberg.com
Miguel Grinberg
<<< предыдущая следующая >>>
Эта статья является переводом седьмой части нового издания учебника Мигеля Гринберга, выпуск которого автор планирует завершить в мае 2018.Прежний перевод давно утратил свою актуальность.
Я, со своей стороны, постараюсь не отставать с переводом.
Это седьмая глава серии Flask Mega-Tutorial, в которой я расскажу вам, как выполнять обработку ошибок в приложении Flask.
Для справки ниже приведен список статей этой серии.
Примечание 1: Если вы ищете старые версии данного курса, это здесь.
Примечание 2: Если вдруг Вы хотели бы выступить в поддержку моей(Мигеля) работы в этом блоге, или просто не имеете терпения дожидаться неделю статьи, я (Мигель Гринберг)предлагаю полную версию данного руководства упакованную электронную книгу или видео. Для получения более подробной информации посетите learn.miguelgrinberg.com.
В этой главе я перехожу от кодирования новых функций для моего микроблогического приложения и вместо этого обсужу несколько стратегий борьбы с ошибками, которые неизменно появляются в любом программном проекте. Чтобы проиллюстрировать эту тему, я намеренно допустил ошибку в коде, который я добавил в главе 6. Прежде чем продолжить чтение, посмотрите, сможете ли вы его найти!
Ссылки GitHub для этой главы: Browse, Zip, Diff.
Обработка ошибок в Flask
Что происходит, когда возникает ошибка в приложении Flask? Лучший способ узнать это — испытать это самому. Запустите приложение и убедитесь, что у вас зарегистрировано не менее двух пользователей. Войдите в систему как один из пользователей, откройте страницу профиля и нажмите ссылку «Изменить». В редакторе профиля попробуйте изменить имя пользователя на существующее имя другого пользователя, который уже зарегистрирован, и попытайтесь применить исправления! Это приведет к появлению страшной страницы «Internal Server Error» ( «Внутренняя ошибка сервера» ):
В сеансе терминала, на котором запущено приложение, вы видите трассировку стека ошибки. Трассировки стека чрезвычайно полезны при отладке ошибок, поскольку они показывают последовательность вызовов в этом стеке, вплоть до строки, вызвавшей ошибку:
(venv) $ flask run
* Serving Flask app "microblog"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
[2017-09-14 22:40:02,027] ERROR in app: Exception on /edit_profile [POST]
Traceback (most recent call last):
File "/home/miguel/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1182, in _execute_context
context)
File "/home/miguel/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 470, in do_execute
cursor.execute(statement, parameters)
sqlite3.IntegrityError: UNIQUE constraint failed: user.username
Трассировка стека указывает, чем вызвана ошибка. Приложение позволяет пользователю изменять имя пользователя без проверки, что новое имя пользователя не совпадает с другим пользователем, уже находящимся в системе. Ошибка возникает из SQLAlchemy, которая пытается записать новое имя пользователя в базу данных, но база данных отвергает его, потому что столбец имени пользователя определен с unique = True.
Важно, что страница с ошибкой, представленная пользователю, не содержит много информации об ошибке, и это правильно. Я определенно не хочу, чтобы пользователи узнали, что авария была вызвана ошибкой базы данных или какой базой данных я пользуюсь, а также именами таблиц и полей в моей базе данных. Вся эта информация должна быть внутренней.
Есть несколько вещей, которые далеки от идеала. У меня есть страница с ошибкой, которая безобразна и не соответствует макету приложения. У меня также есть важные трассировки стека приложений, которые сбрасываются на терминале, и мне нужно постоянно следить за тем, чтобы я не пропустил никаких ошибок. И, конечно, у меня есть ошибка. Я собираюсь решить все эти проблемы, но сначала поговорим о режиме отладки Flask.
Режим отладки
То, как ошибки обрабатываются выше, отлично подходит для системы, которая работает на production сервере. Если есть ошибка, пользователь получает страницу с неопределенной ошибкой (хотя я собираюсь сделать эту страницу с ошибкой более приятной), а важные данные об ошибке — в выводе сервера или в файле журнала.
Но когда вы разрабатываете приложение, вы можете включить режим отладки, режим, в котором Flask выводит действительно хороший отладчик непосредственно в ваш браузер. Чтобы активировать режим отладки, остановите приложение, а затем установите следующую переменную среды:
(venv) $ export FLASK_DEBUG=1
Если вы работаете в ОС Microsoft Windows, не забудьте использовать set
вместо экспорта.
После того, как вы установили FLASK_DEBUG, перезапустите сервер. Строки на вашем терминале будут немного отличаться от того, что вы привыкли видеть:
(venv) microblog2 $ flask run
* Serving Flask app "microblog"
* Forcing debug mode on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 177-562-960
Теперь устроим приложению аварийный сбой еще раз, чтобы увидеть интерактивный отладчик в вашем браузере:
Отладчик позволяет развернуть каждый уровень стека и увидеть соответствующий исходный код. Вы также можете открыть Python для любого из фреймов и выполнить любые допустимые выражения Python, например, чтобы проверить значения переменных.
Крайне важно, чтобы вы никогда не запускали приложение Flask в режиме отладки на рабочем сервере. Отладчик позволяет удаленно выполнять код на сервере, поэтому он может стать неожиданным подарком злоумышленнику, который хочет проникнуть в ваше приложение или на ваш сервер. В качестве дополнительной меры безопасности отладчик, запущенный в браузере, закроется, и при первом использовании запросит PIN-код, который вы можете увидеть на выходе команды flask run
.
Поскольку я говорю о режиме отладки, следует упомянуть про вторую важную функцию, которая включена в режиме отладки — перезагрузка. Это очень полезная функция разработки, которая автоматически перезапускает приложение при изменении исходного файла. Если вы выполните flask run
в режиме отладки, можно продолжать работать в своем приложении и при каждом сохранении файла, приложение перезапустится, чтобы забрать новый код.
Пользовательские страницы ошибок
Flask предоставляет механизм приложения для создания собственных страниц ошибок, так что вашим пользователям не нужно видеть простые и скучные значения по умолчанию. В качестве примера давайте определим пользовательские страницы ошибок для ошибок HTTP 404 и 500, двух наиболее распространенных. Определение страниц для других ошибок работает одинаково.
Чтобы объявить пользовательский обработчик ошибок, используется декоратор @errorhandler
. Я собираюсь поместить обработчики ошибок в новый модуль app/errors.py.
from flask import render_template
from app import app, db
@app.errorhandler(404)
def not_found_error(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('500.html'), 500
Функции ошибок работают аналогично функциям просмотра. Для этих двух ошибок я возвращаю содержимое их соответствующих шаблонов. Обратите внимание, что обе функции возвращают второе значение после шаблона, который является номером кода ошибки. Для всех функций представления, которые я создал до сих пор, мне не нужно было добавлять второе возвращаемое значение, потому что по умолчанию 200 (код состояния для успешного завершения) — это то, что я хотел. Сейчас это страницы с ошибками, поэтому я хочу, чтобы код состояния ответа это отражал.
Обработчик ошибок для 500-й ошибки может быть вызван после возникновения сбоя базы данных, которая на самом деле была вызвана умышленным случаем дубликата имени пользователя. Чтобы убедиться, что неудачные сеансы базы данных не мешают доступу к базе данных, вызванным шаблоном, я выдаю откат сеанса. Это сбрасывает сеанс в чистое состояние.
Вот шаблон для ошибки 404:
{% extends "base.html" %}
{% block content %}
<h1>File Not Found</h1>
<p><a href="{{ url_for('index') }}">Back</a></p>
{% endblock %}
И вот одна из ошибок 500:
{% extends "base.html" %}
{% block content %}
<h1>An unexpected error has occurred</h1>
<p>The administrator has been notified. Sorry for the inconvenience!</p>
<p><a href="{{ url_for('index') }}">Back</a></p>
{% endblock %}
Оба шаблона наследуют шаблон base.html
, так что страница с ошибками имеет тот же внешний вид, что и обычные страницы приложения.
Чтобы получить эти обработчики ошибок, зарегистрированные в Flask, мне нужно импортировать новый модуль app/errors.py
после создания экземпляра приложения:
# ...
from app import routes, models, errors
Если вы установили FLASK_DEBUG = 0
в сеансе терминала и затем снова вызвали ошибку повторного имени пользователя, вы увидите более приятную страницу с ошибкой.
Или так! Рекомендую придумать что то свое в качестве упражнения.
Отправка ошибок по электронной почте
Другая проблема с обработкой ошибок по умолчанию, предоставляемой Flask, заключается в том, что нет уведомлений! Трассировка стека ошибки печатается на терминале, а это означает, что вывод процесса сервера должен контролироваться на обнаружение ошибок. Когда вы запускаете приложение во время разработки, это нормально, но как только приложение будет развернуто на production сервере, никто не будет смотреть на результат, поэтому необходимо создать более надежное решение.
Я думаю, что очень важно, чтобы я активно реагировал на ошибки. Если в production версии приложения возникает ошибка, я хочу знать сразу. Таким образом, моим первым решением будет сконфигурировать Flask для отправки мне сообщения по email сразу после возникновения ошибки с трассировкой стека ошибки в сообщении электронной почты.
Первым шагом является добавление данных сервера электронной почты в файл конфигурации:
class Config(object):
# ...
MAIL_SERVER = os.environ.get('MAIL_SERVER')
MAIL_PORT = int(os.environ.get('MAIL_PORT') or 25)
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') is not None
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
ADMINS = ['your-email@example.com']
Переменные конфигурации для электронной почты содержат сервер и порт, флаг для включения зашифрованных соединений и необязательное имя пользователя и пароль. Пять переменных конфигурации получены из их сопоставлений переменным среды. Если сервер электронной почты не установлен в среде, то я буду использовать это как знак того, что ошибки электронной почты должны быть отключены. Порт сервера электронной почты также можно указать в переменной среды, но если он не установлен, используется стандартный порт 25. Учетные данные почтового сервера по умолчанию не используются, но могут быть предоставлены при необходимости. Переменная конфигурации ADMINS
представляет собой список адресов электронной почты, которые будут получать отчеты об ошибках, поэтому ваш собственный адрес электронной почты должен быть в этом списке.
Flask использует пакет logging
Python для ведения своих журналов, а этот пакет уже имеет возможность отправлять журналы по электронной почте. Все, что мне нужно сделать, чтобы отправлять электронные сообщения, содержащие ошибки, — это добавить экземпляр SMTPHandler в объект журнала Flask, которым является app.logger
:
import logging
from logging.handlers import SMTPHandler
# ...
if not app.debug:
if app.config['MAIL_SERVER']:
auth = None
if app.config['MAIL_USERNAME'] or app.config['MAIL_PASSWORD']:
auth = (app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD'])
secure = None
if app.config['MAIL_USE_TLS']:
secure = ()
mail_handler = SMTPHandler(
mailhost=(app.config['MAIL_SERVER'], app.config['MAIL_PORT']),
fromaddr='no-reply@' + app.config['MAIL_SERVER'],
toaddrs=app.config['ADMINS'], subject='Microblog Failure',
credentials=auth, secure=secure)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler)
Как видно, я включил регистратор электронной почты только, когда приложение работает без режима отладки, что определено приложением в app.debug
как True
, а также когда сервер электронной почты существует в конфигурации.
Настройка почтового регистратора несколько утомительна из-за необходимости обрабатывать дополнительные параметры безопасности, которые присутствуют на многих серверах электронной почты. Но в сущности, вышеприведенный код создает экземпляр SMTPHandler
, устанавливает его уровень, чтобы он отправлял только сообщения об ошибках, а не предупреждения, информационные или отладочные сообщения и, наконец, прикреплял их к app.logger
из Flask.
Существует два подхода к проверке работоспособности этой функции. Самый простой способ — использовать SMTP-сервер отладки от Python. Это ложный почтовый сервер, который принимает сообщения электронной почты, но вместо их отправки выводит их на консоль. Чтобы запустить этот сервер, откройте второй сеанс терминала и запустите на нем следующую команду:
(venv) $ python -m smtpd -n -c DebuggingServer localhost:8025
Оставьте запущенный SMTP-сервер отладки и вернитесь к своему первому терминалу и установите export
MAIL_SERVER = localhost
и MAIL_PORT = 8025
(используйте set
вместо export
, если вы используете Microsoft Windows). Убедитесь, что для переменной FLASK_DEBUG
установлено значение 0
или не установлено вообще, так как приложение не будет отправлять электронные письма в режиме отладки.
Запустите приложение и вызовите ошибку SQLAlchemy еще раз, чтобы узнать, как сеанс терминала, на котором работает поддельный почтовый сервер, показывает электронное письмо с полным содержимым стека ошибки.
Второй метод тестирования для этой функции — настроить настоящий почтовый сервер. Ниже приведена конфигурация для использования почтового сервера для учетной записи Gmail:
export MAIL_SERVER=smtp.googlemail.com
export MAIL_PORT=587
export MAIL_USE_TLS=1
export MAIL_USERNAME=<your-gmail-username>
export MAIL_PASSWORD=<your-gmail-password>
Если вы используете Microsoft Windows, не забудьте использовать set
вместо export
в каждой из приведенной выше инструкции.
Функции безопасности вашей учетной записи Gmail могут препятствовать приложению отправлять электронную почту через нее, если вы явно не разрешаете «less secure apps» («менее безопасным приложениям») доступ к вашей учетной записи Gmail. Прочитать об этом можно здесь, и если вас беспокоит безопасность вашей учетной записи, можно создать вторичную учетную запись, которую настройте только для проверки электронной почты, или временно включите разрешение для менее безопасных приложений на время запуска этого теста, а затем вернитесь к умолчанию.
Запись лога в файл
Получение ошибок по электронной почте полезно, но иногда недостаточно. Есть некоторые случаи сбоя, которые не описываются исключением Python и не являются серьезной проблемой, но они все равно могут быть достаточно интересными для сохранения в целях отладки. По этой причине я также буду поддерживать логфайл для приложения.
Чтобы включить ведение журнала другого обработчика, на этот раз типа RotatingFileHandler
необходимо включить logger приложения аналогично обработчику электронной почты.
# ...
from logging.handlers import RotatingFileHandler
import os
# ...
if not app.debug:
# ...
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/microblog.log', maxBytes=10240,
backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Microblog startup')
Я пишу логфайл с именем microblog.log
в каталоге logs, который я создаю, если он еще не существует.
Класс RotatingFileHandler
удобен, потому что он переписывает журналы, гарантируя, что файлы журнала не будут слишком большими, если приложение работает в течение длительного времени. В этом случае я ограничиваю размер логфайла 10 КБ, и храню последние десять файлов журнала в качестве резервных копий.
Класс logging.Formatter
предоставляет настройку формата сообщений журнала. Поскольку эти сообщения отправляются в файл, я хочу, чтобы они содержали как можно больше информации. Поэтому я использую формат, который включает отметку времени, уровень ведения журнала,
сообщение, исходный файл и номер строки, откуда возникла запись в журнале.
Чтобы сделать регистрацию более полезной, я также понижаю уровень ведения журнала до категории INFO
, как в регистраторе приложений, так и в обработчике файлов. Если вы не знакомы с категориями ведения журнала, это DEBUG
, INFO
, WARNING
,ERROR
и CRITICAL
в порядке возрастания степени тяжести.
В качестве первого полезного использования логфайла сервер записывает строку в журнал каждый раз, когда он запускается. Когда приложение запускается на production сервере, эти записи журнала сообщают вам, когда сервер был перезапущен.
Исправление дубля имени пользователя
Я слишком долго использовал ошибку дублирования имени пользователя. Теперь, когда я показал вам, как подготовить приложение для обработки подобных ошибок, я могу наконец-то это исправить.
Если вы помните, RegistrationForm
уже выполняет проверку для имен пользователей, но требования формы редактирования немного отличаются. Во время регистрации мне нужно убедиться, что имя пользователя, введенное в форму, не существует в базе данных. В форме профиля редактирования я должен выполнить ту же проверку, но с одним исключением. Если пользователь оставляет исходное имя пользователя нетронутым, то проверка должна его разрешить, поскольку это имя пользователя уже назначено этому пользователю. Ниже вы можете увидеть, как я выполнил проверку имени пользователя для этой формы:
class EditProfileForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
about_me = TextAreaField('About me', validators=[Length(min=0, max=140)])
submit = SubmitField('Submit')
def __init__(self, original_username, *args, **kwargs):
super(EditProfileForm, self).__init__(*args, **kwargs)
self.original_username = original_username
def validate_username(self, username):
if username.data != self.original_username:
user = User.query.filter_by(username=self.username.data).first()
if user is not None:
raise ValidationError('Please use a different username.')
Реализация выполняется в специальном методе проверки, функция super в конструкторе класса, который принимает исходное имя пользователя в качестве аргумента. Это имя пользователя сохраняется как переменная экземпляра и проверяется в методе validate_username()
. Если имя пользователя, введенное в форму, совпадает с исходным именем пользователя, то нет причин проверять базу данных на наличие дубликатов.
Чтобы использовать этот новый метод проверки, мне нужно добавить исходный аргумент имени пользователя в функцию вида, где создается объект формы:
@app.route('/edit_profile', methods=['GET', 'POST'])
@login_required
def edit_profile():
form = EditProfileForm(current_user.username)
# ...
Теперь ошибка исправлена, и дубликаты в форме профиля редактирования будут предотвращены в большинстве случаев. Это не идеальное решение, поскольку оно может не работать, когда два или несколько процессов одновременно обращаются к базе данных. В этой ситуации состояние гонки может привести к валидации, но спустя мгновение при попытке переименования база данных уже была изменена другим процессом и не может переименовать пользователя. Это несколько маловероятно, за исключением очень занятых приложений, у которых много серверных процессов, поэтому я пока не буду беспокоиться об этом.
На этом этапе вы можете попытаться воспроизвести ошибку еще раз, чтобы увидеть, как ее предотвращает метод проверки формы.
<<< предыдущая следующая >>>
P.S.
Работа над ошибками
От переводчика
Решил я проверить получение сообщений ошибки админу на почту. Для этого я испортил модуль routes.py
. Для этой самой «порчи», я закомментировал декоратор @app.route('/edit_profile', methods=['GET', 'POST'])
перед def edit_profile()
. В итоге получил ошибку и в файл лога все это вывалилось, а вот письмо не прилетело. Я использую Python 3.3. Возможно в более новых версиях этого и не случится. Но в Windows 7 с русской раскладкой это случилось.
При попытке отправить сообщение админу приложение получило ошибку кодировки при формировании сообщения. В окне консоли содержались такие строки:
Как видим ссылка указывает на директорию в стандартном питоне, а не в виртуальном окружении.
logging
в 3-й версии является стандартной библиотекой Python, поэтому вам не нужно устанавливать ее используя pip
.
Про стандартные модули
И модуль протоколирования, который вы можете найти в PyPI, устаревший, а не Python3-совместимый.
(Согласно файлу README его последняя версия была выпущена 02 марта 2005 года.)
Поэтому просто не пытайтесь установить logging.
Возьмите новый модуль в стандартной библиотеке как должное. Если вам принципиально использовать его в виртальной библиотеке.
После копии в venvLib logging
импортируется из виртуальной среды
Еще раз получаю ошибку
logging
теперь виртуальный. А вот smtplib
стандартный.
Не думаю, что надо тащить все библиотеки из стандартной среды в виртуальную.
Ошибка от этого не исчезнет.
Про стандартный модуль email
Проблема с кодировкой в сообщении решается использованием стандартного пакета email
для создания сообщения с указанием предпочитаемой кодировки.
Вот пример с просторов интернета для этого пакета :
# -*- coding: utf-8 -*-
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import quopri
def QuoHead(String):
s = quopri.encodestring(String.encode('UTF-8'), 1, 0)
return "=?utf-8?Q?" + s.decode('UTF-8') + "?="
FIOin = "Хрюша Степашкин"
emailout = "some@test.ru"
emailin = "some2@test.ru"
msg = MIMEMultipart()
msg["Subject"] = QuoHead("Добрый день " + FIOin).replace('=n', '')
msg["From"] = (QuoHead("Каркуша Федоровна") + " <" + emailout + ">").replace('=n', '')
msg["To"] = (QuoHead(FIOin) + " <" + emailin + ">").replace('=n', '')
m = """Добрый день.
Это тестовое письмо.
Пожалуйста, не отвечайте на него."""
text = MIMEText(m.encode('utf-8'), 'plain', 'UTF-8')
msg.attach(text)
print(msg.as_string())
Но, как это применить для отправки сообщений об ошибке?!
Может кто-то предложит в комментариях к статье.
В модуле flask-mail
эта ситуевина вроде как поправлена. Но тут используется logging
и smtplib
В итоге пока так. Поправил я строку в модуле smtplib.py
.
Добавил encode('utf-8')
И после перезапуска сервера при искусственной ошибке я, наконец-то, получил сообщение на почту.
<<< предыдущая следующая >>>
Обработка HTTP-ошибок 404, 500 и т.д. во Flask.
Если какая-то часть кода кода сайта на Flask ломается при обработке запроса и нет зарегистрированных обработчиков ошибок, то по умолчанию будет возвращена ошибка 500 Internal Server Error
(InternalServerError
). Точно так же будет выводится стандартная страница с ошибкой 404 Not Found
, если запрос будет отправлен на незарегистрированный URL-адрес. Если маршрут получает недопустимый метод запроса, будет активирован HTTP-метод 405 Not Allowed
. Все это подклассы HTTPException
, которые по умолчанию предоставляются в Flask.
Фреймворк Flask дает возможность вызывать любое исключение HTTP, зарегистрированное Werkzeug
, но по умолчанию отдаются простые/стандартные страницы ошибок. Для удобства пользователя сайта, а так же повышения лояльности поисковых систем к сайту необходимо показывать настроенные страницы ошибок (вместо стандартных). Это можно сделать, зарегистрировав обработчики ошибок.
Обработчик ошибок — это функция, которая возвращает ответ при возникновении определенного типа ошибки, аналогично тому, как представление является функцией, которая возвращает ответ при совпадении URL-адреса запроса. Ему передается экземпляр обрабатываемой ошибки, который будет является исключением werkzeug.exceptions.HTTPException
.
Когда Flask перехватывает исключение при обработке запроса, сначала выполняется поиск по коду. Если в коде не зарегистрирован обработчик, то Flask ищет ошибку в иерархии классов и выбирает наиболее конкретный обработчик. В том случае, если обработчик не зарегистрирован, то подклассы HTTPException
показывают наиболее подходящую стандартную страницу с ошибкой, в то время как другие исключения преобразуются в общую страницу 500 Internal Server Error
.
Например, если возникает экземпляр ConnectionRefusedError
и зарегистрированы обработчики ConnectionError
и ConnectionRefusedError
, то для генерации ответа будет вызываться более конкретный обработчик ConnectionRefusedError
.
Содержание.
- Регистрация обработчика ошибок в веб-приложении на Flask;
- Универсальные обработчики исключений во Flask;
- Как Flask обрабатывает необработанные исключения?
- Создание собственного дизайна страницы с HTTP-ошибкой 404;
- Пример пользовательской страницы ошибки с кодом 500;
- Особенности обработки ошибок в схемах
blueprint
Flask; - Возврат ошибок API в формате JSON.
Регистрация обработчика ошибок в веб-приложении на Flask.
Зарегистрировать функцию-обработчик для модуля Flask, можно указав перед ней декоратор @app.errorhandler()
, или зарегистрировать обработчик, использовав функцию app.register_error_handler()
. Не забудьте установить код ошибки при возврате ответа.
from werkzeug.exceptions import BadRequest # регистрируем обработчик при # помощи подкласса `BadRequest` @app.errorhandler(BadRequest) def handle_bad_request(e): return 'bad request!', 400 # или тоже самое, но с кодом ошибки 400 @app.errorhandler(400) def handle_bad_request(e): return 'bad request!', 400 # регистрируем тот же обработчик без декоратора app.register_error_handler(400, handle_bad_request)
Обратите внимание, что подклассы
HTTPException
, такие какBadRequest
и их HTTP-коды (400), взаимозаменяемы при регистрации обработчиков. (BadRequest.code == 400
)
Нестандартные HTTP-коды (такие как HTTP 507 Insufficient Storage
) нельзя зарегистрировать, так как они не известны модулю Werkzeug
. Для регистрации неизвестных HTTP-кодов необходимо определить подкласс werkzeug.exceptions.HTTPException
с соответствующим кодом, зарегистрировать и где надо вернуть HTTP-код 507 Insufficient Storage
принудительно вызовите этот класс исключения при помощи инструкции raise
.
from werkzeug.exceptions import HTTPException, default_exceptions from flask import Flask, abort # создаем подкласс исключения HTTP 507 # Внимание! модуль werkzeug HTTP 507 НЕ ЗНАЕТ! class InsufficientStorage(HTTPException): code = 507 description = 'Not enough storage space.' # добавляем код ошибки и соответствующий # класс к уже известным HTTP-ошибкам default_exceptions[507] = InsufficientStorage app = Flask(__name__) # теперь Flask знает про нестандартную ошибку # и следовательно можно зарегистрировать для # нее собственный обработчик через декоратор @app.errorhandler(507) def error507(e): return "<h1>AbraKadabra</h1>", 507 # или зарегистрировать без декоратора app.register_error_handler(507, error507) @app.route('/') def debug(): abort(507) app.run()
Обработчики могут быть зарегистрированы для любого класса исключений, а не только для подклассов HTTPException
или кодов состояния HTTP. Обработчики могут быть зарегистрированы для определенного класса или для всех подклассов родительского класса.
Обработчики, зарегистрированные в blueprint
, имеют приоритет над обработчиками, зарегистрированными глобально в веб-приложении, при условии, что blueprint
(схема) обрабатывает запрос, вызывающий исключение. Однако blueprint
не может обрабатывать ошибки маршрутизации 404, так как ошибка 404 возникает на уровне маршрутизации до того, как можно определить схему blueprint
.
Универсальные обработчики исключений.
Можно зарегистрировать обработчики ошибок для очень общих базовых классов, таких как HTTPException
или даже Exception
, но имейте в виду, что они будут ловить все ошибки подряд (больше, чем можно ожидать) и в итоге получится одна страница ошибки на разные ситуации.
Например, обработчик ошибок для HTTPException
может быть полезен для преобразования страниц ошибок HTML по умолчанию в JSON. Но тогда этот обработчик будет запускать, например ошибки 404 и 405 во время маршрутизации. В общем будьте внимательны при создании универсальных обработчиков.
from flask import json from werkzeug.exceptions import HTTPException @app.errorhandler(HTTPException) def handle_exception(e): """Возвращает JSON вместо HTML для ошибок HTTP""" # сначала перехватываем ответ Flask для извлечения # правильных заголовков и кода состояния из ошибки response = e.get_response() # заменяем тело ответа сервера на JSON response.data = json.dumps({ "code": e.code, "name": e.name, "description": e.description, }) response.content_type = "application/json" # возвращаем ответ сервера return response
Обработчик ошибок для Exception
может !показаться! полезным для изменения способа представления пользователю всех ошибок, даже не перехваченных в коде. Другими словами: страница ошибки с одним и тем же HTTP-кодом для разных ситуаций (о чем говорилось выше). Исключение Exception
в Python фиксирует все необработанные ошибки, при этом будут включены все коды состояния HTTP.
Правильнее будет безопаснее зарегистрировать обработчики для более конкретных исключений, т.к. экземпляры HTTPException
являются действительными ответами WSGI.
from werkzeug.exceptions import HTTPException @app.errorhandler(Exception) def handle_exception(e): # исключаем ошибки HTTP if isinstance(e, HTTPException): # если это ошибка HTTP, то просто # возвращаем ее без изменений return e # в остальных случаях (ошибка кода веб-приложения) # генерируем страницу с ошибкой HTTP 500 return render_template("500_generic.html", e=e), 500
Обработчики ошибок по-прежнему соблюдают иерархию классов исключений. Если зарегистрировать обработчики как для HTTPException
, так и для Exception
, то обработчик Exception
не будет обрабатывать подклассы HTTPException
, т.к. он является более конкретным обработчиком HTTPException
.
Как Flask обрабатывает необработанные исключения?
Если код сайта на Flask во время работы ломается, то есть возникло исключение, для которого не зарегистрирован обработчик ошибок, то будет возвращена ошибка 500 Internal Server
Если для исключения InternalServerError
зарегистрирован обработчик ошибок, то будет вызван этот обработчик. Начиная с Flask 1.1.0, этому обработчику ошибок всегда будет передаваться экземпляр InternalServerError
, а не исходная не перехваченная ошибка. Исходная ошибка доступна как e.original_exception
.
Обработчику ошибок 500 Internal Server Error
будут передаваться неперехваченные исключения в дополнение к явным ошибкам 500. В режиме отладки обработчик 500 Internal Server Error
не используется, а показывается интерактивный отладчик.
Создание собственного дизайна страницы с HTTP-ошибкой 404.
Почти всегда при создании сайта на Flask необходимо потребоваться вызвать исключение HTTPException
, чтобы сообщить пользователю, что с запросом что-то не так. Фреймворк Flask поставляется с удобной функцией flask.abort()
, которая прерывает запрос со стандартной страницей HTTP-ошибки (только основное описание), зарегистрированной в модуле werkzeug
.
В зависимости от кода ошибки, вероятность того, что пользователь действительно увидит конкретную ошибку, меньше или больше.
Рассмотрим приведенный ниже код. Например, может быть маршрут профиля пользователя, и если пользователь не может передать имя пользователя, то можно выдать 400 Bad Request
. Если пользователь передает имя пользователя, а сайт не можем его найти, то выдаем сообщение 404 Not Found
.
from flask import abort, render_template, request # имя пользователя должно быть указано в параметрах запроса # успешный запрос будет похож на /profile?username=jack @app.route("/profile") def user_profile(): username = request.arg.get("username") # если имя пользователя не указано в запросе, # то вернем `400 Bad Request` if username is None: abort(400) user = get_user(username=username) # Если пользователь не наёден, то `404 not found` if user is None: abort(404) return render_template("profile.html", user=user)
Для того, что бы возвращалась страница 404 not found
с собственным дизайном, необходимо создать функцию обработчик:
from flask import render_template @app.errorhandler(404) def page_not_found(e): # в функцию `render_template()` передаем HTML-станицу с собственным # дизайном, а так же явно устанавливаем статус 404 return render_template('404.html'), 404
При использовании фабрик приложений:
from flask import Flask, render_template # обработчик def page_not_found(e): return render_template('404.html'), 404 def create_app(config_filename): app = Flask(__name__) # регистрация обработчика app.register_error_handler(404, page_not_found) return app
Пример шаблона страницы с ошибкой 404.html
может быть таким:
{% extends "layout.html" %} {% block title %}Page Not Found{% endblock %} {% block body %} <h1>Page Not Found</h1> <h3>То, что вы искали, просто не существует.</h3> <p>Для продолжения перейдите <a href="{{ url_for('index') }}">на главную страницу сайта</a></p> {% endblock %}
Пример пользовательской страницы ошибки с кодом 500.
Приведенные выше примеры не на много улучшат страницы HTTP-ошибок по умолчанию. Так же можно создать собственный шаблон 500.html
следующим образом:
{% extends "layout.html" %} {% block title %}Internal Server Error{% endblock %} {% block body %} <h1>Internal Server Error</h1> <h3>Мы уже знаем об этой ошибке и делаем все возможное для ее устранения!</h3> <p>Приносим извинения за причлененные неудобства, скоро все заработает.</p> {% endblock %}
Создаем функцию обработчик HTTP-ошибок 500 Internal Server Error
:
from flask import render_template @app.errorhandler(500) def internal_server_error(e): # Обратите внимание, что необходимо # явно установить статус 500 return render_template('500.html'), 500
При использовании фабрик приложений:
from flask import Flask, render_template # обработчик def internal_server_error(e): return render_template('500.html'), 500 def create_app(): app = Flask(__name__) # регистрация обработчика app.register_error_handler(500, internal_server_error) return app
При использовании модульных приложений с Blueprints
:
from flask import Blueprint blog = Blueprint('blog', __name__) # регистрация обработчика при помощи декоратора @blog.errorhandler(500) def internal_server_error(e): return render_template('500.html'), 500 # или с использованием метода `register_error_handler()` blog.register_error_handler(500, internal_server_error)
Особенности обработки ошибок в схемах blueprint
Flask.
В модульных приложениях с blueprint
большинство обработчиков ошибок будут работать должным образом, но есть предостережение относительно обработчиков исключений 404 и 405. Эти обработчики вызываются только из соответствующего оператора raise
или вызывают flask.abort()
в другой функции-представлении схемы blueprint
. Они не вызываются, например, из-за недействительного доступа к URL-адресу.
Это связано с тем, что blueprint
не принадлежит определенное пространство URL-адресов, поэтому экземпляр приложения не имеет возможности узнать, какой обработчик ошибок схемы (blueprint
) необходимо запустить, если указан недопустимый URL-адрес. Если необходимо использовать различные стратегии обработки этих ошибок на основе префиксов URL-адресов, то они могут быть определены на уровне приложения с помощью объекта прокси-сервера запроса flask.request
.
from flask import jsonify, render_template # на уровне всего веб-приложения # это не уровень определенной схемы blueprint @app.errorhandler(404) def page_not_found(e): # Если запрос находится в пространстве URL блога if request.path.startswith('/blog/'): # то возвращаем кастомную 404 ошибку для блога return render_template("blog/404.html"), 404 else: # в противном случае возвращаем # общую 404 ошибку для всего сайта return render_template("404.html"), 404 @app.errorhandler(405) def method_not_allowed(e): # Если в запросе указан неверный метод к API if request.path.startswith('/api/'): # возвращаем json с 405 HTTP-ошибкой return jsonify(message="Method Not Allowed"), 405 else: # в противном случае возвращаем # общую 405 ошибку для всего сайта return render_template("405.html"), 405
Возврат ошибок API в формате JSON
При создании API-интерфейсов во Flask некоторые разработчики понимают, что встроенные исключения недостаточно выразительны для API-интерфейсов и что тип содержимого text/html
, который они генерируют, не очень полезен для потребителей API.
Используя те же методы, что и выше плюс flask.jsonify()
, можно возвращать ответы JSON на ошибки API. Функция flask.abort()
вызывается с аргументом description
. Обработчик ошибок будет использовать это как сообщение об ошибке JSON и установит код состояния на 404.
from flask import abort, jsonify @app.errorhandler(404) def resource_not_found(e): return jsonify(error=str(e)), 404 @app.route("/cheese") def get_one_cheese(): resource = get_resource() if resource is None: abort(404, description="Resource not found") return jsonify(resource)
Можно создавать собственные классы исключений. Например, можно ввести новое настраиваемое исключение для API, которое будет принимать правильное удобочитаемое сообщение, код состояния для ошибки и некоторую дополнительную полезную информацию, чтобы дать больше конкретики для ошибки.
Простой пример:
from flask import jsonify, request class InvalidAPIUsage(Exception): status_code = 400 def __init__(self, message, status_code=None, payload=None): super().__init__() self.message = message if status_code is not None: self.status_code = status_code self.payload = payload def to_dict(self): rv = dict(self.payload or ()) rv['message'] = self.message return rv @app.errorhandler(InvalidAPIUsage) def invalid_api_usage(e): return jsonify(e.to_dict()) # маршрут API для получения информации о пользователе # правильный запрос может быть /api/user?user_id=420 @app.route("/api/user") def user_api(user_id): user_id = request.arg.get("user_id") if not user_id: raise InvalidAPIUsage("No user id provided!") user = get_user(user_id=user_id) if not user: raise InvalidAPIUsage("No such user!", status_code=404) return jsonify(user.to_dict())
Теперь функция-представление может вызвать это исключение с сообщением об ошибке. Кроме того, дополнительная полезная информация может быть предоставлена в виде словаря через параметр payload
.
Работа с ошибками приложения
Приложения отказывают,серверы отказывают.Рано или поздно вы увидите исключение в производстве.Даже если ваш код на 100% правильный,вы все равно будете время от времени видеть исключения.Почему? Потому что все остальное,что задействовано в коде,не работает.Вот несколько ситуаций,когда совершенно правильный код может привести к ошибкам сервера:
- клиент завершил запрос раньше времени,а приложение все еще считывало входящие данные
- сервер базы данных был перегружен и не смог обработать запрос
- файловая система заполнена
- разбился жесткий диск
- перегрузка внутреннего сервера
- программная ошибка в используемой вами библиотеке
- сетевое подключение сервера к другой системе не удалось
И это лишь небольшая часть проблем, с которыми вы можете столкнуться. Итак, как нам справиться с такой проблемой? По умолчанию, если ваше приложение работает в производственном режиме и возникает исключение, Flask отобразит для вас очень простую страницу и зарегистрирует исключение в logger
.
Но есть и другие возможности,и мы расскажем о некоторых лучших настройках для работы с ошибками,включая пользовательские исключения и инструменты сторонних производителей.
Инструменты протоколирования ошибок
Отправка сообщений об ошибках, даже если это критические сообщения, может стать чрезмерным, если достаточное количество пользователей сталкивается с ошибкой, а файлы журналов обычно никогда не просматриваются. Вот почему мы рекомендуем использовать Sentry для работы с ошибками приложений. Он доступен в виде проекта с исходным кодом на GitHub , а также доступен в виде размещенной версии , которую вы можете попробовать бесплатно. Sentry собирает повторяющиеся ошибки, фиксирует полную трассировку стека и локальные переменные для отладки и отправляет вам письма на основе новых ошибок или пороговых значений частоты.
Чтобы использовать Sentry, вам необходимо установить клиент sentry-sdk
с дополнительными зависимостями flask
.
$ pip install sentry-sdk[flask]
А затем добавьте это в ваше приложение Flask:
import sentry_sdk from sentry_sdk.integrations.flask import FlaskIntegration sentry_sdk.init('YOUR_DSN_HERE', integrations=[FlaskIntegration()])
Значение YOUR_DSN_HERE
необходимо заменить значением DSN, полученным при установке Sentry.
После установки сбои,приводящие к внутренней ошибке сервера,автоматически сообщаются в Sentry,откуда вы можете получать уведомления об ошибках.
See also:
- Sentry также поддерживает перехват ошибок из рабочей очереди (RQ, Celery и т. Д.) Аналогичным образом. Дополнительную информацию см. В документации Python SDK .
- Начало работы с Sentry
- Flask-specific documentation
Error Handlers
Когда во Flask возникает ошибка, будет возвращен соответствующий код состояния HTTP . 400-499 указывают на ошибки в данных запроса клиента или в запрошенных данных. 500-599 указывают на ошибки сервера или самого приложения.
Вы можете захотеть показывать пользователю пользовательские страницы ошибок при возникновении ошибки.Это можно сделать,зарегистрировав обработчики ошибок.
Обработчик ошибок — это функция, которая возвращает ответ при возникновении ошибки определенного типа, подобно тому, как представление — это функция, возвращающая ответ при совпадении URL-адреса запроса. Ему передается экземпляр обрабатываемой ошибки, который, скорее всего, является HTTPException
.
Код состояния ответа не будет установлен на код обработчика.При возврате ответа от обработчика обязательно указывайте соответствующий код состояния HTTP.
Registering
Зарегистрируйте обработчики, украсив функцию errorhandler()
. Или используйте register_error_handler()
чтобы зарегистрировать функцию позже. Не забудьте установить код ошибки при возврате ответа.
@app.errorhandler(werkzeug.exceptions.BadRequest) def handle_bad_request(e): return 'bad request!', 400 app.register_error_handler(400, handle_bad_request)
Подклассы werkzeug.exceptions.HTTPException
,такие как BadRequest
, и их HTTP-коды взаимозаменяемы при регистрации обработчиков. ( BadRequest.code == 400
)
Нестандартные коды HTTP не могут быть зарегистрированы с помощью кода, поскольку они неизвестны Werkzeug. Вместо этого определите подкласс HTTPException
с соответствующим кодом, зарегистрируйте и поднимите этот класс исключения.
class InsufficientStorage(werkzeug.exceptions.HTTPException): code = 507 description = 'Not enough storage space.' app.register_error_handler(InsufficientStorage, handle_507) raise InsufficientStorage()
Обработчики могут быть зарегистрированы для любого класса исключений, а не только для подклассов HTTPException
или кодов состояния HTTP. Обработчики могут быть зарегистрированы для определенного класса или для всех подклассов родительского класса.
Handling
При создании приложения Flask вы столкнетесь с исключениями. Если какая-то часть вашего кода сломается при обработке запроса (и у вас нет зарегистрированных обработчиков ошибок), по умолчанию будет возвращено «500 Internal Server Error» ( InternalServerError
).Точно так же ошибка «404 Not Found» ( NotFound
) возникает, если запрос отправляется на незарегистрированный маршрут. Если маршрут получает неразрешенный метод запроса, будет поднято «Метод 405 не разрешен» ( MethodNotAllowed
).Все они являются подклассами HTTPException
и по умолчанию предоставляются во Flask.
Flask дает вам возможность поднять любое исключение HTTP,зарегистрированное Werkzeug.Однако HTTP-исключения по умолчанию возвращают простые страницы исключений.Возможно,вы захотите показывать пользователю пользовательские страницы ошибок при возникновении ошибки.Это можно сделать,зарегистрировав обработчики ошибок.
Когда Flask перехватывает исключение при обработке запроса, оно сначала ищется по коду. Если для кода не зарегистрирован обработчик, Flask ищет ошибку по иерархии классов; выбирается наиболее конкретный обработчик. Если обработчик не зарегистрирован, подклассы HTTPException
отображают общее сообщение об их коде, в то время как другие исключения преобразуются в общее сообщение «500 Internal Server Error».
Например, если возникает экземпляр ConnectionRefusedError
и зарегистрирован обработчик для ConnectionError
и ConnectionRefusedError
, более конкретный обработчик ConnectionRefusedError
вызывается с экземпляром исключения для генерации ответа.
Обработчики,зарегистрированные на чертеже,имеют приоритет над обработчиками,зарегистрированными глобально в приложении,при условии,что чертеж обрабатывает запрос,вызвавший исключение.Однако блюпринт не может обрабатывать ошибки маршрутизации 404,поскольку 404 происходит на уровне маршрутизации до того,как можно определить блюпринт.
Общие обработчики исключений
Можно зарегистрировать обработчики ошибок для очень общих базовых классов, таких как HTTPException
или даже Exception
. Однако имейте в виду, что они поймают больше, чем вы могли ожидать.
Например, обработчик ошибок для HTTPException
может быть полезен для преобразования страниц ошибок HTML по умолчанию в формат JSON. Однако этот обработчик срабатывает для вещей, которые вы не вызываете напрямую, таких как ошибки 404 и 405 во время маршрутизации. Тщательно создавайте свой обработчик, чтобы не потерять информацию об ошибке HTTP.
from flask import json from werkzeug.exceptions import HTTPException @app.errorhandler(HTTPException) def handle_exception(e): """Return JSON instead of HTML for HTTP errors.""" response = e.get_response() response.data = json.dumps({ "code": e.code, "name": e.name, "description": e.description, }) response.content_type = "application/json" return response
Обработчик ошибок для Exception
может показаться полезным для изменения способа представления пользователю всех ошибок, даже необработанных. Однако это похоже на выполнение, except Exception:
в Python он фиксирует все необработанные в противном случае ошибки, включая все коды состояния HTTP.
В большинстве случаев будет безопаснее зарегистрировать обработчики для более конкретных исключений. Поскольку экземпляры HTTPException
являются действительными ответами WSGI, вы также можете передать их напрямую.
from werkzeug.exceptions import HTTPException @app.errorhandler(Exception) def handle_exception(e): if isinstance(e, HTTPException): return e return render_template("500_generic.html", e=e), 500
Обработчики ошибок по-прежнему соблюдают иерархию классов исключений. Если вы зарегистрируете обработчики как для HTTPException
,так и для Exception
, обработчик Exception
не будет обрабатывать подклассы HTTPException
, поскольку он является более конкретным обработчиком HTTPException
.
Unhandled Exceptions
Если для исключения не зарегистрирован обработчик ошибок, вместо этого будет возвращена ошибка 500 Internal Server Error. См. flask.Flask.handle_exception()
для получения информации об этом поведении.
Если для InternalServerError
зарегистрирован обработчик ошибок , он будет вызван. Начиная с Flask 1.1.0, этому обработчику ошибок всегда будет передаваться экземпляр InternalServerError
, а не исходная необработанная ошибка.
Исходная ошибка доступна как e.original_exception
.
В обработчик ошибки «500 Internal Server Error» будут передаваться не пойманные исключения в дополнение к явным 500 ошибкам.В режиме отладки обработчик для «500 Internal Server Error» не будет использоваться.Вместо этого будет показан интерактивный отладчик.
Пользовательские страницы ошибок
Иногда при создании приложения Flask вы можете захотеть создать исключение HTTPException
, чтобы сообщить пользователю, что с запросом что-то не так. К счастью, Flask поставляется с удобной функцией abort()
, которая прерывает запрос с ошибкой HTTP от werkzeug по желанию. Он также предоставит вам простую черно-белую страницу ошибки с основным описанием, но ничего особенного.
В зависимости от кода ошибки вероятность того,что пользователь действительно увидит такую ошибку,меньше или больше.
Рассмотрим код ниже,у нас может быть маршрут профиля пользователя,и если пользователь не передает имя пользователя,мы можем выдать сообщение «400 Bad Request».Если пользователь передает имя пользователя,но мы не можем его найти,мы выдаем сообщение «404 Not Found».
from flask import abort, render_template, request @app.route("/profile") def user_profile(): username = request.arg.get("username") if username is None: abort(400) user = get_user(username=username) if user is None: abort(404) return render_template("profile.html", user=user)
Вот еще один пример реализации исключения «404 Page Not Found»:
from flask import render_template @app.errorhandler(404) def page_not_found(e): return render_template('404.html'), 404
При использовании фабрик приложений :
from flask import Flask, render_template def page_not_found(e): return render_template('404.html'), 404 def create_app(config_filename): app = Flask(__name__) app.register_error_handler(404, page_not_found) return app
Пример шаблона может быть следующим:
{% extends "layout.html" %} {% block title %}Page Not Found{% endblock %} {% block body %} <h1>Page Not Found</h1> <p>What you were looking for is just not there. <p><a href="{{ url_for('index') }}">go somewhere nice</a> {% endblock %}
Further Examples
Приведенные выше примеры на самом деле не являются улучшением стандартных страниц исключений.Мы можем создать пользовательский шаблон 500.html следующим образом:
{% extends "layout.html" %} {% block title %}Internal Server Error{% endblock %} {% block body %} <h1>Internal Server Error</h1> <p>Oops... we seem to have made a mistake, sorry!</p> <p><a href="{{ url_for('index') }}">Go somewhere nice instead</a> {% endblock %}
Это можно реализовать путем рендеринга шаблона при «500 Internal Server Error»:
from flask import render_template @app.errorhandler(500) def internal_server_error(e): return render_template('500.html'), 500
При использовании фабрик приложений :
from flask import Flask, render_template def internal_server_error(e): return render_template('500.html'), 500 def create_app(): app = Flask(__name__) app.register_error_handler(500, internal_server_error) return app
При использовании модульных приложений с Blueprints :
from flask import Blueprint blog = Blueprint('blog', __name__) @blog.errorhandler(500) def internal_server_error(e): return render_template('500.html'), 500 blog.register_error_handler(500, internal_server_error)
Обработчики ошибок чертежей
В Modular Applications with Blueprints большинство обработчиков ошибок будут работать должным образом. Однако есть предостережение относительно обработчиков исключений 404 и 405. Эти обработчики ошибок вызываются только из соответствующего оператора raise
или вызова abort
в другой функции представления схемы; они не вызываются, например, недопустимым доступом по URL-адресу.
Это связано с тем, что схема не «владеет» определенным пространством URL-адресов, поэтому экземпляр приложения не может узнать, какой обработчик ошибок схемы следует запустить, если указан недопустимый URL-адрес. Если вы хотите использовать разные стратегии обработки этих ошибок на основе префиксов URL, их можно определить на уровне приложения с помощью прокси-объекта request
from flask import jsonify, render_template @app.errorhandler(404) def page_not_found(e): if request.path.startswith('/blog/'): return render_template("blog/404.html"), 404 else: return render_template("404.html"), 404 @app.errorhandler(405) def method_not_allowed(e): if request.path.startswith('/api/'): return jsonify(message="Method Not Allowed"), 405 else: return render_template("405.html"), 405
Возврат ошибок API в формате JSON
При создании API-интерфейсов во Flask некоторые разработчики понимают, что встроенные исключения недостаточно выразительны для API-интерфейсов и что тип содержимого text / html, который они генерируют, не очень полезен для потребителей API.
Используя те же методы, что и выше, и jsonify()
, мы можем возвращать ответы JSON на ошибки API. abort()
вызывается с параметром description
. Обработчик ошибок будет использовать это как сообщение об ошибке JSON и установит код состояния 404.
from flask import abort, jsonify @app.errorhandler(404) def resource_not_found(e): return jsonify(error=str(e)), 404 @app.route("/cheese") def get_one_cheese(): resource = get_resource() if resource is None: abort(404, description="Resource not found") return jsonify(resource)
Мы также можем создавать пользовательские классы исключений.Например,мы можем создать новое пользовательское исключение для API,которое может принимать сообщение,читаемое человеком,код состояния ошибки и некоторую необязательную полезную нагрузку для создания дополнительного контекста ошибки.
Это простой пример:
from flask import jsonify, request class InvalidAPIUsage(Exception): status_code = 400 def __init__(self, message, status_code=None, payload=None): super().__init__() self.message = message if status_code is not None: self.status_code = status_code self.payload = payload def to_dict(self): rv = dict(self.payload or ()) rv['message'] = self.message return rv @app.errorhandler(InvalidAPIUsage) def invalid_api_usage(e): return jsonify(e.to_dict()), e.status_code @app.route("/api/user") def user_api(user_id): user_id = request.arg.get("user_id") if not user_id: raise InvalidAPIUsage("No user id provided!") user = get_user(user_id=user_id) if not user: raise InvalidAPIUsage("No such user!", status_code=404) return jsonify(user.to_dict())
Теперь представление может вызвать это исключение с сообщением об ошибке. Кроме того, некоторая дополнительная полезная нагрузка может быть предоставлена в виде словаря через параметр payload
.
Logging
См. Ведение журнала для получения информации о том, как регистрировать исключения, например, отправляя их администраторам по электронной почте.
Debugging
См. Отладка ошибок приложений для получения информации о том, как отлаживать ошибки при разработке и производстве.
Flask
2.2
-
Waitress
-
Проектные решения во Flask
-
Extensions
-
Installation
We’ll discuss redirects and errors with Python Flask in this article. A redirect is used in the Flask class to send the user to a particular URL with the status code. conversely, this status code additionally identifies the issue. When we access a website, our browser sends a request to the server, and the server replies with what is known as the HTTP status code, which is a three-digit number, The different reasons for errors are:
- Unauthorized access or poor request.
- Unsupported media file types.
- Overload of the backend server.
- Internal hardware/connection error.
Syntax of Redirect
flask.redirect(location,code=302)
Parameters:
- location(str): the location which URL directs to.
- code(int): The status code for Redirect.
- Code: The default code is 302 which means that the move is only temporary.
Return: The response object and redirects the user to another target location with the specified code.
The different types of HTTP codes are:
Code |
Status |
---|---|
300 | Multiple_choices |
301 | Moved_permanently |
302 | Found |
303 | See_other |
304 | Not_modified |
305 | Use_proxy |
306 | Reserved |
307 | Temporary_redirect |
Import the redirect attribute
We can redirect the URL using Flask by importing redirect. Let’s see how to import redirects from Flask
Python3
from
flask
import
redirect
Example:
Let’s take a simple example to redirect the URL using a flask named app.py.
Python3
from
flask
import
Flask, redirect, url_for, render_template, request
app
=
Flask(__name__)
@app
.route(
'/'
)
def
index():
return
render_template(
"login.html"
)
@app
.route(
'/success'
)
def
success():
return
"logged in successfully"
@app
.route(
"/login"
, methods
=
[
"POST"
,
"GET"
])
def
login():
if
request.method
=
=
"POST"
and
request.form[
"username"
]
=
=
"admin"
:
return
redirect(url_for(
"success"
))
return
redirect(url_for(
'index'
))
if
__name__
=
=
'__main__'
:
app.run(debug
=
True
)
login.html
Create a folder named templates. In the templates, creates a templates/login.html file. On this page, we will take input from the user of its username.
HTML
<!DOCTYPE html>
<
html
lang
=
"en"
>
<
body
>
<
form
method
=
"POST"
,
action
=
"login"
>
Username: <
input
name
=
username
type
=
text
></
input
><
br
>
<
button
type
=
"submit"
>Login</
button
>
</
form
>
</
body
>
</
html
>
Now we will deploy our code.
python app.py
Output:
Case 1: If the Username is admin and the method is POST then it will redirect to the success URL and display logged in successfully.
Case 2: In the other case, if the Username is something like xyz or anything then it will redirect to the index method i.e., it will load the form again until the username is admin.
Flasks Errors
If there is an error in the address or if there is no such URL then Flask has an abort() function used to exit with an error code.
Syntax of abort() method
Syntax: abort(code, message)
- code: int, The code parameter takes any of the following values
- message: str, create your custom message Error.
The different types of errors we can abort in the application in your Flask.
Code |
Error |
---|---|
400 | Bad request |
401 | Unauthenticated |
403 | Forbidden |
404 | Not Found |
406 | Not Acceptable |
415 | Unsupported Media Type |
429 | Too Many Requests |
Example to demonstrate abort
In this example, if the username starts with a number then an error code message will through, else on success “Good username” will be printed.
Example 1:
Python3
from
flask
import
Flask, abort
app
=
Flask(__name__)
@app
.route(
'/<uname>'
)
def
index(uname):
if
uname[
0
].isdigit():
abort(
400
)
return
'<h1>Good Username</h1>'
if
__name__
=
=
'__main__'
:
app.run()
Output:
Case 1: If the username doesn’t start with a number.
Case 2: If the username starts with a number.
Example 2:
In the above Python code, the status code to the abort() is 400, so it raises a Bad Request Error. Let’s try to change the code to 403. If the username starts with a number then it raises a Forbidden Error.
Python3
from
flask
import
Flask, abort
app
=
Flask(__name__)
@app
.route(
'/<uname>'
)
def
index(uname):
if
uname[
0
].isdigit():
abort(
403
)
return
'<h1>Good Username</h1>'
if
__name__
=
=
'__main__'
:
app.run()
Output:
Last Updated :
07 Nov, 2022
Like Article
Save Article