Uvicorn: A pattern for passing arguments to ASGI applications via the CLI

Created on 27 Jul 2018  路  2Comments  路  Source: encode/uvicorn

uvicorn supports starting a specific ASGI application like this:

uvicorn app:App --port=5000

This pattern is also documented as the way to start gunicorn with the uvicorn worker class.

It's not obvious how one would build an ASGI app that takes different arguments but can still be launched in this way.

My datasette app needs exactly this. Datasette is traditionally launched using its own CLI tool - for example:

datasette trends.db flights.db --metadata=metadata.json -p 5000

For passing these arguments to uvicorn/gunicorn the best option I've come up with so far is to bundle them into an environment variable:

DATASETTE_OPTION="trends.db flights.db --metadata=metadata.json" \
    uvicorn datasette:DatasetteApp --port=5000

Is this the best pattern here or is there another path that could be supported?

Most helpful comment

It's not obvious how one would build an ASGI app that takes different arguments but can still be launched in this way.

As with WSGI, I think that "passing arguments to the app" is out of scope to the ASGI protocol.

However, I think there's a strong case to make for the uvicorn cli to accept an env file, that it'll read into os.environ

Datasette is traditionally launched using its own CLI tool - for example...

At a first pass I would probably suggest that you keep doing that, then.

Use your own argument parsing, and call uvicorn.run(...).

That'll give you essentially the same thing that you currently have with sanic.

Here's what you won't get if you do that.

  1. It won't give you autoreload on debug=True. Our autoreload relies on restarting a new process, rather than re-importing python modules. It's far simpler, and less subject to corner cases. However it does require that the app isn't imported by the parent process. Using the uvicorn cli, that's easy to ensure, we don't load the import string, except in the child process. Calling uvicorn.run(...) you're already typically in the application module. (We could draw up guidelines on how to use uvicorn.run with an import string, rather than the app reference itself, but there'd still be lots of scope for an import chain that's pulled in bits of code that then won't get reloaded.)
  2. It won't give multiple running processes. We could add this directly into uvicorn, but for now I've pushed it out of scope, as "for multiprocess use a process manager". It's possible that we might want some lightweight process management to end up in core, and have that supported directly when calling into uvicorn.run(), but I'd probably prefer to wait until other stuff is bedded down. (Alternatively we could go one step smaller than that and add in multi-process support, with no process management.)

All 2 comments

It's not obvious how one would build an ASGI app that takes different arguments but can still be launched in this way.

As with WSGI, I think that "passing arguments to the app" is out of scope to the ASGI protocol.

However, I think there's a strong case to make for the uvicorn cli to accept an env file, that it'll read into os.environ

Datasette is traditionally launched using its own CLI tool - for example...

At a first pass I would probably suggest that you keep doing that, then.

Use your own argument parsing, and call uvicorn.run(...).

That'll give you essentially the same thing that you currently have with sanic.

Here's what you won't get if you do that.

  1. It won't give you autoreload on debug=True. Our autoreload relies on restarting a new process, rather than re-importing python modules. It's far simpler, and less subject to corner cases. However it does require that the app isn't imported by the parent process. Using the uvicorn cli, that's easy to ensure, we don't load the import string, except in the child process. Calling uvicorn.run(...) you're already typically in the application module. (We could draw up guidelines on how to use uvicorn.run with an import string, rather than the app reference itself, but there'd still be lots of scope for an import chain that's pulled in bits of code that then won't get reloaded.)
  2. It won't give multiple running processes. We could add this directly into uvicorn, but for now I've pushed it out of scope, as "for multiprocess use a process manager". It's possible that we might want some lightweight process management to end up in core, and have that supported directly when calling into uvicorn.run(), but I'd probably prefer to wait until other stuff is bedded down. (Alternatively we could go one step smaller than that and add in multi-process support, with no process management.)
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ipmb picture ipmb  路  7Comments

tomchristie picture tomchristie  路  6Comments

toonknapen picture toonknapen  路  5Comments

Legionof7 picture Legionof7  路  3Comments

yezyilomo picture yezyilomo  路  5Comments