Search code examples
javascriptpythonhtmlflaskclient-server

dynamically constructing client side content from server in flask


I am trying to setup client side content from server in a flask app. Here is my code

# Importing flask module in the project is mandatory
# An object of Flask class is our WSGI application.
from flask import Flask, send_file, Response, render_template, redirect, url_for, request
import random
import time
import wave
import io
import os

# Flask constructor takes the name of
# current module (__name__) as argument.
app = Flask(__name__)
 
# The route() function of the Flask class is a decorator,
# which tells the application which URL should call
# the associated function.
# ‘/’ URL is bound with index() function.
@app.route('/')
def paadas():
    #Approach 2
    def generate(files):
        with wave.open(files[0], 'rb') as f:
            params = f.getparams()
            frames = f.readframes(f.getnframes())
        
        for file in files[1:]:
            with wave.open(file, 'rb') as f:
                frames += f.readframes(f.getnframes())
        
        buffer = io.BytesIO()
        with wave.open(buffer, 'wb') as f:
            f.setparams(params)
            f.writeframes(frames)
        
        buffer.seek(0)
        return buffer.read()

    files = []
    number = random.randint(1,10)
    files.append("./numbers/" + str(number) + ".wav")
    times = random.randint(1,10)
    files.append("./times/" + str(times) + ".wav")
    data = dict(
        file=(generate(files), "padaa.wav"),
    )

    app.post(url_for('static', filename='padaa.wav'), content_type='multipart/form-data', data=data)
    print ('posted data on client\n')
    return render_template("index.html")

@app.route("/recording", methods=['POST', 'GET'])
def index():
    if request.method == "POST":
        f = open('./file.wav', 'wb')
        print(request)
        f.write(request.data)
        f.close()
        if os.path.isfile('./file.wav'):
            print("./file.wav exists")

        return render_template('index.html', request="POST")   
    else:
        return render_template("index.html")
 
# main driver function
if __name__ == '__main__':
 
    # run() method of Flask class runs the application
    # on the local development server.
    app.run()

However it fails while doing app.post with the error of

AssertionError: The setup method 'post' can no longer be called on the application. It has already handled its first request, any changes will not be applied consistently.
Make sure all imports, decorators, functions, etc. needed to set up the application are done before running it.

Here is the complete stack:

Traceback (most recent call last):
  File "C:\ML\Tables\app\webapp\venv\lib\site-packages\flask\app.py", line 2213, in __call__
    return self.wsgi_app(environ, start_response)
  File "C:\ML\Tables\app\webapp\venv\lib\site-packages\flask\app.py", line 2193, in wsgi_app
    response = self.handle_exception(e)
  File "C:\ML\Tables\app\webapp\venv\lib\site-packages\flask\app.py", line 2190, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\ML\Tables\app\webapp\venv\lib\site-packages\flask\app.py", line 1486, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\ML\Tables\app\webapp\venv\lib\site-packages\flask\app.py", line 1484, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\ML\Tables\app\webapp\venv\lib\site-packages\flask\app.py", line 1469, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File "C:\ML\Tables\app\PaadasMLFlaskApp\PaadasML.py", line 47, in paadas
    app.post(url_for('static', filename='padaa.wav'), content_type='multipart/form-data', data=data)
  File "C:\ML\Tables\app\webapp\venv\lib\site-packages\flask\scaffold.py", line 50, in wrapper_func
    self._check_setup_finished(f_name)
  File "C:\ML\Tables\app\webapp\venv\lib\site-packages\flask\app.py", line 511, in _check_setup_finished
    raise AssertionError(

What am I missing? How can I do this? You can get my complete working app from https://github.com/sameermahajan/PaadasMLFlaskApp if you want to try out. Just uncomment the app.post... statement in the python file to reproduce the error.

Basically when I return from paadas() I want to play the audio like it is done by

return Response(generate(files), mimetype='audio/wav')

but want the focus on index.html so that I can continue my interaction.


Solution

  • I could do it by changing my server code to something like:

    @app.route('/')
    def index():
        return render_template("index.html")
    
    @app.route('/paadas')
    def paadas():
        ...
        return Response(generate(files), mimetype='audio/wav')
    

    and adding something like

     <audio controls autoplay>
          <source src="{{ url_for('paadas' }}" type="audio/wav">
     </audio>
    

    in my index.html. Thanks to the answer and tip from Rob in my another question. The render_template from my home route keeps my focus on index.html as I wanted. As a part of the rendering, the url_for('paadas') above actually invokes my server side function at /paadas route (the key insight I got from Rob's answer / comment). Since it is returning the response as a wav file, it gets rendered in my browser as a part of the template. (Currently it is auto playing only in case of a single file. I am further investigating the auto play reliably in all my possible cases).

    I have made a lot of progress with my app as you can see in my repo shared originally with this question thanks to this community and the help I have been getting here. My original approach in this question of trying to 'app.post' hoping to get the content on client was incorrect which I was trying since I couldn't get my earlier approach to work. Learnt a lot from this community during this process like differences of running javascript from browser environment, javascript run from browser doesn't really have access to client side filesystem for security reasons, I cannot easily write to my client filesystem with my javascript which can get data from the server side, use of XMLHttpRequest, onreadystatechange and document.write in my javascript to send my custom data from client to server and still render the complete template in my browser updated with server side response to my request etc. to achieve quite a lot in my application so far.

    Posting these updates here in case someone else stumbles upon similar issues working with flask web app and finds these details / pointers useful.