Question
How to test a blocking cli application in Python?
What I am trying to accomplish
I have a manage.py file, which is used to start the flask development web server (and other admin stuff that are out of the scope of this question). Typically I would run this as python manage.py runserver, but for keeping this question simple and relevant I have edited that file (see Minimal, Reproducible Example) to just run the server. So the below command works
$ python manage.py
* Serving Flask app "app" (lazy loading)
* Environment: production...
The issue comes with testing the functionality of this cli application. Click has the CliRunner.invoke(), which allows one to test cli applications. But in this particular case, it fails as the server is already running.
from manage import runserver
from click.testing import CliRunner
import requests
import unittest
class TestManage(unittest.TestCase):
def test_runserver(self):
runner = CliRunner()
runner.invoke(runserver)
# It blocks above, does not go below!
r = requests.get('http://127.0.0.1')
assert r.status_code == 200
if __name__ == '__main__':
unittest.main()
Attempts at solution
After trying many approaches I have found a "solution", which is to spawn a Process to start the server, run the tests and then terminating the process on exit (this is included in the example). This feels more like a hack rather than a "real" solution.
Minimal, Reproducible Example
Folder structure
flask_app/
- app.py
- manage.py
- test_manage.py
app.py
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello():
return 'Hello, World!'
manage.py
import click
from app import app
#click.command()
def runserver():
app.run()
if __name__ == "__main__":
runserver()
test_manage.py
from manage import runserver
from multiprocessing import Process
from click.testing import CliRunner
import requests
import unittest
class TestManage(unittest.TestCase):
def setUp(self):
self.runner = CliRunner()
self.server = Process(
target=self.runner.invoke,
args=(runserver, )
)
self.server.start()
def test_runserver(self):
r = requests.get('http://127.0.0.1')
assert r.status_code == 200
def tearDown(self):
self.server.terminate()
if __name__ == '__main__':
unittest.main()
And run the test using $ python test_manage.py.
Related
I want to run FastAPI server using Uvicorn from A different Python file.
uvicornmodule/main.py
import uvicorn
import webbrowser
from fastapi import FastAPI
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
app = FastAPI()
import os
script_dir = os.path.dirname(__file__)
st_abs_file_path = os.path.join(script_dir, "static/")
app.mount("/static", StaticFiles(directory=st_abs_file_path), name="static")
#app.get("/")
async def index():
return FileResponse('static/index.html', media_type='text/html')
def start_server():
# print('Starting Server...')
uvicorn.run(
"app",
host="0.0.0.0",
port=8765,
log_level="debug",
reload=True,
)
# webbrowser.open("http://127.0.0.1:8765")
if __name__ == "__main__":
start_server()
So, I want to run the FastAPI server from the below test.py file:
from uvicornmodule import main
main.start_server()
Then, I run python test.py.
But I am getting the below error:
RuntimeError:
An attempt has been made to start a new process before the
current process has finished its bootstrapping phase.
This probably means that you are not using fork to start your
child processes and you have forgotten to use the proper idiom
in the main module:
if __name__ == '__main__':
freeze_support()
...
The "freeze_support()" line can be omitted if the program
is not going to be frozen to produce an executable.
What I am doing wrong? I need to run this module as package.
When spawning new processes from the main process (as this is what happens when uvicorn.run() is called), it is important to protect the entry point to avoid recursive spawning of subprocesses, etc. As described in this article:
If the entry point was not protected with an if-statement idiom
checking for the top-level environment, then the script would execute
again directly, rather than run a new child process as expected.
Protecting the entry point ensures that the program is only started
once, that the tasks of the main process are only executed by the main
process and not the child processes.
Basically, your code that creates the new process must be under if __name__ == '__main__':. Hence:
from uvicornmodule import main
if __name__ == "__main__":
main.start_server()
Additionally, running uvicorn programmatically and having reload and/or workers flag(s) enabled, you must pass the application as an import string in the format of "<module>:<attribute>". For example:
# main.py
import uvicorn
from fastapi import FastAPI
app = FastAPI()
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
As a sidenote, the below would also work, if reload and/or workers flags were not used:
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Also, as per FastAPI documentation, when running the server from a terminal in the following way (the default port is 8000. Have a look at all the available command line options):
> uvicorn main:app --reload
the command uvicorn main:app refers to:
main: the file main.py (the Python "module").
app: the object created inside of main.py with the line app = FastAPI().
--reload: make the server restart after code changes. Only use for development.
tl,dr: How can I programmably execute a python module (not function) as a separate process from a different python module?
On my development laptop, I have a 'server' module containing a bottle server. In this module, the name==main clause starts the bottle server.
#bt_app.post("/")
def server_post():
<< Generate response to 'http://server.com/' >>
if __name__ == '__main__':
serve(bt_app, port=localhost:8080)
I also have a 'test_server' module containing pytests. In this module, the name==main clause runs pytest and displays the results.
def test_something():
_rtn = some_server_function()
assert _rtn == desired
if __name__ == '__main__':
_rtn = pytest.main([__file__])
print("Pytest returned: ", _rtn)
Currently, I manually run the server module (starting the web server on localhost), then I manually start the pytest module which issues html requests to the running server module and checks the responses.
Sometimes I forget to start the server module. No big deal but annoying. So I'd like to know if I can programmatically start the server module as a separate process from the pytest module (just as I'm doing manually now) so I don't forget to start it manually.
Thanks
There is my test cases dir tree:
test
├── server.py
└── test_server.py
server.py start a web server with flask.
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello, World!'
if __name__ == '__main__':
app.run()
test_server.py make request to test.
import sys
import requests
import subprocess
import time
p = None # server process
def start_server():
global p
sys.path.append('/tmp/test')
# here you may want to do some check.
# whether the server is already started, then pass this fucntion
kwargs = {} # here u can pass other args needed
p = subprocess.Popen(['python','server.py'], **kwargs)
def test_function():
response = requests.get('http://localhost:5000/')
print('This is response body: ', response.text)
if __name__ == '__main__':
start_server()
time.sleep(3) # waiting server started
test_function()
p.kill()
Then you can do python test_server to start the server and do test cases.
PS: Popen() needs python3.5+. if older version, use run instead
import logging
import threading
import time
def thread_function(name):
logging.info("Thread %s: starting", name)
time.sleep(2)
logging.info("Thread %s: finishing", name)
if __name__ == "__main__":
format = "%(asctime)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO,
datefmt="%H:%M:%S")
threads = list()
for index in range(3):
logging.info("Main : create and start thread %d.", index)
x = threading.Thread(target=thread_function, args=(index,))
threads.append(x)
x.start()
for index, thread in enumerate(threads):
logging.info("Main : before joining thread %d.", index)
thread.join()
logging.info("Main : thread %d done", index)
With threading you can run multiple processes at once!
Wim baasically answered this question. I looked into the subprocess module. While reading up on it, I stumbled on the os.system function.
In short, subprocess is a highly flexible and functional program for running a program. os.system, on the other hand, is much simpler, with far fewer functions.
Just running a python module is simple, so I settled on os.system.
import os
server_path = "python -m ../src/server.py"
os.system(server_path)
Wim, thanks for the pointer. Had it been a full fledged answer I would have upvoted it. Redo it as a full fledged answer and I'll do so.
Async to the rescue.
import gevent
from gevent import monkey, spawn
monkey.patch_all()
from gevent.pywsgi import WSGIServer
#bt_app.post("/")
def server_post():
<< Generate response to 'http://server.com/' >>
def test_something():
_rtn = some_server_function()
assert _rtn == desired
print("Pytest returned: ",_rtn)
sleep(0)
if __name__ == '__main__':
spawn(test_something) #runs async
server = WSGIServer(("0.0.0.0", 8080, bt_app)
server.serve_forever()
Problem
I've created a basic python script, using flask to render an HTML page. On Windows 10, the script works perfectly as a *.py file, but when run as a *.pyw file, the page is not rendered.
In Task Manager, instances of python are opened and closed within seconds after running the script as *.pyw.
Code
from flask import Flask, render_template
app = Flask(__name__)
#app.route("/")
def main():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True, host="0.0.0.0", port=80)
Workaround
Run the *.py version with the following lines added to the code:
import ctypes
...
...
...
if __name__ == '__main__':
cytypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
app.run(debug=True, host="0.0.0.0", port=80)
Code above hides the console, and starts the flask app successfully.
However, I am still interested in an explanation as to why the *.pyw method won't work, if anyone has an idea.
.pyw-files would ran on pythonw.exe rather than python.exe. The difference is, that pythonw.exe does not run in a console by default and runs asynchronous. This would mean that flask starts and runs in the background untill everything else terminates. Since you to not have anything else in your application, the programm ends directly.
I have a Flask application with custom signal handlers to take care of clean up tasks before exiting. When running the application with gunicorn, gunicorn kills the application before it can complete all clean up tasks.
You didn't explain what you mean by custom signal handlers, but I'm not sure that you should be using Flask's signals to capture process-level events, like shutdown. Instead, you can use the signal module from the standard library to hook onto the SIGTERM signal, like so:
# app.py - CREATE THIS FILE
from flask import Flask
from time import sleep, time
import signal
import sys
def create_app():
signal.signal(signal.SIGTERM, my_teardown_handler)
app = Flask(__name__)
#app.route('/')
def home():
return 'hi'
return app
def my_teardown_handler(signal, frame):
"""Sleeps for 3 seconds, then creates/updates a file named app-log.txt with the timestamp."""
sleep(3)
with open('app-log.txt', 'w') as f:
msg = ''.join(['The time is: ', str(time())])
f.write(msg)
sys.exit(0)
if __name__ == '__main__':
app = create_app()
app.run(port=8888)
# wsgi.py - CREATE THIS FILE, in same folder as app.py
import os
import sys
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.exceptions import NotFound
from app import create_app
app = DispatcherMiddleware(create_app())
Assuming you have a virtual environment with Flask and Gunicorn installed, you should then be able to launch the app with Gunicorn:
$ gunicorn --bind 127.0.0.1:8888 --log-level debug wsgi:app
Next, in a separate terminal, you can send the TERM signal to your app, like so:
$ kill -s TERM [PROCESS ID OF GUNICORN PROCESS / $(ps ax | grep gunicorn | head -n 1 | awk '{print $1}')]
And to observe the results, you should notice that the contents of the app-log.txt file get updated when you run that kill command, after the three-second delay. You could even spawn a third terminal window in this directory and run watch -n 1 "cat app-log.txt" to observe this file being updated in real time, while you cycle between starting the app and sending the TERM signal.
As for tying that into production, I know that Supervisor has a configuration option to specify the stopsignal, like so:
[program:my-app]
command = /path/to/gunicorn [RUNTIME FLAGS]
stopsignal = TERM
...
But that's a separate topic from the original issue of ensuring that your app's clean up tasks are completely executed.
I am running SocketIOServer for my flask app and I have a run.py script which looks like this:
import sys
from idateproto import create_app, run_app
if __name__ == "__main__":
if len(sys.argv) > 1:
create_app(sys.argv[1])
else:
create_app('dev')
run_app()
and run_app looks like this:
def run_app():
# Run the application
# See flask/app.py run() for the implementation of run().
# See http://werkzeug.pocoo.org/docs/serving/ for the parameters of Werkzeug's run_simple()
# If the debug parameter is not set, Flask does not change app.debug, which is set from
# the DEBUG app config variable, which we've set in create_app().
#app.run(**app_run_args)
global app
global middleware
SocketIOServer((app.config['HOST'], app.conf`enter code here`ig['PORT']), middleware,namespace="socket.io", policy_server=False).serve_forever()
I run this like: python run.py production/dev/test etc.
Now I want to run it in production using gunicorn and all the tutorials online suggest doing something like:
gunicorn run:app -c gunicorn-config.py
My problem is I don't want to run just my app any more. I want to tell gunicorn to run the serve_forever method on the SocketIOServer instead, I have done a lot of research online and can not find a way to achieve this. Please help.