Neoteroi / BlackSheep

Fast ASGI web framework for Python
https://www.neoteroi.dev/blacksheep/
MIT License
1.86k stars 78 forks source link

Add automatic imports of controllers and routes #334

Open RobertoPrevato opened 1 year ago

RobertoPrevato commented 1 year ago

Explicit is better than implicit. However, having to import controllers and routes explicitly is boring and annoying!

Consider the following example:

β”œβ”€β”€ app πŸ“ 
β”‚Β Β  β”œβ”€β”€ controllers πŸ“ 
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ __init__.py
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ comments.py
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ favorites.py
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ home.py
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ reactions.py
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ reviews.py
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ topics.py
β”‚Β Β  β”‚Β Β  └── users.py
β”‚Β Β  β”œβ”€β”€ __init__.py
β”‚Β Β  β”œβ”€β”€ program.py <-- the app is instantiated here

The user currently needs to import Controller types defined in each module inside the controllers package explicitly. Example:

# app/controllers/__init__.py

from .home import HomeController as HomeController
from .comments import CommentsController as CommentsController
from .favorites import FavoritesController as FavoritesController
from .reactions import ReactionsController as ReactionsController
from .reviews import ReviewsController as ReviewsController
from .topics import TopicsController as TopicsController
from .users import UsersController as UsersController

Note: as added to make linters happy and remove unused imports lints.

from . import controllers  # NoQA

...

This has downsides:

In this case, adopting convention over configuration can help improving the situation:

It would be nice to support the same for regular routes defined using request handlers. But to achieve this, it is also necessary to support importing get, post, put, etc. methods to register request handlers in a similar fashion as already supported for controllers.

Therefore the blacksheep.server.routing module should export a default router that is used by default, when no router is explicitly configured for the application.

In the final form, routes would also be automatically imported:

.
β”œβ”€β”€ main.py
└── routes  ⭐
    β”œβ”€β”€ example.py
    β”œβ”€β”€ home.py
    └── __init__.py
# routes/home.py

from blacksheep import get

@get("/")
async def home():
    return "Hello, World!"

In such scenario, routes would be automatically imported when instantiating an Application class that uses the default router.

# main.py
from blacksheep import Application

app = Application()

Users who want to maintain things explicit has still the option of doing so: