Description
When using Quart's Blueprint, mypy doesn't recognize its inheritance from flask.sansio.blueprints.Blueprint
. This causes type errors when accessing blueprints through app.blueprints
, which returns the Flask Blueprint type.
Environment
- Python version: 3.12.7
- Quart version: 0.20.0
Minimal Reproducible Example
from quart import Quart
from quart.blueprints import Blueprint
app = Quart(__name__)
bp = Blueprint("test", __name__)
app.register_blueprint(bp)
# This works at runtime but fails type check
def process_blueprint(blueprint: Blueprint) -> None:
print(f"Processing blueprint: {blueprint.name}")
# Type error here - blueprints.values() returns flask.sansio.blueprints.Blueprint
for blueprint in app.blueprints.values():
process_blueprint(blueprint) # Error: Expected quart.blueprints.Blueprint, got flask.sansio.blueprints.Blueprint
# Show inferred types
reveal_type(bp) # Shows quart.blueprints.Blueprint
reveal_type(app.blueprints) # Shows Dict[str, flask.sansio.blueprints.Blueprint]
Mypy output:
mypy quart_blueprint_mre.py --show-error-codes --strict
quart_blueprint_mre.py:19: error: Argument 1 to "process_blueprint" has incompatible type "flask.sansio.blueprints.Blueprint"; expected "quart.blueprints.Blueprint" [arg-type]
quart_blueprint_mre.py:22: note: Revealed type is "quart.blueprints.Blueprint"
quart_blueprint_mre.py:23: note: Revealed type is "builtins.dict[builtins.str, flask.sansio.blueprints.Blueprint]"
Found 1 error in 1 file (checked 1 source file)
In my real codebase, I have something like this:
from typing import TypeAlias
from quart import Quart
from quart.blueprints import Blueprint
Scaffold: TypeAlias = Blueprint | Quart
def _inject_routes(app: Scaffold) -> None:
for endpoint, func in app.view_functions.items():
if not injected(func):
wrapped = _make_wrapper(func)
app.view_functions[endpoint] = wrapped
It can be fixed, of course, with suppression or with adding flask.sansio.blueprints.Blueprint
to the type alias, but I guess it's not the best solution.
As I found out, that's because of the more complex hierarchy of the App/Blueprint classes for Quart. So I'm wondering what would be the best way to handle this inheritance in the type system:
- Make Flask's sansio layer more generic to support framework extension:
TBlueprint = TypeVar('TBlueprint', bound='Blueprint', covariant=True)
blueprints: Dict[str, TBlueprint]
- Or add type casting on Quart's side:
@property
def blueprints(self) -> Dict[str, Blueprint]:
return cast(Dict[str, Blueprint], super().blueprints)
I'd be happy to submit a PR with either approach once you suggest me which is more appropriate for the Quart/Flask ecosystem.