Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Changelog
Unreleased
----------

- Type ``CachedResponse.__init__`` so it doesn't trigger mypy ``[no-untyped-call]``
in strict downstream codebases. :issue:`628`
- Docs: clarify that ``@memoize`` uses ``repr(obj)``
(or ``__caching_id__``) for the ``self``/``cls`` identity, not Python's
:func:`id`. :issue:`555`
Expand Down
8 changes: 6 additions & 2 deletions src/flask_caching/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
37 changes: 37 additions & 0 deletions tests/test_init.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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"
Loading