- •ВВЕДЕНИЕ
- •1. ПОДГОТОВКА К РАЗРАБОТКЕ
- •1.1. НАЧАЛО РАБОТЫ
- •1.2. ИСПОЛЬЗОВАНИЕ КОНТЕЙНЕРОВ DOCKER
- •1.3. НАСТРОЙКА СУБД POSTGRESQL
- •1.4. НАСТРОЙКА ТЕСТОВ
- •1.6. СОЗДАНИЕ RESTFUL-МАРШРУТОВ
- •1.6.1. МАРШРУТ POST /USERS
- •1.6.3. МАРШРУТ GET /USERS
- •1.6.2. МАРШРУТ GET /USERS/:ID
- •1.7. ИСПОЛЬЗОВАНИЕ ШАБЛОНОВ JINJA
- •1.8. СТРУКТУРА ПРОЕКТА
- •2. ОТЛАДКА ПРИЛОЖЕНИЯ
- •2.1. ПОКРЫТИЕ ТЕСТАМИ И КАЧЕСТВО КОДА
- •2.1.1. ПОКРЫТИЕ КОДА ТЕСТАМИ
- •2.1.2. КАЧЕСТВО КОДА
- •2.2. НЕПРЕРЫВНАЯ ИНТЕГРАЦИЯ
- •2.3. ПАНЕЛЬ ИНСТРУМЕНТОВ ОТЛАДКИ FLASK
- •2.4. СТРУКТУРА ПРИЛОЖЕНИЯ
- •3. РАЗВИТИЕ ПРИЛОЖЕНИЯ
- •3.1. ПАКЕТ FLASK MIGRATE
- •3.1.1. ИЗМЕНЕНИЕ МОДЕЛИ
- •3.1.2. НАСТРОЙКА FLASK MIGRATE
- •3.2. РЕФАКТОРИНГ КОДА
- •3.3. ПАКЕТ FLASK BCRYPT
- •3.4. НАСТРОЙКА LOG ROUNDS
- •3.5. ИСПОЛЬЗОВАНИЕ ТОКЕНОВ JWT
- •3.5.1. ПАКЕТ PYJWT
- •3.5.2. РАСШИФРОВКА ТОКЕНА
- •3.6. МАРШРУТЫ ДЛЯ АУТЕНТИФИКАЦИИ
- •3.6.1. МАРШРУТ POST /AUTH/REGISTER
- •3.6.2. МАРШРУТ POST /AUTH/LOGIN
- •3.6.3. МАРШРУТ GET /AUTH/LOGOUT
- •3.6.4. МАРШРУТ GET /AUTH/STATUS
- •ЗАКЛЮЧЕНИЕ
- •СПИСОК ЛИТЕРАТУРЫ
from project import create_app
app = create_app()
Также импортируем пакет db в файле base.py.
Наконец, удалим переменную среды FLASK_APP из файла docker-
compose-dev.yml:
environment:
-FLASK_DEBUG=1
-APP_SETTINGS=project.config.DevelopmentConfig
-DATABASE_URL=postgres://postgres:postgres@usersdb:5432/users_dev
-DATABASE_TEST_URL=postgres://postgres:postgres@usersdb:5432/users_test
Выполним контрольную проверку текущей версии приложения:
$ docker-compose -f docker-compose-dev.yml up -d $ docker-compose -f docker-compose-dev.yml \
run users python manage.py recreate_db
$ docker-compose -f docker-compose-dev.yml \ run users python manage.py test
1.6. СОЗДАНИЕ RESTFUL-МАРШРУТОВ
Создадим три новых маршрута, следуя лучшим практикам архитектурно-
го стиля RESTful, с использованием принципов разработки через тестирование
(Test-driven development, TDD). Новые маршруты перечислены в таблице ниже:
Точка доступа |
HTTP-метод |
CRUD-метод |
Результат |
|
|
|
|
/users |
GET |
READ |
Получение данных всех |
|
|
|
пользователей |
/users/:id |
GET |
READ |
Получение данных одного |
|
|
|
пользователя |
/users |
POST |
CREATE |
Добавление пользователя |
Далее для каждого из новых маршрутов мы выполним следующие дейст-
вия:
1)создадим тесты;
2)выполним тесты и увидим неудачные результаты («красный цвет» со-
гласно методологии TDD);
3) напишем необходимый код для получения успешного результата тес-
тов («зелёный цвет» согласно методологии TDD);
4) выполним рефакторинг кода (при необходимости).
20
1.6.1. МАРШРУТ POST /USERS
Начнём с создания POST-маршрута. Добавим тест в класс
TestUserService() в файл project/tests/test_users.py:
def test_add_user(self):
"""Проверка, что в базу данных можно добавить нового пользовате-
ля."""
with self.client:
response = self.client.post( '/users', data=json.dumps({
'username': 'testname1',
'email': 'testname1@flaskapp.org' }), content_type='application/json', )
data = json.loads(response.data.decode()) self.assertEqual(response.status_code, 201) self.assertIn('testname1@flaskapp.org was added!', da-
ta['message'])
self.assertIn('success', data['status'])
Затем добавим обработчик маршрута в файл project/api/users.py
@users_blueprint.route('/users', methods=['POST']) def add_user():
post_data = request.get_json() username = post_data.get('username') email = post_data.get('email')
db.session.add(User(username=username, email=email)) db.session.commit()
response_object = { 'status': 'success',
'message': f'{email} was added!'
}
return jsonify(response_object), 201
Также обновим импорт:
from flask import Blueprint, jsonify, request from project.api.models import User
from project import db
Запустим тесты. Все тесты должны выполнится успешно:
Ran 5 tests in 0.092s
OK
21
А что насчёт ошибок и исключений? Рассмотрим следующие варианты.
1.Полезная нагрузка не была отправлена.
2.Полезная нагрузка некорректна, т.е. объект JSON пустой или содер-
жит неправильные ключи.
3. Пользователь уже существует в базе данных.
Добавим несколько тестов для случаев, перечисленных выше:
def test_add_user_invalid_json(self):
"""Проверка, что выдается ошибка, если объект JSON пуст.""" with self.client:
response = self.client.post( '/users', data=json.dumps({}),
content_type='application/json',
)
data = json.loads(response.data.decode()) self.assertEqual(response.status_code, 400) self.assertIn('Invalid payload.', data['message']) self.assertIn('fail', data['status'])
def test_add_user_invalid_json_keys(self):
"""Проверка, что выдаётся ошибка, если объект JSON не имеет клю-
ча username.""" with self.client:
response = self.client.post( '/users',
data=json.dumps({'email': 'testname1@flaskapp.org'}), content_type='application/json',
)
data = json.loads(response.data.decode()) self.assertEqual(response.status_code, 400) self.assertIn('Invalid payload.', data['message']) self.assertIn('fail', data['status'])
def test_add_user_duplicate_email(self):
"""Проверка, что выдаётся ошибка, если адрес электронной почты уже существует."""
with self.client: self.client.post(
'/users',
data=json.dumps({
22
'username': 'testname1',
'email': 'testname1@flaskapp.org' }), content_type='application/json',
)
response = self.client.post( '/users', data=json.dumps({
'username': 'testname1', 'email': 'testname1@flaskapp.org'
}), content_type='application/json',
)
data = json.loads(response.data.decode()) self.assertEqual(response.status_code, 400) self.assertIn(
'Sorry. That email already exists.', data['message']) self.assertIn('fail', data['status'])
Убедимся, что тесты после запуска завершаются неудачно, а затем обно-
вим обработчик маршрута:
@users_blueprint.route('/users', methods=['POST']) def add_user():
post_data = request.get_json() response_object = {
'status': 'fail',
'message': 'Invalid payload.'
}
if not post_data:
return jsonify(response_object), 400 username = post_data.get('username') email = post_data.get('email')
try:
user = User.query.filter_by(email=email).first() if not user:
db.session.add(User(username=username, email=email)) db.session.commit()
response_object['status'] = 'success' response_object['message'] = f'{email} was added!' return jsonify(response_object), 201
else:
23
