
Добрый день, хабраюзер.
Не так давно мы с другом начали делать небольшую текстовую игрушку в рамках фантастического проекта «Версум». В статье я хочу рассказать о тех проблемах, с которыми нам пришлось столкнутся, а так же о том, какие пути решения нами были выбраны.
Мы используем 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
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 выглядит следующим образом:
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, в котором хранится бизнес-логика. Минимальный набор для старта может быть таким:
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.
