Home > Enterprise >  Is there a way to run a function in the background of a python flask app?
Is there a way to run a function in the background of a python flask app?

Time:11-26

I'm attempting to run a function in the background of a flask app. The app currently attempts to read input from a midi keyboard and send the information to a website. I've looked into threading, but my findings/attempts so far have been unsuccessful.

Essentially, JavaScript/JQuery requests for data from the '/get_notes' app route every second. Theoretically, if get_notes() is running between these seconds it can send a json-formatted list containing all keys pressed in that specific time period.

I've paraphrased & included the relevant code from app.py below. Any suggestions would be appreciated!

from Flask import Flask
import mido

app = Flask(__name__)

# List to store pressed keys
notes_pressed = []

@app.route('/get_notes', methods=['GET'])
def return_pressed_notes():
    return json.dumps(notes_pressed)

# Function to translate midi key numbers to note letters
def translate_key(key_num):
    ...

# Function that returns recently played notes within time period
def get_notes():
    # Open port to listen for key presses
    with mido.open_input() as inport:
        for msg in inport:
            # If key press is valid
            # - note is pressed
            # - velocity!=0 (prevents ghost notes)
            if (msg.type=='note_on' and msg.velocity!=0 and msg.channel==0):
                # Add new note to list
                notes_pressed.append(translate_key(msg.note - lowest_key))

if __name__ == '__main__':
    app.run()
    # Somehow run get_notes() in background?

CodePudding user response:

You can use an APScheduler

example:

from Flask import Flask
from flask_apscheduler import APScheduler

app = Flask(__name__)
scheduler = APScheduler()
scheduler.init_app(app)

@scheduler.task('cron', id='get_note', hour=12) # every day at noon
def get_notes:
    return 'blablabla'

if __name__ == '__main__':
    scheduler.start()
    app.run()

What do you think about this method?

CodePudding user response:

I have run into a similar problem building some online cassino games using flask.

The solution I found was to run a celery task with an infinite loop and save the current state of the task so that flask can access it anytime.

Celery is a production grade solution for running background tasks that works well with flask.

I'm not going to dig deep into celery here as it is a complex topic, but these guide is excellent for implementing celery with flask: https://blog.miguelgrinberg.com/post/using-celery-with-flask

In your case, I am not really sure how the mido library works, but per your question it looks like "with mido.open_input() as inport:" already opens a I/O channel and maitains it perpetually. In this case you won't even need to reformat the function, just add the output list as the task's state.

@celery.task(bind=True)
def get_notes(self):
    notes_pressed = []
    # Open port to listen for key presses
    with mido.open_input() as inport:
        for msg in inport:
            # If key press is valid
            # - note is pressed
            # - velocity!=0 (prevents ghost notes)
            if (msg.type=='note_on' and msg.velocity!=0 and msg.channel==0):
                # Add new note to list
                notes_pressed.append(translate_key(msg.note - lowest_key))
                self.update_state(state="PROGRESS",
                                   meta={data: notes_pressed})

Then you can create two different endpoints in your flask app. One that initializes the celery task and another that reads its status:

@app.route('/longtask', methods=['POST'])
def longtask():
    task = get_notes.apply_async()
    return jsonify({}), 202, {'Location': url_for('taskstatus',
                                                  task_id=task.id)}

@app.route('/status/<task_id>')
def taskstatus(task_id):
    task = get_notes.AsyncResult(task_id)
    notes_pressed = task.info.get("data")
    #do whatever you want with it
    return json.dumps(notes_pressed)

Please let me know if that would work for you and if there are any issues using mido with celery.

  • Related