sanic icon indicating copy to clipboard operation
sanic copied to clipboard

RFC: Blueprint pre-registration

Open ahopkins opened this issue 2 years ago • 1 comments

Background

I see a lot of developers getting stuck because of import ordering. This usually happens because of a mistake in trying to import a module with reference to the application before the application has actually been created.

While I think there are some helpful patterns to avoid this (factories, late imports, etc), it might also be helpful to provide a little leeway. This PR is meant to add the ability for a Blueprint to declare its application up front.

Then, as long as that module has been imported somewhere, it will automatically be registered when the application starts up.

What it would look like

# some.other.module
bp = Blueprint("SomeBP")
bp.pre_register("SomeAppName")
# anywhere
import some.other.module

This could be simplified, and made to look similar to the Sanic.get_app pattern. In this example, we call Sanic.lazy, which will return a preregistered Blueprint object.

# some.other.module
from sanic import Sanic

app = Sanic.lazy("SomeApp")


@app.before_server_start
async def before_server_start(app: Sanic, _):
    ...


@app.get("/foo")
async def get(_):
    ...
# server
import some.other.module

app = Sanic("SomeApp")

Taking it further

We also can borrow the pattern from Sanic.get_app() and use the only application instance (if there is only one). This would allow:

lazy_app = Sanic.lazy()

If this is the case, then it would be natural--and highly likely--that someone may attempt the run the application from that lazily created Blueprint. We could allow that by simply creating a basic application instance for them at startup.

$ sanic path.to.server:lazy_app

I am myself still undecided on this last bit. It seems logical and convenient, but I have not fully thought thru the issue.

Potential PR implementation

See PR #2339

ahopkins avatar Dec 14 '21 23:12 ahopkins

Another thought...

When Sanic.lazy is used without a name, the implied blueprint name should use the current module's name:

name = inspect.getmodulename(inspect.stack()[1].filename)

ahopkins avatar Nov 13 '22 09:11 ahopkins