- •ВВЕДЕНИЕ
- •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
- •ЗАКЛЮЧЕНИЕ
- •СПИСОК ЛИТЕРАТУРЫ
# установка зависимостей
RUN pip install -r requirements.txt
# добавление скрипта entrypoint.sh
COPY ./entrypoint.sh /usr/src/app/entrypoint.sh
# добавление приложения
COPY . /usr/src/app
# запуск сервера
CMD ["./entrypoint.sh"]
Обновим права доступа к файлам:
$ chmod +x services/users/entrypoint.sh
Выполним итоговую проверку:
$ docker-compose -f docker-compose-dev.yml up -d –build
Убедимся, что сервис http://DOCKER_MACHINE_IP: 5001/users/ping всё
ещё работает:
{
"message": "pong!",
"status": "success"
}
Обновим файл manage.py:
# services/users/manage.py
from flask.cli import FlaskGroup from project import app, db
cli = FlaskGroup(app)
@cli.command() def recreate_db():
db.drop_all() db.create_all() db.session.commit()
if __name__ == '__main__': cli()
1.4. НАСТРОЙКА ТЕСТОВ
Начнём процесс написания тестов для приложения. Для этого добавим каталог tests в каталог project, а затем создадим следующие файлы во вновь созданном каталоге:
14
1.__init__.py
2.base.py
3.test_config.py
4.test_users.py
Содержимое файлов показано ниже:
__init__.py
# services/users/project/tests/__init__.py
base.py
# services/users/project/tests/base.py from flask_testing import TestCase from project import app, db
class BaseTestCase(TestCase): def create_app(self):
app.config.from_object('project.config.TestingConfig') return app
def setUp(self): db.create_all() db.session.commit()
def tearDown(self): db.session.remove() db.drop_all()
test_config.py
# services/users/project/tests/test_config.py import os
import unittest
from flask import current_app from flask_testing import TestCase
from project import app
class TestDevelopmentConfig(TestCase): def create_app(self):
app.config.from_object('project.config.DevelopmentConfig') return app
def test_app_is_development(self): self.assertTrue(app.config['SECRET_KEY'] == 'secret_key') self.assertFalse(current_app is None)
self.assertTrue( app.config['SQLALCHEMY_DATABASE_URI'] ==
15
os.environ.get('DATABASE_URL')
)
class TestTestingConfig(TestCase): def create_app(self):
app.config.from_object('project.config.TestingConfig') return app
def test_app_is_testing(self): self.assertTrue(app.config['SECRET_KEY'] == 'secret_key') self.assertTrue(app.config['TESTING'])
self.assertFalse( app.config['PRESERVE_CONTEXT_ON_EXCEPTION'])
self.assertTrue( app.config['SQLALCHEMY_DATABASE_URI'] == os.environ.get('DATABASE_TEST_URL')
)
class TestProductionConfig(TestCase): def create_app(self):
app.config.from_object('project.config.ProductionConfig') return app
def test_app_is_production(self): self.assertTrue(app.config['SECRET_KEY'] == 'secret_key') self.assertFalse(app.config['TESTING'])
if __name__ == '__main__': unittest.main()
test_users.py
# services/users/project/tests/test_users.py import json
import unittest
from project.tests.base import BaseTestCase
class TestUserService(BaseTestCase):
""" Тесты для службы User.""" def test_users(self):
""" Проверка, что маршрут /ping работает корректно.""" response = self.client.get('/users/ping')
data = json.loads(response.data.decode()) self.assertEqual(response.status_code, 200) self.assertIn('pong!', data['message']) self.assertIn('success', data['status'])
16
if __name__ == '__main__':
unittest.main()
Добавим пакет Flask-Testing в файл с требованиями:
Flask-Testing==0.6.2
Добавим новую команду в manage.py для обнаружения и запуска тестов:
@cli.command() def test():
""" Запускает тесты без покрытия кода"""
tests = unittest.TestLoader().discover('project/tests', \ pattern='test*.py')
result = unittest.TextTestRunner(verbosity=2).run(tests) if result.wasSuccessful():
return 0 return 1
Далее импортируем библиотеку unittest:
import unittest
Нам нужно пересобрать образы, поскольку пакеты из файлов с требова-
ниями устанавливаются во время сборки, а не во время выполнения:
$ docker-compose -f docker-compose-dev.yml up -d –build
После запуска контейнеров необходимо запустить тесты:
$ docker-compose -f docker-compose-dev.yml \ run users python manage.py test
После запуска тестов должно появиться следующее сообщение об ошибке:
self.assertTrue(app.config['SECRET_KEY'] == 'secret_key')
Обновим базовую конфигурацию:
class BaseConfig:
"""Базовая конфигурация"""
TESTING = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = 'secret_key'
И проверим результаты выполнения тестов:
------------------------------------------------------------------
Ran 4 tests in 0.071s
OK
1.5.РЕФАКТОРИНГ ПРИЛОЖЕНИЯ
СИСПОЛЬЗОВАНИЕМ FLASK BLUEPRINT
Имея написанные тесты, выполним рефакторинг приложения, добавив в
него концепцию Blueprint.
17
Создадим новый каталог в project с имненм api и добавим файлы
__init__.py, users.py и models.py. Затем в users.py добавим следующий код:
# services/users/project/api/users.py users_blueprint = Blueprint('users', __name__)
@users_blueprint.route('/users/ping', methods=['GET']) def ping_pong():
return jsonify({ 'status': 'success', 'message': 'pong!'
})
Мы создали новый экземпляр класса Blueprint и связали с ним функцию ping_pong(). Затем добавим в models.py следующий код:
# services/users/project/api/models.py class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(128), nullable=False)
email = db.Column(db.String(128), nullable=False)
active = db.Column(db.Boolean(), default=True, nullable=False)
def __init__(self, username, email): self.username = username self.email = email
Обновим файл project/__init__.py, удалив маршрут и модель и добавив
шаблон фабрики приложений:
#services/users/project/__init__.py import os
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
#инстанционирование базы данных
db = SQLAlchemy()
def create_app(script_info=None):
# инстанционирование приложения app = Flask(__name__)
# подключение конфигурации app_settings = os.getenv('APP_SETTINGS') app.config.from_object(app_settings)
#запуск расширений db.init_app(app)
18
# регистрация blueprints
from project.api.users import users_blueprint app.register_blueprint(users_blueprint)
# контекст оболочки для flask cli app.shell_context_processor({'app': app, 'db': db}) return app
Обратите внимание на shell_context_processor. Этот механизм использу-
ется для регистрации приложения и базы данных в оболочке. Теперь мы можем работать с контекстом приложения и базой данных, не импортируя их непо-
средственно в оболочку.
Обновим файл manage.py:
# services/users/manage.py import unittest
from flask.cli import FlaskGroup
from project import create_app, db from project.api.models import User
app = create_app()
cli = FlaskGroup(create_app=create_app)
@cli.command() def recreate_db():
db.drop_all() db.create_all() db.session.commit()
@cli.command() def test():
"""Запускает тесты без покрытия кода"""
tests = unittest.TestLoader().discover('project/tests',\ pattern='test*.py')
result = unittest.TextTestRunner(verbosity=2).run(tests) if result.wasSuccessful():
return 0 return 1
if __name__ == '__main__': cli()
Обновим содержание части импорта в заголовке файлов project/tests/
base.py и project/tests/test_config.py:
19
