Запустите код после запуска колбы

Моя цель - запустить произвольный код после запуска приложения Flask. Вот что у меня есть:

def run():
    from webapp import app
    app.run(debug=True, use_reloader=False)

В идеале я мог бы просто сделать это:

def run():
    from webapp import app
    app.run(debug=True, use_reloader=False)
    some_code()

Но код не продолжается мимо app.run(), так что some_code() никогда не запускается.

Решение, над которым я сейчас работаю, состоит в том, чтобы запустить some_code() в отдельном потоке от app.run(), создать функцию запроса перед первым запросом, которая устанавливает это:

app.is_running = True

Затем заставьте some_code() выполнить базовый запрос к приложению, чтобы выполнялся код "перед первым запросом". Это довольно запутанно и будет трудно документировать. Я бы предпочел использовать параметр app.is_running, который уже представлен в Flask, или использовать @app.after_server_start декоратор, но, насколько мне известно, ни один из них не существует.

Помогите мне сделать этот код лучше?


Посмертно: каждый раз, когда я думаю об этой проблеме, мне хочется, чтобы @app.after_server_start декоратор существовал.

7 ответов

Если вам нужно выполнить некоторый код после запуска вашего приложения-колбы, но строго перед первым запросом, даже не запускаться при выполнении первого запроса, как может обрабатывать @app.before_first_request, вы должны использовать Flask_Script, как сказал CESCO, но вы мог бы создать подкласс класса Server и переписать метод __call__, вместо того, чтобы перезаписать команду runserver с @manager.command:

from flask import Flask
from flask_script import Manager, Server

def custom_call():
    #Your code
    pass

class CustomServer(Server):
    def __call__(self, app, *args, **kwargs):
        custom_call()
        #Hint: Here you could manipulate app
        return Server.__call__(self, app, *args, **kwargs)

app = Flask(__name__)
manager = Manager(app)

# Remeber to add the command to your Manager instance
manager.add_command('runserver', CustomServer())

if __name__ == "__main__":
    manager.run()

Таким образом, вы не переопределяете параметры по умолчанию команды runserver.

Я только что сделал (в main.py выполнено с python main.py):

with app.app_context():
    from module import some_code()
    some_code()

def run():
    from webapp import app
    app.run(debug=True, use_reloader=False)

Это работало для меня без более полных ответов, предложенных выше. В моем случае some_code() инициализирует кеш через flask_caching.Cache,

Но это, вероятно, зависит от того, что именно some_code делается...

Мне не очень нравится ни один из методов, упомянутых выше, потому что вам не нужен Flask-Script, и не все проекты уже будут использовать Flask-Script.

Самый простой способ - создать собственный подкласс Flask. Где вы строите свое приложение с Flask(__name__)Вы просто добавили бы свой собственный класс и использовали бы его вместо этого.

def do_something():
  print('MyFlaskApp is starting up!')


class MyFlaskApp(Flask):
  def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
    if not self.debug or os.getenv('WERKZEUG_RUN_MAIN') == 'true':
      with self.app_context():
        do_something()
    super(MyFlaskApp, self).run(host=host, port=port, debug=debug, load_dotenv=load_dotenv, **options)


app = MyFlaskApp(__name__)
app.run()

Конечно, это не запускается после запуска, но прямо перед run() наконец-то называется. С контекстом приложения вы должны иметь возможность делать все, что вам может понадобиться с базой данных, или все, что требует контекста приложения. Это должно работать с любым сервером (uwsgi, gunicorn и т. Д.).

Если вам нужно do_something() чтобы быть неблокирующим, вы можете просто threading.Thread(target=do_something).start() вместо.

Условный оператор должен предотвратить двойной вызов при использовании режима отладки / перегрузчика.

Используйте Flask-Script для запуска вашего приложения, затем перезапишите класс / метод runserver следующим образом

# manage.py

from flask.ext.script import Manager

from myapp import app

manager = Manager(app)

def crazy_call():
    print("crazy_call")

@manager.command
def runserver():
    app.run()
    crazy_call()

if __name__ == "__main__":
    manager.run()

Если вы хотите запустить набор команд (как обычное приложение Py) после запуска фляги, используйте библиотеку с несколькими процессорами (например, многопроцессорность). В этом решении вы можете иметь приложение API/ веб-приложение рядом с системной программой.

import flask
from flask import request, jsonify,make_response
import time
import multiprocessing

app = flask.Flask('__name__')

def API(Conf):
   print('In API selction')
   app.run(host='0.0.0.0', port=1337,)
if __name__ == "__main__":
   config = {"Something":"SomethingElese"}
   p = multiprocessing.Process(target=API, args=(Conf,))
   p.start()
   #time.sleep(3)
   print('After Flask run')

Примечание: приведенный выше код - это всего лишь образец / идея. Может быть какая-то ошибка. (Но я использовал это решение на производстве, и все в порядке.)

PS: (с моей точки зрения) проблема этого решения: больше сложностей в разделе отладки.

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

Я запустил flask в параллельном потоке, например:

      import flask, requests, multiprocessing

app = flask.Flask(__name__)

@app.route("/"):
def frontpage():
    return "Hello World"

server = multiprocessing.Process(target=app.run)
try:
    server.start()

    # Is time.sleep(n) needed here to avoid a potential race condition?

    assert requests.get("http://127.0.0.1:5000/").text == "Hello World"

finally:
    server.terminate()
    server.join()

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

HEALTHCHECK CMD curl --fail http://localhost:8080/alive || exit 1

Поведение по умолчанию - выполнять эту команду каждые 30 секунд, и первый запуск удобно запускает мой метод init(). https://docs.docker.com/engine/reference/builder/

Другие вопросы по тегам