Это пятнадцатая часть серии мега-учебника Flask, в которой я собираюсь реструктурировать приложение, используя стиль, подходящий для более крупных приложений.
Оглавление
Глава 15: Улучшенная структура приложения (Эта статья)
Глава 16: Полнотекстовый поиск
Глава 17: Развертывание в Linux
Глава 18: Развертывание на Heroku
Глава 19: Развертывание в контейнерах Docker
Глава 20: Немного магии JavaScript
Глава 21: Уведомления пользователей
Глава 22: Фоновые задания
Глава 23: Интерфейсы прикладного программирования (API)
Микроблог уже является приложением приличного размера, поэтому я подумал, что это хорошая возможность обсудить, как приложение Flask может расширяться, не становясь беспорядочным или слишком сложным в управлении. Flask - это фреймворк, разработанный для того, чтобы дать вам возможность организовать свой проект любым удобным для вас способом, и, как часть этой философии, он позволяет изменять или адаптировать структуру приложения по мере его увеличения или по мере изменения ваших потребностей или уровня опыта.
В этой главе я собираюсь обсудить некоторые шаблоны, применимые к большим приложениям, и, чтобы продемонстрировать их, я собираюсь внести некоторые изменения в структуру моего проекта Microblog, с целью сделать код более удобным в обслуживании и лучше организованным. Но, конечно, в истинном духе Flask я призываю вас воспринимать эти изменения просто как рекомендацию при попытке выбрать способ организации ваших собственных проектов.
Ссылки на GitHub для этой главы: Browse, Zip, Diff.
Текущие ограничения
Есть две основные проблемы с приложением в его текущем состоянии. Если вы посмотрите на то, как структурировано приложение, вы заметите, что существует несколько различных подсистем, которые можно идентифицировать, но код, который их поддерживает, полностью перемешан, без каких-либо четких границ. Давайте рассмотрим, что это за подсистемы:
Подсистема аутентификации пользователя, которая включает в себя некоторые функции просмотра в app/routes.py, некоторые формы в app/forms.py, некоторые шаблоны в app/templates и поддержку по электронной почте в app/email.py.
Подсистема ошибок, которая определяет обработчики ошибок в app/errors.py и шаблоны в app/templates.
Основная функциональность приложения, которая включает в себя отображение и написание сообщений в блоге, профили пользователей и подписчиков, а также перевод сообщений в блоге, которые распределены по большинству модулей и шаблонов приложения.
Размышляя об этих трех подсистемах, которые я определил, и о том, как они структурированы, вы, вероятно, можете заметить закономерность. Пока что логика организации, которой я следовал, основана на наличии модулей, предназначенных для различных функций приложения. Есть модуль для функций просмотра, еще один для веб-форм, один для ошибок, один для электронных писем, каталог для HTML-шаблонов и так далее. Хотя эта структура имеет смысл для небольших проектов, как только проект начинает расти, некоторые из этих модулей становятся действительно большими и беспорядочными.
Один из способов ясно увидеть проблему - это подумать, как бы вы начали второй проект, повторно используя как можно больше из этого. Например, часть аутентификации пользователя должна хорошо работать в других приложениях, но если вы хотите использовать этот код как есть, вам придется перейти к нескольким модулям и скопировать / вставить соответствующие разделы в новые файлы в новом проекте. Видите, насколько это неудобно? Не было бы лучше, если бы в этом проекте все файлы, связанные с аутентификацией, были отделены от остальной части приложения? Функция blueprints в Flask помогает добиться более практичной организации, что упрощает повторное использование кода.
Есть вторая проблема, которая не столь очевидна. Экземпляр приложения Flask создается как глобальная переменная в app/__init__.py, а затем импортируется множеством прикладных модулей. Хотя само по себе это не проблема, использование приложения в качестве глобальной переменной может усложнить определенные сценарии, в частности те, которые связаны с тестированием. Представьте, что вы хотите протестировать это приложение в разных конфигурациях. Поскольку приложение определено как глобальная переменная, на самом деле нет способа создать экземпляры двух приложений, использующих разные переменные конфигурации. Другая ситуация, которая не идеальна, заключается в том, что все тесты используют одно и то же приложение, поэтому тест может вносить изменения в приложение, которые влияют на другой тест, который запускается позже. В идеале я хочу, чтобы все тесты выполнялись на нетронутом экземпляре приложения.
Вы действительно можете увидеть в модуле tests.py, в котором я прибегаю к хитрости перезаписи DATABASE_URL
переменной окружения перед импортом приложения, чтобы тесты использовали базу данных в памяти вместо базы данных SQLite по умолчанию на диске.
Лучшим решением было бы не использовать глобальную переменную для приложения, а вместо этого использовать функцию фабрики приложений для создания приложения во время выполнения. Это будет функция, которая принимает объект конфигурации в качестве аргумента и возвращает экземпляр приложения Flask, настроенный с этими настройками. Если бы я мог модифицировать приложение для работы с функцией фабрики приложений, то написание тестов, требующих специальной настройки, стало бы простым делом, потому что каждый тест может создавать свое собственное приложение.
В этой главе я собираюсь провести рефакторинг приложения, чтобы представить blueprint для трех подсистем, которые я определил выше, и функцию фабрики приложений. Показывать вам подробный список изменений будет непрактично, потому что практически в каждом файле, входящем в состав приложения, есть небольшие изменения, поэтому я собираюсь обсудить шаги, которые я предпринял для проведения рефакторинга, и затем вы сможете загрузить приложение с внесенными изменениями.
Blueprints
В Flask blueprint - это логическая структура, представляющая подмножество приложения. Blueprint может включать такие элементы, как маршруты, функции просмотра, формы, шаблоны и статические файлы. Если вы пишете свой blueprint в отдельном пакете Python, то у вас есть компонент, который инкапсулирует элементы, относящиеся к конкретной функции приложения.
Содержащиеся в blueprint элементы изначально находится в неактивном состоянии. Чтобы активировать эти элементы, blueprint необходимо зарегистрировать в приложении. Во время регистрации все элементы, которые были добавлены в blueprint, передаются приложению. Таким образом, вы можете рассматривать blueprint как временное хранилище функциональности приложения, которое помогает упорядочить ваш код.
Blueprint обработки ошибок
Первым созданным мной проектом будет тот, который инкапсулирует поддержку обработчиков ошибок. Структура этого проекта следующая:
app/
errors/ <-- blueprint package
__init__.py <-- blueprint creation
handlers.py <-- error handlers
templates/
errors/ <-- error templates
404.html
500.html
__init__.py <-- blueprint registration
По сути, что я сделал, так это переместил модуль app/errors.py в app/errors/handlers.py и два шаблона ошибок в app/templates/errors, чтобы они были отделены от других шаблонов. Мне также пришлось изменить вызовы render_template()
в обоих обработчиках ошибок, чтобы использовать новый подкаталог шаблона errors. После этого я добавил создание blueprint в модуль app/errors/__init__.py, а регистрацию blueprint - в app/__init__.py, после создания экземпляра приложения.
Я должен отметить, что blueprint Flask можно настроить так, чтобы в них был отдельный каталог для шаблонов или статических файлов. Я решил переместить шаблоны в подкаталог каталога шаблонов приложения, чтобы все шаблоны находились в единой иерархии, но если вы предпочитаете, чтобы шаблоны, принадлежащие blueprint, находились внутри пакета blueprint, это поддерживается. Например, если вы добавите аргумент template_folder='templates'
в конструктор Blueprint()
, вы сможете сохранить шаблоны blueprint в app/errors/templates.
Создание blueprint довольно похоже на создание приложения. Это делается в модуле ___init__.py пакета blueprint:
app/errors/__init__.py: Blueprint ошибок.
from flask import Blueprint
bp = Blueprint('errors', __name__)
from app.errors import handlers
Класс Blueprint
принимает имя blueprint, имя базового модуля (обычно устанавливается равным __name__
, как в экземпляре приложения Flask) и несколько необязательных аргументов, которые в данном случае мне не нужны. После создания объекта blueprint я импортирую модуль handlers.py, чтобы обработчики ошибок в нем были зарегистрированы в blueprint. Этот импорт находится внизу, чтобы избежать циклических зависимостей.
В модуле handlers.py вместо того, чтобы прикреплять обработчики ошибок к приложению с помощью декоратора @app.errorhandler
, я использую декоратор проекта @bp.app_errorhandler
. Хотя оба декоратора достигают одинакового конечного результата, идея состоит в том, чтобы попытаться сделать blueprint независимым от приложения, чтобы сделать его более переносимым. Мне также нужно изменить путь к двум шаблонам ошибок, чтобы учесть новый подкаталог errors, куда они были перемещены.
Последним шагом для завершения рефакторинга обработчиков ошибок является регистрация blueprint в приложении:
app/__init__.py: Регистрация blueprint в приложении.
app = Flask(__name__)
# ...
from app.errors import bp as errors_bp
app.register_blueprint(errors_bp)
# ...
from app import routes, models # <-- remove errors from this import!
Для регистрации blueprint используется метод register_blueprint()
экземпляра приложения Flask. Когда blueprint зарегистрирован, все функции просмотра, шаблоны, статические файлы, обработчики ошибок и т.д. подключаются к приложению. Я поместил импорт blueprint прямо над app.register_blueprint()
, чтобы избежать циклических зависимостей.
Blueprint аутентификации
Процесс преобразования функций аутентификации приложения в blueprint довольно похож на процесс обработки ошибок. Вот схема переработанного blueprint.:
app/
auth/ <-- blueprint package
__init__.py <-- blueprint creation
email.py <-- authentication emails
forms.py <-- authentication forms
routes.py <-- authentication routes
templates/
auth/ <-- blueprint templates
login.html
register.html
reset_password_request.html
reset_password.html
__init__.py <-- blueprint registration
Чтобы создать этот blueprint, мне пришлось перенести все функции, связанные с аутентификацией, в новые модули, которые я создал в blueprint. Это включает в себя несколько функций просмотра, веб-форм и функций поддержки, таких как та, которая отправляет токены для сброса пароля по электронной почте. Я также переместил шаблоны в подкаталог, чтобы отделить их от остальной части приложения, как я сделал со страницами ошибок.
При определении маршрутов в blueprint вместо декоратора @app.route
используется декоратор @bp.route
. Также требуется изменить синтаксис, используемый в url_for()
для построения URL-адресов. Для обычных функций просмотра, подключенных непосредственно к приложению, первым аргументом url_for()
является имя функции просмотра. Когда маршрут определен в схеме элементов, этот аргумент должен включать имя схемы элементов и имя функции просмотра, разделенные точкой. Так, например, мне пришлось заменить все вхождения url_for('login')
на url_for('auth.login')
, и то же самое для остальных функций просмотра.
Чтобы зарегистрировать blueprint auth
в приложении, я использовал немного другой формат:
app/__init__.py: Регистрация аутентификации blueprint в приложении.
# ...
from app.auth import bp as auth_bp
app.register_blueprint(auth_bp, url_prefix='/auth')
# ...
В этом случае у вызова register_blueprint()
есть дополнительный аргумент url_prefix
. Это совершенно необязательно, но Flask дает вам возможность прикрепить blueprint с префиксом URL, поэтому любые маршруты, определенные в blueprint, получают этот префикс в своих URL. Во многих случаях это полезно как своего рода "пространство имен", которое отделяет все маршруты в blueprint от других маршрутов в приложении или других blueprint. Для аутентификации я подумал, что было бы неплохо, чтобы все маршруты начинались с /auth, поэтому я добавил префикс. Итак, теперь URL для входа будет http://localhost:5000/auth/login. Все URL-адреса будут автоматически включать префикс, потому что я использую url_for()
для генерации URL-адресов.
Основной blueprint приложения
Третья схема содержит основную логику приложения. Для рефакторинга этого blueprint требуется тот же процесс, который я использовал с двумя предыдущими схемами. Я дал этому blueprint название main
, поэтому все url_for()
вызовы, ссылающиеся на функции просмотра, должны были получать префикс main.
. Учитывая, что это основная функциональность приложения, я решил оставить шаблоны в тех же местах. Это не проблема, потому что я переместил шаблоны из двух других blueprint в подкаталоги.
Шаблон фабрики приложений
Как я упоминал во введении к этой главе, использование приложения в качестве глобальной переменной создает некоторые сложности, в основном в виде ограничений для некоторых сценариев тестирования. До того, как я представил blueprints, приложение должно было быть глобальной переменной, потому что все функции просмотра и обработчики ошибок должны были быть оформлены декораторами, взятыми из app
, такими как @app.route
. Но теперь, когда все маршруты и обработчики ошибок перенесены в blueprints, причин сохранять приложение глобальным стало намного меньше.
Итак, что я собираюсь сделать, это добавить функцию с именем create_app()
, которая создает экземпляр приложения Flask, и исключить глобальную переменную. Преобразование не было тривиальным, мне пришлось разобраться с несколькими сложностями, но давайте сначала посмотрим на функцию фабрики приложений:
app/__init__.py: Функция фабрики приложений.
# ...
db = SQLAlchemy()
migrate = Migrate()
login = LoginManager()
login.login_view = 'auth.login'
login.login_message = _l('Please log in to access this page.')
mail = Mail()
moment = Moment()
babel = Babel()
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
db.init_app(app)
migrate.init_app(app, db)
login.init_app(app)
mail.init_app(app)
moment.init_app(app)
babel.init_app(app)
# ... no changes to blueprint registration
if not app.debug and not app.testing:
# ... no changes to logging setup
return app
Вы видели, что большинство расширений Flask инициализируются путем создания экземпляра расширения и передачи приложения в качестве аргумента. Когда приложение не существует как глобальная переменная, существует альтернативный режим, в котором расширения инициализируются в два этапа. Экземпляр расширения сначала создается в глобальной области видимости, как и раньше, но ему не передаются аргументы. При этом создается экземпляр расширения, который не привязан к приложению. Во время создания экземпляра приложения в функции фабрики приложений метод init_app()
должен быть вызван в экземплярах расширения, чтобы привязать его к уже известному на тот момент приложению.
Другие задачи, выполняемые во время инициализации, остаются прежними, но перемещаются в функцию фабрики приложений вместо того, чтобы находиться в глобальной области видимости. Это включает регистрацию blueprint и конфигурацию ведения журнала. Обратите внимание, что я добавил конструкцию not app.testing
к условию, которое решает, следует ли включать ведение журнала электронной почты и файлов или нет, так что все это ведение журнала пропускается во время модульных тестов. При запуске модульных тестов будет установлен флаг app.testing
со значением True
из-за того, что для переменной TESTING
установлено значение True
в конфигурации.
Итак, кто вызывает функцию фабрики приложений? Очевидным местом для использования этой функции является скрипт верхнего уровня microblog.py, который является единственным модулем, в котором приложение теперь существует в глобальной области видимости. Другое место находится в tests.py, и я более подробно рассмотрю модульное тестирование в следующем разделе.
Как я упоминал выше, большинство ссылок на app
исчезли с появлением blueprints, но в коде все еще оставались некоторые, к которым мне пришлось обратиться. Например, во всех модулях app/models.py, app/translate.py и app/main/routes.py были ссылки на app.config
. К счастью, разработчики Flask постарались упростить для функций просмотра доступ к экземпляру приложения без необходимости импортировать его, как я делал до сих пор. Переменная current_app
, которую предоставляет Flask, представляет собой специальную "контекстную" переменную, которую Flask инициализирует в приложении перед отправкой запроса. Вы уже видели раньше другую контекстную переменную, переменную g
, в которой я сохраняю текущую локаль. Эти две, наряду с current_user
Flask-Login и несколькими другими, которые вы еще не видели, являются в некотором роде "волшебными" переменными, поскольку они работают как глобальные переменные, но доступны только во время обработки запроса и только в потоке, который его обрабатывает.
Замена переменной app
Flask на current_app
устраняет необходимость импорта экземпляра приложения в качестве глобальной переменной. Я смог изменить все ссылки с app.config
на current_app.config
без каких-либо затруднений с помощью простого поиска и замены.
Модуль app/email.py представлял собой немного более сложную задачу, поэтому мне пришлось прибегнуть к небольшой хитрости:
app/email.py: Передача экземпляра приложения другому потоку.
from flask import current_app
def send_async_email(app, msg):
with app.app_context():
mail.send(msg)
def send_email(subject, sender, recipients, text_body, html_body):
msg = Message(subject, sender=sender, recipients=recipients)
msg.body = text_body
msg.html = html_body
Thread(target=send_async_email,
args=(current_app._get_current_object(), msg)).start()
В функции send_email()
экземпляр приложения передается в качестве аргумента фоновому потоку, который затем доставит электронное письмо, не блокируя основное приложение. Использование current_app
непосредственно в функции send_async_email()
, которая выполняется как фоновый поток, не сработало бы, потому что current_app
это контекстно-зависимая переменная, привязанная к потоку, обрабатывающему запрос клиента. В другом потоке current_app
не было бы присвоено значение. Передача current_app
непосредственно в качестве аргумента объекту потока также не сработала бы, потому что current_app
на самом деле это прокси-объект, который динамически сопоставляется экземпляру приложения. Таким образом, передача прокси-объекта будет такой же, как использование current_app
непосредственно в потоке. Что мне нужно было сделать, так это получить доступ к реальному экземпляру приложения, который хранится внутри прокси-объекта, и передать его в качестве аргумента app
. Выражение current_app._get_current_object()
извлекает фактический экземпляр приложения изнутри прокси-объекта, так что это то, что я передал потоку в качестве аргумента.
Другим сложным модулем был app/cli.py, который реализует несколько быстрых команд для управления языковыми переводами и использует декоратор @app.cli.group()
. Замена app
на current_app
в данном случае не работает, потому что эти команды регистрируются при запуске, а не во время обработки запроса, который является единственным временем, когда current_app
можно использовать. Чтобы удалить ссылку на app
в этом модуле, я создал другую схему:
app/cli.py: Схема пользовательских команд приложения.
import os
from flask import Blueprint
import click
bp = Blueprint('cli', __name__, cli_group=None)
@bp.cli.group()
def translate():
"""Translation and localization commands."""
pass
@translate.command()
@click.argument('lang')
def init(lang):
"""Initialize a new language."""
# ...
@translate.command()
def update():
"""Update all languages."""
# ...
@translate.command()
def compile():
"""Compile all languages."""
# ...
Flask помещает команды, прикрепленные к схемам, в группу с именем схемы по умолчанию. Это привело бы к тому, что эти команды были бы доступны как flask cli translate ...
. Чтобы избежать дополнительной группы cli
, cli_group=None
передана в blueprint.
Затем я регистрирую этот blueprint cli
в функции фабрики приложений:
app/__init__.py: Функция фабрики приложений.
# ...
def create_app(config_class=Config):
# ...
from app.cli import bp as cli_bp
app.register_blueprint(cli_bp)
# ...
return app
Улучшения в модульном тестировании
Как я намекал в начале этой главы, большая часть работы, которую я проделал до сих пор, была направлена на улучшение рабочего процесса модульного тестирования. Когда вы запускаете модульные тесты, вы хотите убедиться, что приложение настроено таким образом, чтобы оно не мешало вашим ресурсам разработки, таким как ваша база данных.
Текущая версия tests.py прибегает к хитрости изменения конфигурации после того, как она была применена к экземпляру приложения, что является опасной практикой, поскольку не все типы изменений будут работать, когда они будут сделаны так поздно. Что я хочу, так это иметь возможность указать мою конфигурацию тестирования до того, как она будет добавлена в приложение.
Функция create_app()
теперь принимает класс конфигурации в качестве аргумента. По умолчанию используется класс Config
, определенный в config.py, но теперь я могу создать экземпляр приложения, который использует другую конфигурацию, просто передав новый класс функции. Вот пример класса конфигурации, который можно было бы использовать для моих модульных тестов:
tests.py: Конфигурация для тестирования.
from config import Config
class TestConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite://'
Что я здесь делаю, так это создаю подкласс класса приложения Config
и переопределяю конфигурацию SQLAlchemy для использования базы данных SQLite в памяти. Я также добавил атрибут TESTING
, установленный в True
, который полезен, когда приложению нужно определить, выполняется ли оно в рамках модульных тестов или нет.
Если вы помните, мои модульные тесты основывались на методах setUp()
и tearDown()
, автоматически вызываемых фреймворком модульного тестирования для создания и уничтожения среды, подходящей для выполнения каждого теста. Теперь я могу использовать эти два метода также для создания и уничтожения совершенно нового приложения для каждого теста:
tests.py: Создание приложения для каждого теста.
class UserModelCase(unittest.TestCase):
def setUp(self):
self.app = create_app(TestConfig)
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
Новое приложение будет сохранено в self.app
, и, как и раньше, из него будет создан контекст приложения, но что это именно?
Помните переменную current_app
, которая каким-то образом действует как прокси для приложения, когда нет глобального приложения для импорта? Эта переменная знает экземпляр приложения, потому что она ищет контекст приложения, который был передан в текущем потоке. Если она находит такое приложение, то она получает из него экземпляр приложения. Если контекста нет, то нет способа узнать, какое приложение активно, поэтому current_app
вызывает исключение. Ниже вы можете увидеть, как это работает в консоли Python. Это должна быть консоль, запускаемая командой python
, потому что команда flask shell
автоматически активирует контекст приложения для удобства.
>>> from flask import current_app
>>> current_app.config['SQLALCHEMY_DATABASE_URI']
Traceback (most recent call last):
...
RuntimeError: Working outside of application context.
>>> from app import create_app
>>> app = create_app()
>>> app.app_context().push()
>>> current_app.config['SQLALCHEMY_DATABASE_URI']
'sqlite:////home/miguel/microblog/app.db'
Итак, вот в чем секрет! Перед вызовом ваших функций просмотра Flask запускает контекст приложения, который вызывает к жизни current_app
и g
. Когда запрос завершен, контекст удаляется вместе с этими переменными. Для того, чтобы вызов db.create_all()
работал в методе setUp()
, необходимо передать контекст приложения для экземпляра приложения, и таким образом db.create_all()
может использовать current_app.config
, чтобы узнать, где находится база данных. Затем в методе tearDown()
я закрываю контекст, чтобы привести все в чистое состояние.
Вы также должны знать, что контекст приложения - это один из двух контекстов, используемых Flask. Существует также контекст запроса, который более специфичен, поскольку применяется к запросу. Когда контекст запроса активируется непосредственно перед обработкой запроса, становятся доступными переменные Flask request
и session
, а также current_user
Flask-Login.
Переменные среды
Как вы видели при создании этого приложения, существует ряд параметров конфигурации, которые зависят от настройки переменных в вашей среде перед запуском сервера. Сюда входят ваш секретный ключ, информация о сервере электронной почты, URL-адрес базы данных и ключ API Microsoft Translator. Вы, вероятно, согласитесь со мной, что это неудобно, потому что каждый раз, когда вы открываете новый сеанс терминала, эти переменные нужно устанавливать заново.
Обычным шаблоном для приложений, зависящих от множества переменных окружения, является сохранение их в файле .env в корневом каталоге приложения. Приложение импортирует переменные в этот файл при запуске, и таким образом, вам не нужно устанавливать все эти переменные вручную.
Существует пакет Python, который поддерживает .env файлы с именами python-dotenv
, и он уже установлен, потому что я использовал его с файлом .flaskenv. Хотя файлы .env и .flaskenv похожи, Flask ожидает, что собственные переменные конфигурации Flask будут в .flaskenv, в то время как переменные конфигурации приложения (включая некоторые, которые могут иметь конфиденциальный характер) будут в .env. Файл .flaskenv можно добавить в систему управления версиями, поскольку он не содержит никаких секретов или паролей. Файл .env не предполагается добавлять в систему управления версиями, чтобы гарантировать защиту ваших секретов.
Команда flask
автоматически импортирует в среду любые переменные, определенные в .flaskenv и .env файлах. Этого достаточно для файла .flaskenv, потому что его содержимое необходимо только при запуске приложения с помощью команды flask
. Однако, файл .env будет использоваться также при производственном развертывании этого приложения, в котором не будет использоваться команда flask
. По этой причине рекомендуется явно импортировать содержимое файла .env.
Поскольку в модуле config.py я считываю все переменные среды, я собираюсь импортировать файл .env перед созданием класса Config
, чтобы переменные были уже установлены при создании класса:
config.py: Импорт файла .env с переменными среды.
import os
from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '.env'))
class Config:
# ...
Итак, теперь вы можете создать файл .env со всеми переменными окружения, которые необходимы вашему приложению. Важно, чтобы вы не добавляли свой файл .env в систему управления версиями. Вы же не хотите, чтобы файл, содержащий пароли и другую конфиденциальную информацию, был включен в ваше хранилище исходного кода.
Файл .env можно использовать для настройки всех переменных конфигурации приложения, но его нельзя использовать для переменных среды Flask FLASK_APP
и FLASK_DEBUG
, потому что они необходимы на самом раннем этапе процесса начальной загрузки приложения, до того, как будут созданы экземпляр приложения и его объект конфигурации.
В следующем примере показан файл .env, который определяет секретный ключ, настраивает отправку электронной почты на локальный почтовый сервер через порт 8025 без аутентификации, настраивает ключ API Microsoft Translator и пропускает конфигурацию базы данных для использования значений по умолчанию:
SECRET_KEY=a-really-long-and-unique-key-that-nobody-knows
MAIL_SERVER=localhost
MAIL_PORT=8025
MS_TRANSLATOR_KEY=<your-translator-key-here>
Файл Requirements
На данный момент я установил достаточное количество пакетов в виртуальной среде Python. Если вам когда-нибудь понадобится повторно создать свою среду на другом компьютере, вам будет сложно вспомнить, какие пакеты вам приходилось устанавливать, поэтому общепринятой практикой является создание файла requirements.txt в корневой папке вашего проекта со списком всех зависимостей вместе с их версиями. Составить этот список на самом деле несложно:
(venv) $ pip freeze > requirements.txt
Команда pip freeze
создаст дамп всех пакетов, установленных в вашей виртуальной среде, в правильном формате для файла requirements.txt. Теперь, если вам нужно создать такую же виртуальную среду на другом компьютере, вместо установки пакетов по одному, вы можете запустить:
(venv) $ pip install -r requirements.txt