Skip to content

Commit

Permalink
address mypy strict findings
Browse files Browse the repository at this point in the history
  • Loading branch information
davidism committed Jan 16, 2024
1 parent 5a48a0f commit 6000e80
Show file tree
Hide file tree
Showing 24 changed files with 346 additions and 234 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Expand Up @@ -8,6 +8,7 @@ Unreleased
- Session data is untagged without relying on the built-in ``json.loads``
``object_hook``. This allows other JSON providers that don't implement that.
:issue:`5381`
- Address more type findings when using mypy strict mode. :pr:`5383`


Version 3.0.0
Expand Down
16 changes: 1 addition & 15 deletions pyproject.toml
Expand Up @@ -82,21 +82,7 @@ python_version = "3.8"
files = ["src/flask", "tests/typing"]
show_error_codes = true
pretty = true
#strict = true
allow_redefinition = true
disallow_subclassing_any = true
#disallow_untyped_calls = true
#disallow_untyped_defs = true
#disallow_incomplete_defs = true
no_implicit_optional = true
local_partial_types = true
#no_implicit_reexport = true
strict_equality = true
warn_redundant_casts = true
warn_unused_configs = true
warn_unused_ignores = true
#warn_return_any = true
#warn_unreachable = true
strict = true

[[tool.mypy.overrides]]
module = [
Expand Down
2 changes: 2 additions & 0 deletions requirements/typing.in
@@ -1,4 +1,6 @@
mypy
types-contextvars
types-dataclasses
asgiref
cryptography
python-dotenv
10 changes: 7 additions & 3 deletions requirements/typing.txt
Expand Up @@ -4,19 +4,23 @@
#
# pip-compile typing.in
#
asgiref==3.7.2
# via -r typing.in
cffi==1.16.0
# via cryptography
cryptography==41.0.5
cryptography==41.0.7
# via -r typing.in
mypy==1.6.1
mypy==1.8.0
# via -r typing.in
mypy-extensions==1.0.0
# via mypy
pycparser==2.21
# via cffi
python-dotenv==1.0.0
# via -r typing.in
types-contextvars==2.4.7.3
# via -r typing.in
types-dataclasses==0.6.6
# via -r typing.in
typing-extensions==4.8.0
typing-extensions==4.9.0
# via mypy
45 changes: 26 additions & 19 deletions src/flask/app.py
@@ -1,10 +1,10 @@
from __future__ import annotations

import collections.abc as cabc
import os
import sys
import typing as t
import weakref
from collections.abc import Iterator as _abc_Iterator
from datetime import timedelta
from inspect import iscoroutinefunction
from itertools import chain
Expand Down Expand Up @@ -54,6 +54,9 @@
from .wrappers import Response

if t.TYPE_CHECKING: # pragma: no cover
from _typeshed.wsgi import StartResponse
from _typeshed.wsgi import WSGIEnvironment

from .testing import FlaskClient
from .testing import FlaskCliRunner

Expand Down Expand Up @@ -200,11 +203,11 @@ class Flask(App):

#: The class that is used for request objects. See :class:`~flask.Request`
#: for more information.
request_class = Request
request_class: type[Request] = Request

#: The class that is used for response objects. See
#: :class:`~flask.Response` for more information.
response_class = Response
response_class: type[Response] = Response

#: the session interface to use. By default an instance of
#: :class:`~flask.sessions.SecureCookieSessionInterface` is used here.
Expand All @@ -216,11 +219,11 @@ def __init__(
self,
import_name: str,
static_url_path: str | None = None,
static_folder: str | os.PathLike | None = "static",
static_folder: str | os.PathLike[str] | None = "static",
static_host: str | None = None,
host_matching: bool = False,
subdomain_matching: bool = False,
template_folder: str | os.PathLike | None = "templates",
template_folder: str | os.PathLike[str] | None = "templates",
instance_path: str | None = None,
instance_relative_config: bool = False,
root_path: str | None = None,
Expand Down Expand Up @@ -282,7 +285,7 @@ def get_send_file_max_age(self, filename: str | None) -> int | None:
if isinstance(value, timedelta):
return int(value.total_seconds())

return value
return value # type: ignore[no-any-return]

def send_static_file(self, filename: str) -> Response:
"""The view function used to serve files from
Expand Down Expand Up @@ -447,13 +450,13 @@ def raise_routing_exception(self, request: Request) -> t.NoReturn:
or request.routing_exception.code in {307, 308}
or request.method in {"GET", "HEAD", "OPTIONS"}
):
raise request.routing_exception # type: ignore
raise request.routing_exception # type: ignore[misc]

from .debughelpers import FormDataRoutingRedirect

raise FormDataRoutingRedirect(request)

def update_template_context(self, context: dict) -> None:
def update_template_context(self, context: dict[str, t.Any]) -> None:
"""Update the template context with some commonly used variables.
This injects request, session, config and g into the template
context as well as everything template context processors want
Expand Down Expand Up @@ -481,7 +484,7 @@ def update_template_context(self, context: dict) -> None:

context.update(orig_ctx)

def make_shell_context(self) -> dict:
def make_shell_context(self) -> dict[str, t.Any]:
"""Returns the shell context for an interactive shell for this
application. This runs all the registered shell context
processors.
Expand Down Expand Up @@ -724,7 +727,7 @@ def handle_http_exception(
handler = self._find_error_handler(e, request.blueprints)
if handler is None:
return e
return self.ensure_sync(handler)(e)
return self.ensure_sync(handler)(e) # type: ignore[no-any-return]

def handle_user_exception(
self, e: Exception
Expand Down Expand Up @@ -756,7 +759,7 @@ def handle_user_exception(
if handler is None:
raise

return self.ensure_sync(handler)(e)
return self.ensure_sync(handler)(e) # type: ignore[no-any-return]

def handle_exception(self, e: Exception) -> Response:
"""Handle an exception that did not have an error handler
Expand Down Expand Up @@ -849,7 +852,7 @@ def dispatch_request(self) -> ft.ResponseReturnValue:
return self.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment]
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]

def full_dispatch_request(self) -> Response:
"""Dispatches the request and on top of that performs request
Expand Down Expand Up @@ -913,7 +916,7 @@ def make_default_options_response(self) -> Response:
rv.allow.update(methods)
return rv

def ensure_sync(self, func: t.Callable) -> t.Callable:
def ensure_sync(self, func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
"""Ensure that the function is synchronous for WSGI workers.
Plain ``def`` functions are returned as-is. ``async def``
functions are wrapped to run and wait for the response.
Expand All @@ -928,7 +931,7 @@ def ensure_sync(self, func: t.Callable) -> t.Callable:
return func

def async_to_sync(
self, func: t.Callable[..., t.Coroutine]
self, func: t.Callable[..., t.Coroutine[t.Any, t.Any, t.Any]]
) -> t.Callable[..., t.Any]:
"""Return a sync function that will run the coroutine function.
Expand Down Expand Up @@ -1166,7 +1169,7 @@ def make_response(self, rv: ft.ResponseReturnValue) -> Response:

# make sure the body is an instance of the response class
if not isinstance(rv, self.response_class):
if isinstance(rv, (str, bytes, bytearray)) or isinstance(rv, _abc_Iterator):
if isinstance(rv, (str, bytes, bytearray)) or isinstance(rv, cabc.Iterator):
# let the response class set the status and headers instead of
# waiting to do it manually, so that the class can handle any
# special logic
Expand Down Expand Up @@ -1240,7 +1243,7 @@ def preprocess_request(self) -> ft.ResponseReturnValue | None:
rv = self.ensure_sync(before_func)()

if rv is not None:
return rv
return rv # type: ignore[no-any-return]

return None

Expand Down Expand Up @@ -1353,7 +1356,7 @@ def app_context(self) -> AppContext:
"""
return AppContext(self)

def request_context(self, environ: dict) -> RequestContext:
def request_context(self, environ: WSGIEnvironment) -> RequestContext:
"""Create a :class:`~flask.ctx.RequestContext` representing a
WSGI environment. Use a ``with`` block to push the context,
which will make :data:`request` point at this request.
Expand Down Expand Up @@ -1425,7 +1428,9 @@ def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> RequestContext:
finally:
builder.close()

def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
def wsgi_app(
self, environ: WSGIEnvironment, start_response: StartResponse
) -> cabc.Iterable[bytes]:
"""The actual WSGI application. This is not implemented in
:meth:`__call__` so that middlewares can be applied without
losing a reference to the app object. Instead of doing this::
Expand Down Expand Up @@ -1473,7 +1478,9 @@ def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:

ctx.pop(error)

def __call__(self, environ: dict, start_response: t.Callable) -> t.Any:
def __call__(
self, environ: WSGIEnvironment, start_response: StartResponse
) -> cabc.Iterable[bytes]:
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app`, which can be
wrapped to apply middleware.
Expand Down
2 changes: 1 addition & 1 deletion src/flask/blueprints.py
Expand Up @@ -39,7 +39,7 @@ def get_send_file_max_age(self, filename: str | None) -> int | None:
if isinstance(value, timedelta):
return int(value.total_seconds())

return value
return value # type: ignore[no-any-return]

def send_static_file(self, filename: str) -> Response:
"""The view function used to serve files from
Expand Down

0 comments on commit 6000e80

Please sign in to comment.