From 69719096bda56661a3e64cf301216e2fda6bcf80 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Braun Date: Fri, 22 May 2026 12:19:33 +0200 Subject: [PATCH 1/2] fix: type CachedResponse.__init__ so mypy --strict accepts it (#628) Without ``-> None`` and parameter annotations, calling ``CachedResponse(response, timeout)`` in a mypy --strict codebase raises ``[no-untyped-call]``. Annotate the constructor and document the in-place ``__dict__`` swap that adopts an existing Response. --- CHANGES.rst | 7 +++++++ src/flask_caching/__init__.py | 8 ++++++-- tests/test_init.py | 37 +++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index a832c22e..f439bcd3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,13 @@ Changelog ========= +Unreleased +---------- + +- Type ``CachedResponse.__init__`` so it doesn't trigger mypy ``[no-untyped-call]`` + in strict downstream codebases. :issue:`628` + + Version 2.4.0 ------------- diff --git a/src/flask_caching/__init__.py b/src/flask_caching/__init__.py index 77bd7576..c2657412 100644 --- a/src/flask_caching/__init__.py +++ b/src/flask_caching/__init__.py @@ -57,9 +57,13 @@ class CachedResponse(Response): to override the cache TTL dynamically """ - timeout = None + timeout: int | None = None - def __init__(self, response, timeout): + def __init__(self, response: Response, timeout: int | None) -> None: + # ``CachedResponse`` adopts the state of an existing Response in + # place rather than calling ``Response.__init__``; copying + # ``__dict__`` preserves headers, status and body without having + # to round-trip the response through Werkzeug's constructor. self.__dict__ = response.__dict__ self.timeout = timeout diff --git a/tests/test_init.py b/tests/test_init.py index f1b2d900..2416d9f7 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,9 +1,12 @@ import importlib.util +import inspect import pytest from flask import Flask +from flask import make_response from flask_caching import Cache +from flask_caching import CachedResponse from flask_caching.backends import FileSystemCache from flask_caching.backends import MemcachedCache from flask_caching.backends import NullCache @@ -61,3 +64,37 @@ def test_init_nullcache(cache_type, app, tmp_path): cache = Cache(app=app) assert isinstance(app.extensions["cache"][cache], cache_type) + + +def test_cached_response_init_is_typed(): + """Regression test for issue #628. + + ``CachedResponse.__init__`` must declare a return type and parameter + types so that mypy ``--strict`` doesn't flag construction in typed + code with ``[no-untyped-call]``. See + https://github.com/pallets-eco/flask-caching/issues/628. + """ + sig = inspect.signature(CachedResponse.__init__) + # __init__ should declare ``-> None`` rather than be untyped. + # inspect.signature returns the literal ``None`` for ``-> None``. + assert sig.return_annotation is None, ( + f"CachedResponse.__init__ return annotation is {sig.return_annotation!r}; " + "expected None" + ) + # All non-self parameters should carry an annotation. + for name, param in sig.parameters.items(): + if name == "self": + continue + assert param.annotation is not inspect.Parameter.empty, ( + f"CachedResponse.__init__ parameter {name!r} has no annotation" + ) + + +def test_cached_response_construction_with_flask_response(): + """Sanity check that CachedResponse still wraps a flask Response.""" + app = Flask(__name__) + with app.test_request_context(): + resp = make_response("hi") + cached = CachedResponse(resp, 10) + assert cached.timeout == 10 + assert cached.get_data(as_text=True) == "hi" From 80b8a7145cee81a8b031160c7115aa8e123589cf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 10:26:48 +0000 Subject: [PATCH 2/2] [pre-commit.ci lite] apply automatic fixes --- tests/test_init.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 2416d9f7..980ea9a2 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -85,9 +85,9 @@ def test_cached_response_init_is_typed(): for name, param in sig.parameters.items(): if name == "self": continue - assert param.annotation is not inspect.Parameter.empty, ( - f"CachedResponse.__init__ parameter {name!r} has no annotation" - ) + assert ( + param.annotation is not inspect.Parameter.empty + ), f"CachedResponse.__init__ parameter {name!r} has no annotation" def test_cached_response_construction_with_flask_response():