I try to run the server by command uvicorn my_packege.app:create_app and got error: TypeError: __call__() missing 1 required positional argument: 'send'. What could be the reason?
ping?
We don't document an app factory style anywhere, and it's not supported.
Run an app instance instead.
mypackage.py
app = create_app()
Then...
$ uvicorn my_package:app
I'm curious where the intent is coming from here, since Gunicorn doesn't use an "app factory" style either. Is there a particular Python framework / server that you're comparing this too?
(I'm not totally against us having an invocation like that, but it's not expected usage, and it's not obvious that it's required)
I'm curious where the intent is coming from here, since Gunicorn doesn't use an "app factory" style either. Is there a particular Python framework / server that you're comparing this too?
flask, aiohttp
(I'm not totally against us having an invocation like that, but it's not expected usage, and it's not obvious that it's required)
Factory need when you need test the app. If you use a global app then you get one app but in unit tests need to create an app for each test.
Okay, reviewed https://flask.palletsprojects.com/en/1.1.x/tutorial/factory/ and I see where you're coming from.
I'm used to using a twelve-factor style, and making sure that the system environment variables are setup differently for testing.
We could possibly accept an --app-factory flag, which changes the main callable from "the app itself" into "a function returning the app".
Alternately you could instantiate the default application using the factory, so...
def create_app(...): # We can use this to create a different app instance for testing.
...
app = create_app() # The default case.
Recently ran into this myself; the main reason my team did not want to use app = create_app() is that it would instantiate an instance whenever the module it exists in (e.g. from cool_web_thing.app import app) is imported (import cool_web_thing.app) and we weren't sure we wanted that to happen.
We got around it by creating a separate module from cool_web_thing._devel import app which we only use for testing changes locally (in production we use Gunicorn).
Also, fwiw, Gunicorn does allow using an app-factory / function:
gunicorn cool_web_thing.app:create_app() -b :5000 -k uvicorn.workers.UvicornWorker --reload
Works while:
uvicorn cool_web_thing.app:create_app() --port 5000 --reload
Does not.
Creating application as global variable in some module is worst ever possible option in the world. Mentioned twelve-factor app has absolutely nothing to do with such style of programming.
I _think_ a lazy loading pattern ought to address your use case here…?
# cool_web_thing.py
from .factory import create_app
class LazyApp:
def __init__(self):
self._app = None
async def __call__(self, scope, receive, send):
if self._app is None:
self._app = create_app()
await self._app(scope, receive, send)
app = LazyApp()
Run as
uvicorn cool_web_thing:app
as usual, except the _actual_ app will be instantiated on the first request — meaning you're not risking an "instantiate on module import".
Going to close this off for now since I'm not sure we're going to support app factories _just yet_, especially since this seems to be motivated in more advanced use cases (when startup time matters, e.g. very-large-scale apps/projects), and in that case a bit of extra code like the above shouldn't be an issue.
application factories were added to flask due to the need to not having application creation a side effect of importing modules. It also allows having different versions/configurations of applications existing at the same time, which is great for unit tests. This pattern extends further, in that _settings_ are typically stored as an object on the application itself, rather than statics in a settings module. Different settings can thus be created for different purposes, without having to restart the entire process.
I am a bit surprised that this kind of pattern isn't encouraged and supported by starlette / uvicorn, since it evolved as a response to a need.
:arrow_up: I've had some spare time, and I've created a small PR to support this case. I've read the contribution guide, and I feel bad not asking permission to work on this from the maintainers. But at this point the code was already done. :)
Most helpful comment
flask, aiohttp
Factory need when you need test the app. If you use a global app then you get one app but in unit tests need to create an app for each test.