I am currently running an api project (fastapi served with uvicorn) where at startup a series of initializations are done.
If I set reload=True as an argument to my uvicorn startup function, my api correctly recognizes code changes and reloads. The issue is that I don't get the initializations when reloading the api, and this ends up breaking my workflow and effectively blocking me from using what I consider a very useful feature.
Example:
# fastapi app object is located in another module
def main() -> None:
various_initializations()
if __name__ == "__main__":
main()
uvicorn.run("my.project.location.for:app", host="0.0.0.0", port=my_port, reload=True)
In this case I need my main function to be run at each reload.
Edit:
Testable example
main.py
import uvicorn
from my_class import MyClass
def init_stuff() -> None:
MyClass.initialize()
if __name__ == "__main__":
init_stuff()
uvicorn.run("api:app", host="0.0.0.0", port=10000, reload=True)
my_class.py
class MyClass:
initialized = False
#staticmethod
def initialize() -> None:
MyClass.initialized = True
#staticmethod
def do_stuff() -> None:
if not MyClass.initialized:
raise ValueError("not initialized!")
print("doing stuff")
api.py
from fastapi import FastAPI
from my_class import MyClass
app = FastAPI()
#app.get("/stuff")
def api_stuff() -> None:
return MyClass.do_stuff()
When reload=False, if I hit the /stuff endpoint I get a correct behavior (doing stuff is printed on the terminal). With reload=True I get an exception indicating that MyClass hasn't been initialized (ValueError: not initialized!)
as #MatsLindh suggested, the (a?) way to do it is to hook on the startup event of fastapi, making the initialization run on each reload.
my main.py file now looks like this:
import uvicorn
from my_class import MyClass
from api import app
#app.on_event("startup")
def init_stuff() -> None:
MyClass.initialize()
if __name__ == "__main__":
uvicorn.run("api:app", host="0.0.0.0", port=10000, reload=True)
Happy api noises
Related
I'm trying to get 100% code coverage with pytest-cov and I'm just missing the lines 245-248 according to this.
server.py, 97%, 245-248
Here are the relevant lines in server.py:
if __name__ == '__main__':
with app.app_context():
db.drop_all() # Drop all existing tables in the database
db.create_all() # Create new tables in the database
app.run(port=5000, debug=True)
testing.py:
import tempfile
import os
import pytest
import server
import time
# Set up client
#pytest.fixture
def client():
db_fd, name = tempfile.mkstemp() # Creates and opens a temporary file
server.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+str(name)
server.app.config['TESTING'] = True
with server.app.test_client() as client:
with server.app.app_context():
server.db.drop_all() # Clear database between test functions
server.db.create_all() # Create new database
yield client
# Test is over, close and remove the temporary file
os.close(db_fd) # Tear down
os.unlink(name) # Unlink
def test_server(client):
with server.app.app_context():
server.db.drop_all()
server.db.create_all()
with server.db.session.begin():
server.db.session.add(server.User(username='testuser'))
result = server.db.session.query(server.User).filter_by(username='testuser').first()
assert result is not None
assert isinstance(result, server.User)
It passed but it's not covering the test case. What do I need to change to make it work?
If you are worried that the code inside if __name__ == '__main__': is not getting tested, which would be a valid concern, then I would strongly suggest to move it out into a function that could be tested, so that you end up only with something like:
if __name__ == '__main__':
main()
If after that you are still not happy that you don't have 100% coverage, then I'm afraid that what you are chasing is a bit of a fallacy. However, you could then exclude all such blocks from your reports.
.coveragerc:
[report]
exclude_lines =
if __name__\s*==\s*['"]__main__['"]\s*:
You can add # pragma: no cover in the lines you want to exclude:
if __name__ == '__main__': # pragma: no cover
with app.app_context():
db.drop_all() # Drop all existing tables in the database
db.create_all() # Create new tables in the database
app.run(port=5000, debug=True)
I am trying to get coverage up on my Python code and am testing my Flask app. Without being too specific, let's look at the sample code here:
# from /my_project/app_demo/app.py
from private_module import logs
from flask import Flask
import logging
logs.init_logging()
logger = logging.getLogger(__name__)
app = Flask(__name__)
#app.route("/greet/<name:name>", methods=["GET"])
def greet(name):
logger.info(f"{name} wants a greeting")
return f"Hello, {name}!"
if __name__ == "__main__":
app.run(debug=True)
If I am to write a unit test for the greet function, I want to mock the logger to assert it is called when it is created and when the info method is called. For this example, the sources root folder is app_demo and there is an init.py in that python folder.
# from /my_project/tests/test_app.py
import pytest
from unittest import mock
#pytest.fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client
def test_greet(client):
logs_mock = mock.patch("app_demo.app.logs.init_logging")
logger_mock = mock.patch("app_demo.app.logging.getLogger")
actual = client.get("George")
assert "Hello, George!" == actual
# these next assertions do not work
logs_mock.assert_called_once_with()
logging_mock.assert_called_once_with("app_demo.app") # not sure if this will be "__main__"
logging_mock.return_value.info.assert_called_once_with("George wants a greeting")
If I debug where the logger.info is called in the greet function, the object is a logger and not the mock object I want it to be.
I have tried making the mock in another fixture as well but that did not make it work either.
The tornado testing subject doc is so simple, I am not quite sure how to do a unit test on tornado. like that:
here is a api.py:
import tornado
import logging
from tornado.web import RequestHandler
import time
class AnalyticsBWSpecificHour(RequestHandler):
def get(self):
return self.write({'message':'no get method'})
class Application(tornado.web.Application):
def __init__(self,**kwargs):
api_handlers = [
(r"/", AnalyticsBWSpecificHour),
]
logging.debug(api_handlers)
super(Application, self).__init__(api_handlers, **kwargs)
and the test_tornado.py :
from api import Application
from tornado.testing import AsyncHTTPTestCase
import tornado
import logging
logging.basicConfig(level=logging.DEBUG)
import unittest
class ApiTestCase(AsyncHTTPTestCase):
def get_app(self):
self.app = Application(debug=True)
return self.app
def test_status(self):
print(self.get_url('/'))
response = self.fetch(self.get_url('/'),method='GET')
self.assertEqual(response.code,200)
if __name__ == '__main__':
unittest.main()
even this is quite simple example, I also get the 599 error. please help me.
response = self.fetch(self.get_url('/'),method='GET')
self.fetch() calls self.get_url for you. Either do self.fetch('/') or self.http_client.fetch(self.get_url('/')), but don't mix the two.
Also don't pass debug=True in tests; the autoreload will do the wrong thing in a unittest environment.
I am rapid prototyping so for now I would like to write unit tests together with the code I am testing - rather than putting the tests in a separate file. However, I would only like to build the test an invoke them if we run the code in the containing file as main. As follows:
class MyClass:
def __init(self)__:
# do init stuff here
def myclassFunction():
# do something
if __name__ == '__main__':
import unittest
class TestMyClass(unittest.TestCase):
def test_one(self):
# test myclassFunction()
suite = unittest.TestLoader().loadTestsFromTestCase(TestMyClass)
unittest.TextTestRunner(verbosity=2).run(suite)
This of course doesn't run as unittest is unable to find or make use of the test class, TestMyClass. Is there a way to get this arrangement to work or do I have to pull everything out of the if __name__ == '__main__' scope except the invocation to unittest?
Thanks!
If you move your TestMyClass above of if __name__ == '__main__'
you will get exactly what you want:
Tests will run only when file executed as main
Something like this
import unittest
class MyClass:
def __init(self)__:
# do init stuff here
def myclassFunction():
# do something
class TestMyClass(unittest.TestCase):
def test_one(self):
if __name__ == '__main__':
unittest.main()
I'm dealing with global variables in Python. The code should work fine, but there is a problem. I have to use global variable for instance of class Back. When I run the application it says that back is None which should be not true because the second line in setup() function - 'back = Back.Back()'
# -*- coding: utf-8 -*-
from flask import Flask
from flask import request
from flask import render_template
import Search
import Back
app = Flask(__name__)
global back
back = None
#app.route('/')
def my_form():
return render_template('my-form.html')
def setup():
global back
back = Back.Back()
def is_ascii(s):
return all(ord(c) < 128 for c in s)
#app.route('/', methods=['POST'])
def search():
from time import time
pattern = request.form['text']
startTime = time()
pattern=pattern.lower()
arr = []
if len(pattern)<1:
arr.append('Incorrect input')
currentTime = time()-startTime
return render_template('my-form.html', arr=arr, time=currentTime)
arr = []
search = Search.Search(pattern,back)
results = search.getResults()
..................
return render_template('my-form.html', arr=arr, time=currentTime, pattern=pattern)
app.debug=True
if __name__ == '__main__':
setup()
app.run()
Why is the back variable None instead of instance of Back class? Thanks
The Flask development server runs your module twice. Once to run the server itself, and another time in a child process so it can reload your whole script each time you make a change to it. It is that second process that won't run the __main__ guarded code and the global is left as None.
You'll get the same problem if you used another WSGI server; it'd import your file as a module, not as the initial script and the __main__ guard is not executed.
Use a #app.before_first_request function instead; it is guaranteed to be executed when the very first request is handled for this process. This also keeps your global working if you moved to a proper WSGI container that used multiple child processes to scale your site:
#app.before_first_request
def setup():
global back
back = Back.Back()