Новости

Как правильно применять в JavaScript асинхронные функции: примеры работы с ES 2017
От автора: возможность писать на JavaScript асинхронные функции является важным обновлением в ES2017. Что такое асинхронные функции? Асинхронные функции — это функции, которые возвращают promise. Мы

WordPress JavaScript — как правильно подключить файл скрипта к шаблону сайта
Использование в шаблонах WordPress JavaScript скриптов давно стало обычным делом. Их подключение возможно несколькими способами, начиная с классического варианта с использование голого HTML. Но чтобы все

Как исправить JavaScript error "ВКонтакте"? Что делать при ошибках JavaScript в "ВКонтакте"?
"ВКонтакте" - это на сегодняшний день самый удобный русскоязычный ресурс, который является не только популярнейшей социальной сетью, но и сервисом для прослушивания аудиозаписей и просмотра видео. Здесь

Правильное использование Tor Browser
Tor Browser полностью анонимен – Миф или реальность? Многие считают, что Tor — это полностью анонимное и безопасное средство для интернет-серфинга, которое не дает никому возможность контролировать то,

Javascript error object is not a function вконтакте как исправить
"ВКонтакте" - это на сегодняшний день самый удобный русскоязычный ресурс, который является не только популярнейшей социальной сетью, но и сервисом для прослушивания аудиозаписей и просмотра видео. Здесь

Как исправить ошибку javascript error вконтакте
На сегодняшний день «Вконтакте» является наиболее удобным русскоязычным ресурсом, который представляет собой не только крупнейшую социальной сеть, но и сервис для просмотра видео и прослушивания аудиозаписей.

Что такое JavaScript и для чего он используется?
Подробности декабря 10, 2015 Просмотров: 20225 В интернете миллионы веб-страниц,

Практика javascript синтаксис написания
Javascript — это язык программирования, который активно используется для построения динамических веб страниц. Собственно с этой целью он и был изобретен. У нашего с вами языка еще есть такое интересное

JavaScript учебник
Код функций в JavaScript начинает выполнение после их вызова. Функции являются одним из наиболее важных строительных блоков кода в JavaScript. Функции состоят из набора команд и обычно выполняют

Рекомендации решившим начать изучать JavaScript
Если вы решили начать изучать JavaScript , то эта статья для вас. Надеюсь, что её прочтение избавит вас в будущем от множества ошибок и сделает его изучения более простым, быстрым и эффективным. В статье

Python flask фреймворк - правильная структура приложения

Опубликовано: 01.09.2018

Многие фреймворки диктуют свою структуру приложения при разработке. С одной стороны это хорошо: в больших проектах человеку знакомому с фреймворком будет легче и проще вникнуть в проект. С другой стороны - это накладывает определенные ограничения, и иногда вынуждает использовать методы и паттерны без которых вполне можно было обойтись. Во всех таких фреймворках есть определенные инструменты позволяющие разделять приложение на модули. Во flask так же есть подобный инструмент, и называются он blueprints .

Вопреки частому заблуждению, flask прекрасно подходит для больших проектов. В нескольких таких крупных проектах мне даже довелось учавствовать. Поэтому, если вас убеждают, что flask никто не использует не верьте им. Ещё как используют. Flask прекрасный, небольшой фреймворк подходящий для очень широкого круга задач.

В этой статье я расскажу про наиболее популярную, гибкую и правильную на мой взгляд структуру flask приложения. Используя подобную структуру при разработке, вы сможете построить гибкое и удобное приложение модули которого можно будет с легкостью переиспользовать в других проектах. Определенно стоит уточнить, что серебряной пули нет, и далеко не всем может подойти такая структура, но если вы не очень хорошо знакомы с flask, то вам определенно стоит как минимум взять её за основу.

Для тех, кто не знаком с Flask - это небольшой фреймворк написанный на языке python c весьма большим сообществом, и множеством модулей на все случаи жизни. В отличии от, скажем, Django, Flask не навязывает определенное решение той или иной задачи. Вместо этого, он предлагает использовать различные сторонние или собственные решения по вашему усмотрению. Я ни в коем случае не хочу сказать, что Django плохой фреймворк. Просто в некоторых случаях Django вынуждает писать много лишнего кода попутно залезая все глубже и глубже во внутренности самого фреймворка. Так что flask - это лишь ещё один инструмент, подходящий под определенные задачи.

Disclaimer: в этой статье нет ничего принципиально нового и неизвестного. Все это общеизвестные практики, только собранные в одном месте. Эта статья ориентирована в основном на тех, кто не знаком с flask и новичков.

Чаще всего я (да и большинство остальных разработчиков) использую flask совместно со следующими python-библиотеками:

Sqlalchemy (используя расширение Flask-Sqlalchemy) - для работы с базой данных; Wtforms - (используя расширение flask-wtf) для обработки html-форм; psycopg2 - прослойка для Sqlalchemy. Необходима для работы postgresql; flask-script - для удобного запуска приложения и различных скриптов;

Конечная структура будет такой:

testproject ├── app │ ├── __init__.py │ ├── database.py │ ├── firstmodule │ │ ├── controllers.py │ │ ├── forms.py │ │ ├── models.py │ ├── static │ │ ├── css │ │ ├── img │ │ └── js │ └── templates │ ├── base.html │ ├── index.html │ └── _list.html │ ├── firstmodule │ │ ├── create.html │ │ ├── delete.html │ │ ├── list.html │ │ ├── update.html │ │ └── view.html ├── config.py ├── env ├── manage.py └── requipments ├── base.txt ├── development.txt └── production.txt

Перед началом, убедимся что все нужные пакеты у нас в системе установлены. Нам нужен собственно сам python и pip:

sudo apt-get install python3-pip python3-dev

Кстати, так же не слушайте тех, кто говорит что не стоит использовать python3. Возьмите обогреватель и попробуйте разморозить их. Все нужное давно есть и нет смысла не использовать python3 для новых проектов . Разве что нет какой-то редкой, специфичной библиотекии написать которую самостоятельно не представляется возможным. До сих пор я не встретил ни одной такой библиотеки хотя писал самые разные приложения.

Основной "скелет" приложения

Сначала создадим наше виртуальное окружение. Создадим директорию, где будет располагаться наш проект. Здесь, и дальше во всей статье я буду предполагать что это директория testproject:

mkdir testproject cd testproject

Создадим виртуальное окружение c помощью virtualenv:

virtualenv -p python3 env

Создадим директорию для кода нашего будущего проекта:

И заодно директорию для шаблонов, и статических файлов:

# Директория для шаблонов mkdir app/templates # Директория для статических файлов (стили, javascript, и т.д.) mkdir -p app/static/{css,js,img}

Активируем виртуальное окружение:

Важно: здесь и далее считаем, что все команды выполняются внутри виртуального окружения!

Установим нужные нам пакеты с помощью pip:

pip install flask flask-sqlalchemy flask-wtf flask-script psycopg2

Основной "скелет" приложения готов.

Git

Этот пункт строго обязателен. Разработку любого приложения всегда следует вести с использованием системы контроля версий.

В корневой директории нашего проекта (напомню, это testproject) создадим файл .gitignore.

Он необходим для того, чтобы лишние файлы, такие как кэши не попали в git репозиторий. Добавим в него следующее:

.idea / .idea / * lib / lib / * * .py [ cod ] * .pyc bin / bin / * share / share / * local / local / * include / include / * env /

Инициализируем репозиторий:

И сделаем первый коммит:

git add -A git commit -m 'Initial commit'

Теперь необходимо указать url git репозитория. Для этого его необходимо сначала создать. Сейчас очень много различных компаний предлагают бесплатный хостинг git репозиториев. Из них можно выделить всем известные github и bitbucket . Лично я для своих проектов предпочитаю последний, т.к. он позволяет создавать скрытые репозитории бесплатно. После создания репозитория на любом из этих сервисов, будет показана подсказка, в которой будет описано как добавить url репозитория в существующий проект. Сейчас я на этом подробно останавливаться не буду.

Зависимости

Все зависимости приложения необходимо обязательно сохранять. Это необходимо для того, чтобы не забыть какие библиотеки вы использовали при разработке приложения, и это так же знали другие разработчики которые работают над этим приложением. Возможно сейчас вы думаете, что никогда в будущем не будете ничего делать с этим приложением, или вы всегда будете делать его один, но спустя несколько лет успешной работы приложения, вам может понадобиться добавить совсем чуть-чуть новых возможностей и даже может быть будете делать его уже не в одиночку. Поверьте, уже спустя месяц вы не будете помнить какие именно библиотеки вы устанавливали и использовали, и будете неимоверно рады что сохранили все зависимости.

Зависимости мы будем хранить в нескольких различных файлах: по одному для каждого используемого окружения, и один, в котором содержаться общие для всех окружений. Под окружением подразумевается среда, в которой это приложение запущено. В рамках этой статьи мы будем использовать два:

development - зависимости, необходимые для разработки; production - зависимости, необходимые только в production окружении;

При желании вы легко сможете добавить сколько угодно необходимых. Сами же файлы с версиями будем хранить в отдельной директории - requipments. Создадим её:

Теперь создадим базовый файл, в котором будут содержаться зависимости общие для всех окружений:

pip freeze > requipments/base.txt

Этой командой мы добавим в файл base.txt список всех установленных в данный момент библиотек с их зависимостями. В данный момент мы уже установили в наше виртуальное окружение собственно сам flask, Flask-Sqlalchemy, flask-wtf и psycopg2. Содержимое файла будет таким:

click==6.6 Flask==0.11 Flask-SQLAlchemy==2.1 Flask-Script==2.0.5 itsdangerous==0.24 Jinja2==2.8 MarkupSafe==0.23 psycopg2==2.6.1 SQLAlchemy==1.0.13 Werkzeug==0.11.10 WTForms==2.1

Для того, чтобы переиспользовать содержимое base.txt в файлах других окружений, в начало каждого из них необходимо добавить:

А затем остальные зависимости построчно.

Например, во время разработки в development окружении, возможно вы захотите использовать flask-debugtoolbar. В этом случае файл requipments/development.txt может быть таким:

-r base.txt flask-debugtoolbar

В production окружении будет хорошей идеей использовать gunicorn для запуска самого приложения, и содержимое файла requipments/production.txt может быть таким:

Так же не лишнем будет указать версии используемых библиотек аналогично тому, как они указаны в файле requipments/base.txt.

Файл настроек

Перед тем как мы неподсредственно начнем писать модули приложения, необходимо создать файл с конфигурацией и скрипт для запуска приложения.

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

import os class Config ( object ): # Определяет, включен ли режим отладки # В случае если включен, flask будет показывать # подробную отладочную информацию. Если выключен - # - 500 ошибку без какой либо дополнительной информации. DEBUG = False # Включение защиты против "Cross-site Request Forgery (CSRF)" CSRF_ENABLED = True # Случайный ключ, которые будет исползоваться для подписи # данных, например cookies. SECRET_KEY = 'YOUR_RANDOM_SECRET_KEY' # URI используемая для подключения к базе данных SQLALCHEMY_DATABASE_URI = os . environ [ 'DATABASE_URL' ] SQLALCHEMY_TRACK_MODIFICATIONS = False class ProductionConfig ( Config ): DEBUG = False class DevelopmentConfig ( Config ): DEVELOPMENT = True DEBUG = True

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

Файл запуска приложения

Для запуска приложения мы будем использовать удобное расширение - flask-scripts. Весь необходимый код поместим в файл manage.py в корень проекта. Название не случайно. Кроме того, что оно "говорящее" ещё и довольно привычное многим python-разработчикам.

1 2 3 4 5 6 7 8 9 10 11 12 #!/usr/bin/env python import os from flask_script import Manager from app import create_app app = create_app () app . config . from_object ( os . environ [ 'APP_SETTINGS' ]) manager = Manager ( app ) if __name__ == '__main__' : manager . run ()

Само flask приложение мы будем создавать используя "фабрику" - функцию, создающую экземпляр приложения и возвращающую его. Её мы опишем дальше в этой статье.

База данных

Как выше уже говорилось, для работы с базой данных используем sqlalchemy через дополнение Flask-sqlalchemy. Это, пожалуй, самая часто используемая связка. Переменную, в которой будет хранится сессия для работы с базой данных мы будем хранить в отдельном файле - app/database.py. В этом файле почти всегда будет несколько строк, но помещать эти строки в тот же файл, где мы будем инициализировать flask приложение не рекомендуется. Мы же не хотим получать странные сообщения об ошибках во время запуска потому, что случайно сделали круговой импорт модулей (circular import)? Инициализацию же расширения flask-sqlaclhemy мы будем проводить там же, где и инициализацию flask приложения.

Содержимое файла app/database.py:

from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy ()

Инициализация приложения

Как я уже выше писал, инициализация приложения будет происходить в функции-фабрике. Под инициализацией подразумевается создание экземпляра flask приложения со всеми необходимыми настройками. Использовать фабрику строго обязательно. Без этого, в какой-то момент "по запарке" можем выстрелить себе в ногу все теми же "круговыми импортами". Да и тесты иначе не получится нормально писать, а без тестов вообще нельзя. Совсем. Никогда. :)

Наша фабрика будет находится в файле app/__init__.py. Вот его содержимое:

import os from flask import Flask from .database import db def create_app (): app = Flask ( __name__ ) app . config . from_object ( os . environ [ 'APP_SETTINGS' ]) db . init_app ( app ) with app . test_request_context (): db . create_all () import app.firstmodule.controllers as firstmodule app . register_blueprint ( firstmodule . module ) return app

Обратите внимание, что инициализация расширения flask-sqlaclhemy происходит внутри тела функции, а не просто в файле. Тоже самое касается и импортов blueprint модулей.

Модули (blueprints)

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

Создадим директорию для шаблонов модуля:

mkdir -p app/templates/firstmodule

И директорию для самого модуля:

Да, шаблоны используемые модулем лучше хранить в отдельной под-директории для лучшей его (модуля) переносимости.

Вот так выглядит структура модуля:

╰─➤ tree app/firstmodule/ . ├── controllers.py ├── forms.py ├── models.py controllers.py - предназначен для контроллеров; models.py - предназначен для sqlalchemy моделей; forms.py - предназначен для форм;

Теперь более подробно примерное содержимое каждого из файлов.

app/firstmodule/models.py:

from sqlalchemy import event from app.database import db class Entity ( db . Model ): __tablename__ = 'entity' id = db . Column ( db . Integer , primary_key = True ) name = db . Column ( db . String ( 1000 ), nullable = False , unique = True ) slug = db . Column ( db . String ( 1000 )) content = db . Column ( db . String ( 5000 )) comments = db . relationship ( 'Comment' , backref = 'entity' ) def __str__ ( self ): return self . name @event.listens_for ( Entity , 'after_delete' ) def event_after_delete ( mapper , connection , target ): # Здесь будет очень важная бизнес логика # Или нет. На самом деле, старайтесь использовать сигналы только # тогда, когда других, более правильных вариантов не осталось. pass

app/firstmodule/controllers.py:

# ВНИМАНИЕ: код для примера! Не нужно его бездумно копировать! from flask import ( Blueprint , render_template , request , flash , abort , redirect , url_for , current_app , ) from sqlalchemy.exc import SQLAlchemyError from .models import Entity , db from .forms import EntityCreateForm from app.comment.models import Comment module = Blueprint ( 'entity' , __name__ , url_prefix = '/entity' ) def log_error ( * args , ** kwargs ): current_app . logger . error ( * args , ** kwargs ) @module.route ( '/' , methods = [ 'GET' ]) def index (): entities = None try : entities = Entity . query . join ( Comment ) . order_by ( Entity . id ) . all () db . session . commit () except SQLAlchemyError as e : log_error ( 'Error while querying database' , exc_info = e ) flash ( 'Во время запроса произошла непредвиденная ошибка.' , 'danger' ) abort ( 500 ) return render_template ( 'entity/index.html' , object_list = entities ) @module.route ( '/<int:id>/view/' , methods = [ 'GET' ]) def view ( id ): entity = None try : entity = Entity . query . outerjoin ( Comment ) . first_or_404 ( id ) db . session . commit () if entity is None : flash ( 'Нет entity с таким идентификатором' , 'danger' ) abort ( 404 ) except SQLAlchemyError as e : log_error ( 'Error while querying database' , exc_info = e ) flash ( 'Во время запроса произошла непредвиденная ошибка' , 'danger' ) abort ( 500 ) return render_template ( 'entity/view.html' , object = image ) @module.route ( '/create/' , methods = [ 'GET' , 'POST' ]) def create (): form = EntityCreateForm ( request . form ) try : if request . method == 'POST' and form . validate (): entity = Entity ( ** form . data ) db_session . add ( entity ) db_session . flush () id = entity . id db_session . commit () flash ( 'Запись была успешно добавлена!' , 'success' ) return redirect ( url_for ( 'entity.view' , id = id )) except SQLAlchemyError as e : log_error ( 'There was error while querying database' , exc_info = e ) db . session . rollback () flash ( 'Произошла непредвиденная ошибка во время запроса к базе данных' , 'danger' ) return render_template ( 'entity/create.html' , form = form ) # Наивное удаление. Чаще всего, будет сложная логика с правильной обработкой # зависимых объектов. @module.route ( '/<int:id>/remove/' , methods = [ 'GET' , 'POST' ]) def remove ( id ): entity = None try : entity = Entity . query . get ( id ) if entity is None : flash ( 'Нет записи с таким идентификатором' , 'danger' ) except SQLAlchemyError as e : log_error ( 'Error while querying database' , exc_info = e ) flash ( 'Произошла непредвиденная ошибка во время запроса к базе данных' , 'danger' ) finally : db_session . commit () flash ( 'Запись была успешна удалена!' , 'success' ) return redirect ( url_for ( 'entity.index' ))

app/firstmodule/forms.py:

from flask.ext.wtf import Form from wtforms import ( StringField , TextAreaField , ) from wtforms.validators import DataRequired class EntityCreateForm ( Form ): name = StringField ( 'Название' , [ DataRequired ( message = "Поле обязательно для заполнения" ) ], description = "Название" ) content = TextAreaField ( 'Содержимое' , [], description = "Содержимое записи" , )

Шаблоны

Как я уже выше писал, шаблоны приложения будут находится в app/templates/. В этой директории надо расположить базовый шаблон, шаблоны страниц с ошибками и какие либо служебные части. Шаблоны отдельных модулей надо помещать в директорию с именем соответствующим имени модуля.

Вот пример:

. ├── 403.html ├── 404.html ├── 500.html ├── base.html ├── entity │ ├── create.html │ ├── delete.html │ ├── list.html │ ├── update.html │ └── view.html ├── form-errors.html ├── form-macros.html ├── index.html ├── messages.html ├── pagination.html

Примеры самих шаблонов я приводить не буду, они как правило слишком объёмные.

Определяем переменные окружения

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

В терминале пишем:

export APP_SETTINGS="config.DevelopmentConfig" export DATABASE_URL='postgresql://USERNAME:[email protected]/DBNAME'

Это хорошая практика не хранить нигде логин/пароль в файлах конфигурации. В случае взлома приложения, взломщику будет намного тяжелее получить пароль базы данных. На production сервере в переменной окружения APP_SETTINGS конечно же будет необходимо указать config.ProductionConfig.

Запуск приложения

В примерах кода выше было очень много допущений, поэтому если вы просто копировали их, то ничего не заработает. :) Сделано это специально, т.к. в статье я рассказывал не о том как писать приложения на flask, а как правильно организовывать их структуру. Полностью рабочий пример можете найти в репозитории на github .

Запуск приложения будет выгядеть следующим образом:

# Делаем файл manage.py исполняемым chmod +x manage.py ╰─➤ ./manage.py runserver * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger pin code: 147-804-358

Главное не забудьте про переменные окружения , иначе ничего тем более не заработает. :)

Полезные ссылки:

rss