Добрый день, читатель.
Не так давно мы с другом начали делать небольшую текстовую игрушку в рамках фантастического проекта «Версум». В статье я хочу рассказать о тех проблемах, с которыми нам пришлось столкнутся, а так же о том, какие пути решения нами были выбраны.
Мы используем Python, в частности микрофреймворк Flask.
Устанавливаем Flask
Процесс достаточно простой
pip install flask pip install sqlalchemy pip install flask-sqlalchemy pip install alembic
В тех мануалах, которые попадались мне, для миграция используется sqlalchemy-migrate, но мне кажется он чуть более чем ужасен. У него зависимость от версии sqlalchemy, то есть для использования миграций надо специально подбирать версии друг к другу. К тому же на странице sqlalchemy-migrate так и написано:
Если вы хотите начать свой проект с участием SQLAlchemy и вам нужна миграция схем данных, то используйте Alembic
Грех не воспользоваться советом.
cd ~ mkdir flask cd flask alembic init alembic mkdir app_name
И переходим к следующему пункту.
Скелет приложения
Для написание простенького «Hello, World!» есть хороший мануал на официальном сайте. Но, наигравшись в песочнице, мы начали собирать скелет своего будущего приложения и получили первые проблемы, если можно так выразиться. Если Django навязывает нам свою структуру приложения, генерируя ее автоматически после startproject и startapp, то Flask дает нам полную свободу. Не знаю, стоит ли считать это «проблемой», но когда совершенно не знаешь куда копать становится грустно, и руки опускаются.
Однако, после длительной медитации над официальным учебником, после долгого разглядывания репозиториев на гитхабе (в частности поисковая выдача по запросу «flask skeleton»), после прочтения серии статей (автор Miguel Grinberg) — пришло некоторое прозрение и успокоение.
В конечном итоге скелет нашего приложения выглядит следующим образом:
~/flask |- /alembic |- /app_name | |- /static | | |- /css | | |- /js | | |- /img | |- /templates | | |- index.html | |- __init__.py | |- config.py | |- models.py | |- views.py |- alembic.ini |- README.md |- requirements.txt |- runserver.py
Пойдем по-порядку, flask — это корневая папка проекта, здесь инициализирован git, здесь создано виртуальное окружение, в общем лежит все, что относится к проекту, в том числе и файл запуска dev-сервера — runserver.py. Он достаточно прост, я пользуюсь им для запуска приложения на локальной машине:
#!/usr/bin/env python
from app_name import app
if __name__ == '__main__':
if app.debug:
app.run(debug=True)
else:
app.run(host='0.0.0.0')
Все самое необходимое, в том числе инициализация приложения и его модулей, происходит в __init__.py
# -*- coding: utf-8 -*-
import os
FLASK_APP_DIR = os.path.dirname(os.path.abspath(__file__))
# Flask
from flask import Flask
app = Flask(__name__)
# Config
app.config.from_object('app_name.config.DevelopmentConfig')
# ProductionConfig
#app.config.from_object('app_name.config.ProductionConfig')
# Connect to database with sqlalchemy.
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
# Business Logic
# http://flask.pocoo.org/docs/patterns/packages/
# http://flask.pocoo.org/docs/blueprints/
from app_name.views import frontend
app.register_blueprint(frontend)
Сам же config.py выглядит следующим образом:
# -*- coding: utf-8 -*-
class Config(object):
SECRET_KEY = 'some_secret'
SITE_NAME = 'app_name.ru'
SQLALCHEMY_DATABASE_URI = 'mysql://user:pass@localhost/tabe_name?charset=utf8'
class ProductionConfig(Config):
DEBUG = False
TESTING = False
class TestConfig(Config):
DEBUG = False
TESTING = True
class DevelopmentConfig(Config):
'''Use "if app.debug" anywhere in your code, that code will run in development code.'''
DEBUG = True
TESTING = True
Теперь чтобы достучатся до объекта app, который повсеместно используется в приложении (при условии, что в PYTHONPATH у вас добавлена папка flask):
from app_name import app
При этом подключается все необходимое для работы приложения.
При инициализации alembic в корне так же будет лежать alembic.ini, но нам его трогать надобности нет. Для интеграции с нашим приложением нужно будет залезть в файл env.py, который лежит внутри папки alembic.
В него после строчки # target_metadata = mymodel.Base.metadata надо дописать:
# target_metadata = mymodel.Base.metadata
from app_name import db
target_metadata = db.metadata
и переписать run_migrations_online:
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
alembic_config = config.get_section(config.config_ini_section)
from app_name import app
alembic_config['sqlalchemy.url'] = app.config['SQLALCHEMY_DATABASE_URI']
engine = engine_from_config(
alembic_config,
prefix='sqlalchemy.',
poolclass=pool.NullPool)
connection = engine.connect()
context.configure(
connection=connection,
target_metadata=target_metadata
)
try:
with context.begin_transaction():
context.run_migrations()
finally:
connection.close()
На этом интеграция alembic с нашим приложением завершена. Для автоматической миграции используем:
alembic revision --autogenerate -m 'some text'
При том создаются файлы миграций вида hashcode_some_text.py в папке versions. Желательно, а иногда даже необходимо, в них заглядывать. По словам самих же разработчиков, alembic не понимает переименования таблиц и полей, а так же плохо создает ключи.
Для применения миграции используем следующую команду:
alembic upgrade head
Последней строкой в файле config.py мы цепляем views.py, в котором хранится бизнес-логика. Минимальный набор для старта может быть таким:
# -*- coding: utf-8 -*-
from app_name import app, db
from flask import Blueprint, request, render_template
frontend = Blueprint('frontend', __name__)
# 404 page not found "route"
@app.errorhandler(404)
def not_found(error):
title = "404 Page not found"
return render_template('404.html', title=title), 404
# 500 server error "route"
@app.errorhandler(500)
def server_error(error):
title = "500 Server Error"
db.session.rollback()
return render_template('500.html', title=title), 500
# general routes
@frontend.route('/')
def index():
return render_template(
'index.html',
title = u'Добро пожаловать',
)
В самом начале добавлены хендлеры для ошибок, которые будут работать после отключения gebug. В общем, это все тот же «Hello, World!», но в расширенном варианте.
Файл models.py будет хранить наши модели, пока что он пуст. Со статикой и шаблонами, которые соответственно лежат в static и templates, вроде все понятно.
Запускаем на сервере
После запуска на сервере с nginx и uwsgi выяснилось, что при падении красивый деббагер не появляется. При старте сервера через runserver.py все хорошо, деббагер на месте.
Демон uwsgi пишет подробную информация об ошибках в свои логи, так что не все так плохо. Баг достаточно старый, по крайней мере нашелся годовалый вопрос на stackoverflow. Похоже, что это проблема самого uwsgi или их дружбы с nginx, но решить это недоразумение мы так и не смогли. Довольствуемся деббагером на локальной машине и сообщениями об ошибках на почту.
Надеюсь эта статья поможет сохранить немного времени тем, кто впервые будет писать свое приложение на Flask.
Автор: jafte