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

в 12:58, , рубрики: flask, python, Песочница, метки: ,

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

# -*- 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js