Skip to content

Preserve types of functions decorated with jwt_required and loaders - #581

Merged
vimalloc merged 2 commits into
vimalloc:mainfrom
guidoiaquinti:main
Oct 5, 2026
Merged

vimalloc merged 2 commits into
vimalloc:mainfrom
guidoiaquinti:main

Conversation

@guidoiaquinti

Copy link
Copy Markdown
Contributor

Description

jwt_required() is annotated -> Any, and the JWTManager loader decorators (user_identity_loader, user_lookup_loader, token_in_blocklist_loader, …) are annotated (callback: Callable) -> Callable. Because the package ships py.typed, type checkers trust these annotations, so every protected view and every registered loader loses its signature: parameters and return types are no longer checked, and the loss carries over to callers, tests that call views directly, and decorators stacked on top.

Changes

  • jwt_required() now returns Callable[[_F], _F], where _F = TypeVar("_F", bound=Callable[..., Any]). The inner wrapper is annotated, and a cast is needed because functools.wraps doesn't carry the type over.
  • All 15 JWTManager.*_loader decorators are now typed (callback: _F) -> _F. They already return the callback unchanged, so no cast is needed there.
  • The TypeVar is private (_F), so it doesn't add to the public API.
  • New tests/test_typing.py: assert_type checks run by the existing mypy tox env. With the old annotations, mypy fails on this test, so a regression to Any would be caught in CI.

Compatibility

  • No runtime change. Only annotations changed, plus one cast, which does nothing at runtime. The full test suite passes unchanged.
  • Nothing that type-checks today is rejected. The TypeVar bound is as loose as the old bare Callable. Checked with mypy --strict and pyright against sync and async views, every jwt_required keyword argument, MethodView.decorators, decorated view methods, jwt_required()(lambda: ...), and loaders registered with lambdas, functools.partial and untyped functions. Neither checker reported an error from the library.
  • Downstream projects that type-check may see new errors after upgrading. Every one is a real bug that Any was hiding, for example calling a view with the wrong argument types, a loader with the wrong signature, or @jwt_required written without parentheses.
  • async def views keep their declared coroutine type, the same convention Flask's own decorators follow. (At runtime ensure_sync makes the wrapper synchronous. A more precise ParamSpec/overload typing was considered but rejected: it would change the inferred type of every async view and add new errors for currently valid code.)

Testing

  • mypy flask_jwt_extended/ tests/: no issues
  • pytest tests/: 181 passed, 1 skipped
  • flake8 and black clean on the changed files

Comment thread tests/test_typing.py
@vimalloc

vimalloc commented Oct 2, 2026

Copy link
Copy Markdown
Owner

It looks like there was an issue on a couple of the CI tasks. Let me know if you need any help getting those resolved!

I also left one comment, if you don't mind giving that a look.

Thank you for the PR!

@guidoiaquinti

Copy link
Copy Markdown
Contributor Author

It looks like there was an issue on a couple of the CI tasks. Let me know if you need any help getting those resolved!

I also left one comment, if you don't mind giving that a look.

Thank you for the PR!

I believe I've addressed the comments + extended the test suite to cover for more cases.

Note: I'll follow up with another PR as the black version of .pre-commit-config.yaml drifted from the one defined in requirements.txt

@vimalloc

vimalloc commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Note: I'll follow up with another PR as the black version of .pre-commit-config.yaml drifted from the one defined in requirements.txt

Ah sorry about that. I remember getting that notice, then completely forgetting to do anything about it.

@vimalloc
vimalloc merged commit 3fd0934 into vimalloc:main Oct 5, 2026
14 checks passed
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.

2 participants