Расширенный «Hello! World» на микрофреймворке Flask

image

Добрый день, хабраюзер.

Не так давно мы с другом начали делать небольшую текстовую игрушку в рамках фантастического проекта «Версум». В статье я хочу рассказать о тех проблемах, с которыми нам пришлось столкнутся, а так же о том, какие пути решения нами были выбраны.

Мы используем 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.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 7

    +3
    Отлично, что описали свой опыт в виде туториала для начинающих.

    Со своей стороны могу добавить (в коллекцию для новичков) более наглядный пример использования Flask на примере создания гостевой книги и последующем деплое на Heroku.

    Автор этого сайта (блога) опубликовал много разных полезных статей, связанных не только с Питоном.
      0
      Спасибо, не попадал на его записи через поиск, интересно почитать.
      +1
      Добавлю более навороченный скелет, который появился совсем недавно.
      mattupstate.com/python/2013/06/26/how-i-structure-my-flask-applications.html
      Он подходит для более навороченных и сложных приложений. Однозначно стоит ознакомиться.
        0
        Спасибо, может пригодится в скором времени.
        0
        Как подсунуть IP из заголовка X-Forwarded-For при проксировании через nginx?

        Фикс который предлагается в доках у меня не работает flask.pocoo.org/docs/deploying/others/

        from werkzeug.contrib.fixers import ProxyFix
        app.wsgi_app = ProxyFix(app.wsgi_app)
        


        Приходится извращаться с конструкциями вида request.headers.get(«X-Forwarded-For»)
          0
          Блин, дал ссылку на блог Miguel, не заметив что она уже есть в статье.
            0
            Спасибо за туториал. Только недавно подумал про Flask для создания небольших сервисов на скорую руку, где Django излишне. Для всего остального не могу без Django. Сколько не пытался от Django уйти, так и не смог :) В одном проекте правда использую WebOb.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое