Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make router and route classes configurable via subclassing #2904

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

Purushot14
Copy link

Summary

This PR allows customization of the router and route classes used by Starlette. It introduces flexibility by enabling users to subclass Starlette and Router to specify custom implementations, enhancing the extensibility of the framework.

Key Changes:

  • Introduced the router_class attribute in the Starlette class to enable specifying custom routers via subclassing.
  • Added route_class and websocket_route_class attributes to the Router class to allow overriding the default route implementations.
  • Updated .gitignore to ignore IDE-specific files (.idea) and local virtual environment directories (.venv).
  • Fixed a minor typo in the Starlette class docstring.

This approach maintains backward compatibility while significantly improving flexibility for developers building on top of Starlette.

Checklist

  • I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.

Motivation

We needed this flexibility to integrate Starlette with custom routing logic tailored for specific performance or organizational requirements, without modifying core library code directly.

Purushot14 and others added 4 commits March 12, 2025 20:58
- Allow `Starlette` to specify a custom router by overriding `router_class`.
- Allow `Router` to specify custom route classes by overriding `route_class` and `websocket_route_class`.
- Improve extensibility for custom implementations.
- Minor typo fix in Starlette class docstring.
- Update .gitignore to exclude IDE (.idea) and virtual environment directories (.venv).
@adriangb
Copy link
Member

If I understand correctly the point is to make @app.get and co. use a custom router class? If so I think there are two issues:

  1. Starlette has moved / is moving in the direction of preferring Starlette(routes=[...]) instead of decorators.
  2. Using a custom router is already possible:
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Mount, Route, Router
from starlette.testclient import TestClient


async def homepage(_request: Request) -> JSONResponse:
    return JSONResponse({'message': 'Hello, world!'})

router = Router(routes=[Route('/home', endpoint=homepage)])

app = Starlette(routes=[Mount('', app=router)])

client = TestClient(app)

resp = client.get('/home')
assert resp.status_code == 200
print(resp.json())  # Prints: {'message': 'Hello, world!'}

Given that this isn't a supper common use case and it's targeted at deprecated APIs I don't think we'd want to add it.

The only argument I see for something like this is that as far as I can tell you can't set the router class on the Starlette object. If we wanted to be able to allow that (and I'm not sure we do, I don't know what the use case would be) I think that should be done by adding a router parameter to Starlette which can be passed instead of (but not alongside) the existing routes parameter.

@Purushot14
Copy link
Author

@adriangb Thanks for the detailed feedback!

The intention behind my PR wasn’t just to customize router usage via decorators (like @app.get) but rather to allow setting a custom router class directly during Starlette initialization, since currently Starlette always initializes its internal router as the default one, even if we pass custom routes or routers.

While your suggestion using Mount works, the core limitation remains: Starlette always internally creates its default router instance at init. This makes it difficult to customize routing behaviour deeply globally (e.g., extending routing functionality, creating Flask Blueprint-like modular/sub-router structures).

My main motivation is to have router modularity, somewhat similar to Flask’s Blueprint feature, but with deeper nesting capabilities. For example, I’d like to structure my routers like:

AnalyticRouter
UserRouter
    ├── GroupRouter
    ├── ApiKeyRouter
    └── ProfileRouter

Currently, using Mount means each sub-router is essentially treated as a separate application, which slightly complicates handling middleware, error handling, or extensions across sub-router boundaries.

Therefore, adding a router parameter to the Starlette constructor (as you suggested, mutually exclusive with routes) would elegantly solve this issue and provide deeper control over router behavior without relying on deprecated decorators.

Would you be open to reconsidering if I adjusted my PR to implement exactly that—a way to directly inject a custom router into Starlette via the constructor?

@adriangb
Copy link
Member

adriangb commented Mar 26, 2025

I'm still not sure I completely understand the use case but I do think it'd be reasonable to add a router_class parameter to the constructor of Starlette. I'd merge that as long as another maintainer gives a +1.

@Kludex
Copy link
Member

Kludex commented Mar 26, 2025

I think this PR came out a couple of times in the last 5 years, or maybe it was discussed... can we get the reference to those? I'd like to see the concerns we had at the time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants