S Andrew
S Andrew

Reputation: 7268

How to move out of app.run() in flask API Python

I am working on python project which includes starting a flask API server on a button click. For buttons and UI I am using pyqt5. So on start server button click flask api server will start and on stop server button click, flask api server will stop.

I am able to start the server but the problem is when we start the flask api server, we use app.run(HOST, 80). During this the control always remains here and doesnt move out of it due to which when I click stop button, it doesnt stop. Below is the code:

app.py

import sys
import time
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from server import start_local_server
from PyQt5.QtCore import pyqtSlot
from threading import Thread


run = True


def start_api_server():
    while run:
        start_local_server()
        print("server is running")
    time.sleep(1)
    print("SERVER HAS STOPPED")


class App(QWidget):
    global run

    def __init__(self):
        super().__init__()
        self.title = 'PyQt5 button - pythonspot.com'
        self.left = 10
        self.top = 10
        self.width = 320
        self.height = 200
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        start_btn = QPushButton('Start Server', self)
        start_btn.move(100, 70)
        start_btn.clicked.connect(self.on_click_start_btn)

        stop_btn = QPushButton('Stop Server', self)
        stop_btn.move(200, 70)
        stop_btn.clicked.connect(self.on_click_stop_btn)

        fun_btn = QPushButton('Click to check responsiveness', self)
        fun_btn.move(150, 100)
        fun_btn.clicked.connect(self.on_click_fun_btn)

        self.show()

    @pyqtSlot()
    def on_click_start_btn(self):
        Thread(target=start_api_server).start()
        
    @pyqtSlot()
    def on_click_stop_btn(self):
        print("Stop server ")
        run = False
        
    @pyqtSlot()
    def on_click_fun_btn(self):
        print('If it is working, this means UI is responsive')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

In above code, I have button click function on_click_start_btn this starts a thread with target start_api_server. This will further start the local flask server. When I want to stop the server, I will simply make run as False which will break the start_api_server function and the server will stop. As per my understanding this should work fine.

Below is the code for flask api:

server.py

import os
import datetime
from flask import Flask, jsonify
from flask_cors import CORS


app = Flask(__name__)
CORS(app)
wsgi_app = app.wsgi_app


@app.route('/api/status')
def check_status():
    return jsonify({'status': 'ok', 'date': datetime.datetime.now().isoformat()}), 200


def start_local_server():
    HOST = os.environ.get('SERVER_HOST', 'localhost')
    try:
        PORT = int(os.environ.get('SERVER_PORT', '5555'))
    except ValueError:
        PORT = 5555
    app.run(HOST, 80)

The problem occurs in above flask code. In above code, there is function start_local_server() which we are using in app.py. In this we have app.run(), when our code reaches here, it always remains here and never moves out it due to which I am not able to stop this server.

I want to simply make code where I can start and stop the flask server using button click but due to app.run, its not working. Can anyone please help me with this issue. Is there any alternative to above problem. Please help. Thanks

Updated code as per answer:

server = Process

def start_local_server():
    global server
    server = Process(target=app.run, args=('localhost', 80))
    # HOST = os.environ.get('SERVER_HOST', 'localhost')
    # try:
    #     PORT = int(os.environ.get('SERVER_PORT', '5555'))
    # except ValueError:
    #     PORT = 5555
    # app.run(HOST, 80)


def stop_local_server():
    global server
    server.terminate()

Upvotes: 1

Views: 8162

Answers (2)

S Andrew
S Andrew

Reputation: 7268

I have resolved the issue by starting the API server in separate thread so that Ui keeps on working. Then I am shutting down the API server using shutdown api

run = True


def start_api_server():
    while run:
        start_local_server()
        
    time.sleep(1)
    print("SERVER HAS STOPPED")

@pyqtSlot()
def on_click_start_btn(self):
    Thread(target=start_api_server).start()

Above is the code to start the server on button click. Below is how I am shutting it down:

@pyqtSlot()
def on_click_stop_btn(self):
    global run
    print("STOPPING SERVER")
    run = False
    try:
        x = requests.get("http://localhost:80/api/shutdown")
        rdata = x.text
        rdata = json.loads(rdata)
        if rdata['status']:
            print("Server shutting down")
        print(rdata)

    except Exception as e:
        print(e)

Above code calls a shutdown API which terminate the flask api server. Below is the code for it:

def shutdown_server():
    func = request.environ.get('werkzeug.server.shutdown')
    if func is None:
        raise RuntimeError('Not running with the Werkzeug Server')
    func()


@app.route('/api/shutdown')
def shutdown():
    shutdown_server()
    return 'Server shutting down...'

Upvotes: 0

moe asal
moe asal

Reputation: 582

You can run app.run() in a seperate Process. Multiprocessing would be helpful in your case, since you're using pyQt.

from multiprocessing import Process

server = Process(target=app.run, args=(HOST, 80))
server.start() # to start the server
server.terminate() # to terminate the server

You can take a deeper look at multiprocessing library documentation here https://docs.python.org/2/library/multiprocessing.html

You forgot to strart the server :)

server = None

def start_local_server():
    global server
    server = Process(target=app.run, args=('localhost', 80))
    server.start()
    # HOST = os.environ.get('SERVER_HOST', 'localhost')
    # try:
    #     PORT = int(os.environ.get('SERVER_PORT', '5555'))
    # except ValueError:
    #     PORT = 5555
    # app.run(HOST, 80)


def stop_local_server():
    global server
    server.terminate()

Upvotes: 2

Related Questions