🐛 Типичные ошибки
⚡ Топ-3 ошибки урока
- Нет db.session.commit() — объект создан в памяти, но в БД не записан.
- Не проверен None — после get_json() или session.get() обращение к полям без проверки → AttributeError / TypeError.
- Legacy Query API —
Model.query.get(id)устарел; используйdb.session.get(Model, id).
1. Забытый db.session.commit()
Симптом
Запрос возвращает 201 Created, но при повторном GET объект не появляется. Данные «теряются» при перезапуске сервера.
Причина
db.session.add() помещает объект во временный буфер (unit of work). Без commit() транзакция не отправляется в базу данных.
Плохо
question = Question(text=data['text'])
db.session.add(question)
# Забыли commit!
return jsonify({'id': question.id}), 201
Хорошо
question = Question(text=data['text'])
db.session.add(question)
db.session.commit() # Обязательно!
return jsonify({'id': question.id}), 201
2. Не проверяется None после get_json()
Симптом
TypeError: 'NoneType' object is not subscriptable когда клиент забывает заголовок Content-Type: application/json.
Причина
request.get_json() возвращает None если тело запроса не является корректным JSON или заголовок Content-Type не установлен.
Плохо
data = request.get_json()
question = Question(text=data['text']) # KeyError или TypeError!
Хорошо
data = request.get_json()
if not data or 'text' not in data:
return jsonify({'error': 'No question text provided'}), 400
question = Question(text=data['text'])
3. Не проверяется None после session.get()
Симптом
AttributeError: 'NoneType' object has no attribute 'text' при запросе несуществующего ID.
Причина
db.session.get(Model, id) возвращает None если запись не найдена. Обращение к атрибутам None — AttributeError.
Плохо
question = db.session.get(Question, id)
return jsonify({'text': question.text}) # AttributeError если None
Хорошо
question = db.session.get(Question, id)
if question is None:
return jsonify({'message': 'Не найден'}), 404
return jsonify({'text': question.text})
4. Использование устаревшего Query.query.get()
Симптом
Предупреждение LegacyAPIWarning: The Query.get() method is considered legacy или поведение меняется при обновлении SQLAlchemy.
Причина
Model.query.get(id) — Legacy Query API, устаревший в SQLAlchemy 2.0.
Плохо (из лекции)
question = Question.query.get(id)
Хорошо (SQLAlchemy 2.x)
question = db.session.get(Question, id)
5. Двойной commit или commit до add
Симптом
Частичное сохранение данных при ошибке между двумя commit. Или DetachedInstanceError.
Причина
Несколько commit() нарушают атомарность. Если ошибка произойдёт между ними, часть данных сохранится, часть — нет.
Плохо
db.session.add(response)
db.session.commit() # сохранили Response
db.session.add(statistic)
db.session.commit() # если тут ошибка — Response уже в БД
Хорошо — атомарно
db.session.add(response)
db.session.add(statistic)
db.session.commit() # оба объекта или ни один
6. Неверный URL_prefix в Blueprint
Симптом
Flask возвращает 404 на все запросы к Blueprint, хотя маршруты определены.
Причина
При регистрации Blueprint указан неверный url_prefix, или Blueprint задекорирован с /, а регистрируется без prefix.
Плохо
questions_bp = Blueprint('questions', __name__, url_prefix='/questions')
# В маршруте:
@questions_bp.route('/questions/', methods=['GET']) # Двойной /questions!
Хорошо
questions_bp = Blueprint('questions', __name__, url_prefix='/questions')
# В маршруте:
@questions_bp.route('/', methods=['GET']) # /questions/ — правильно
7. Нет заголовка Content-Type при тестировании в Postman
Симптом
request.get_json() возвращает None, хотя тело запроса заполнено.
Причина
Postman не добавляет Content-Type: application/json автоматически при выборе «raw».
Решение
В Postman: Body → raw → выбрать JSON в выпадающем меню (не Text). Или вручную добавить заголовок Content-Type: application/json.