quart icon indicating copy to clipboard operation
quart copied to clipboard

mypy: Blueprint type mismatch with `flask.sansio.blueprints.Blueprint`

Open hrimov opened this issue 10 months ago • 0 comments

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:

  1. Make Flask's sansio layer more generic to support framework extension:
TBlueprint = TypeVar('TBlueprint', bound='Blueprint', covariant=True)
blueprints: Dict[str, TBlueprint]
  1. 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.

hrimov avatar Feb 08 '25 00:02 hrimov