Skip to content

Commit

Permalink
Set mypy to disallow_untyped_defs on core.py
Browse files Browse the repository at this point in the history
Enforce that all function and method definitions in webargs.core have
annotations for all arguments and return values.

To make it easier to roll out stricter config, move to a mypy.ini
which disables the untyped-defs check on specific modules.
  • Loading branch information
sirosen committed Jun 14, 2023
1 parent 3752485 commit da79514
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 26 deletions.
39 changes: 39 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[mypy]
ignore_missing_imports = true
warn_unreachable = true
warn_unused_ignores = true
warn_redundant_casts = true
# warn_return_any = true
warn_no_return = true
no_implicit_optional = true
disallow_untyped_defs = true

[mypy-webargs.fields]
disallow_untyped_defs = false

[mypy-webargs.multidictproxy]
disallow_untyped_defs = false

[mypy-webargs.testing]
disallow_untyped_defs = false

[mypy-webargs.aiohttpparser]
disallow_untyped_defs = false

[mypy-webargs.bottleparser]
disallow_untyped_defs = false

[mypy-webargs.djangoparser]
disallow_untyped_defs = false

[mypy-webargs.falconparser]
disallow_untyped_defs = false

[mypy-webargs.flaskparser]
disallow_untyped_defs = false

[mypy-webargs.pyramidparser]
disallow_untyped_defs = false

[mypy-webargs.tornadoparser]
disallow_untyped_defs = false
3 changes: 0 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,3 @@ license_files = LICENSE
max-line-length = 90
max-complexity = 18
extend-ignore = E203,E266

[mypy]
ignore_missing_imports = true
2 changes: 1 addition & 1 deletion src/webargs/asyncparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async def parse(
validate: core.ValidateArg = None,
error_status_code: int | None = None,
error_headers: typing.Mapping[str, str] | None = None,
) -> typing.Mapping | None:
) -> typing.Any:
"""Coroutine variant of `webargs.core.Parser`.
Receives the same arguments as `webargs.core.Parser.parse`.
Expand Down
55 changes: 34 additions & 21 deletions src/webargs/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
Request = typing.TypeVar("Request")
ArgMap = typing.Union[
ma.Schema,
typing.Type[ma.Schema],
typing.Mapping[str, typing.Union[ma.fields.Field, typing.Type[ma.fields.Field]]],
typing.Callable[[Request], ma.Schema],
]
Expand All @@ -37,6 +38,8 @@
T = typing.TypeVar("T")
# type var for callables, to make type-preserving decorators
C = typing.TypeVar("C", bound=typing.Callable)
# type var for multidict proxy classes
MultiDictProxyT = typing.TypeVar("MultiDictProxyT", bound=MultiDictProxy)
# type var for a callable which is an error handler
# used to ensure that the error_handler decorator is type preserving
ErrorHandlerT = typing.TypeVar("ErrorHandlerT", bound=ErrorHandler)
Expand All @@ -51,7 +54,7 @@
DEFAULT_VALIDATION_STATUS: int = 422


def _iscallable(x) -> bool:
def _iscallable(x: typing.Any) -> bool:
# workaround for
# https://github.com/python/mypy/issues/9778
return callable(x)
Expand Down Expand Up @@ -177,13 +180,19 @@ def __init__(
unknown: str | None = _UNKNOWN_DEFAULT_PARAM,
error_handler: ErrorHandler | None = None,
schema_class: type[ma.Schema] | None = None,
):
) -> None:
self.location = location or self.DEFAULT_LOCATION
self.error_callback: ErrorHandler | None = _callable_or_raise(error_handler)
self.schema_class = schema_class or self.DEFAULT_SCHEMA_CLASS
self.unknown = unknown

def _makeproxy(self, multidict, schema: ma.Schema, cls: type = MultiDictProxy):
def _makeproxy(
self,
multidict: typing.Any,
schema: ma.Schema,
*,
cls: type[MultiDictProxyT] | type[MultiDictProxy] = MultiDictProxy,
) -> MultiDictProxyT | MultiDictProxy:
"""Create a multidict proxy object with options from the current parser"""
return cls(multidict, schema, known_multi_fields=tuple(self.KNOWN_MULTI_FIELDS))

Expand Down Expand Up @@ -214,7 +223,9 @@ def _load_location_data(
loader_func = self._get_loader(location)
return loader_func(req, schema)

async def _async_load_location_data(self, schema, req, location):
async def _async_load_location_data(
self, schema: ma.Schema, req: Request, location: str
) -> typing.Any:
# an async variant of the _load_location_data method
# the loader function itself may or may not be async
loader_func = self._get_loader(location)
Expand Down Expand Up @@ -340,7 +351,7 @@ def _process_location_data(
location: str,
unknown: str | None,
validators: CallableList,
):
) -> typing.Any:
# after the data has been fetched from a registered location,
# this is how it is processed
# (shared between sync and async variants)
Expand Down Expand Up @@ -379,7 +390,7 @@ def parse(
validate: ValidateArg = None,
error_status_code: int | None = None,
error_headers: typing.Mapping[str, str] | None = None,
):
) -> typing.Any:
"""Main request parsing method.
:param argmap: Either a `marshmallow.Schema`, a `dict`
Expand Down Expand Up @@ -438,7 +449,7 @@ async def async_parse(
validate: ValidateArg = None,
error_status_code: int | None = None,
error_headers: typing.Mapping[str, str] | None = None,
) -> typing.Mapping | None:
) -> typing.Any:
"""Coroutine variant of `webargs.core.Parser.parse`.
Receives the same arguments as `webargs.core.Parser.parse`.
Expand Down Expand Up @@ -496,10 +507,10 @@ def get_request_from_view_args(
def _update_args_kwargs(
args: tuple,
kwargs: dict[str, typing.Any],
parsed_args: tuple,
parsed_args: dict[str, typing.Any],
as_kwargs: bool,
arg_name: str | None,
) -> tuple[tuple, typing.Mapping]:
) -> tuple[tuple, dict[str, typing.Any]]:
"""Update args or kwargs with parsed_args depending on as_kwargs"""
if as_kwargs:
# expand parsed_args into kwargs
Expand Down Expand Up @@ -573,7 +584,9 @@ def decorator(func: typing.Callable) -> typing.Callable:
if asyncio.iscoroutinefunction(func):

@functools.wraps(func)
async def wrapper(*args, **kwargs):
async def wrapper(
*args: typing.Any, **kwargs: typing.Any
) -> typing.Any:
req_obj = req_

if not req_obj:
Expand All @@ -595,8 +608,8 @@ async def wrapper(*args, **kwargs):

else:

@functools.wraps(func) # type: ignore
def wrapper(*args, **kwargs):
@functools.wraps(func)
def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
req_obj = req_

if not req_obj:
Expand Down Expand Up @@ -729,8 +742,8 @@ def _handle_invalid_json_error(
self,
error: json.JSONDecodeError | UnicodeDecodeError,
req: Request,
*args,
**kwargs,
*args: typing.Any,
**kwargs: typing.Any,
) -> typing.NoReturn:
"""Internal hook for overriding treatment of JSONDecodeErrors.
Expand Down Expand Up @@ -759,7 +772,7 @@ def load_json(self, req: Request, schema: ma.Schema) -> typing.Any:
except UnicodeDecodeError as exc:
return self._handle_invalid_json_error(exc, req)

def load_json_or_form(self, req: Request, schema: ma.Schema):
def load_json_or_form(self, req: Request, schema: ma.Schema) -> typing.Any:
"""Load data from a request, accepting either JSON or form-encoded
data.
Expand All @@ -773,7 +786,7 @@ def load_json_or_form(self, req: Request, schema: ma.Schema):

# Abstract Methods

def _raw_load_json(self, req: Request):
def _raw_load_json(self, req: Request) -> typing.Any:
"""Internal hook method for implementing load_json()
Get a request body for feeding in to `load_json`, and parse it either
Expand All @@ -789,29 +802,29 @@ def _raw_load_json(self, req: Request):
"""
return missing

def load_querystring(self, req: Request, schema: ma.Schema):
def load_querystring(self, req: Request, schema: ma.Schema) -> typing.Any:
"""Load the query string of a request object or return `missing` if no
value can be found.
"""
return missing

def load_form(self, req: Request, schema: ma.Schema):
def load_form(self, req: Request, schema: ma.Schema) -> typing.Any:
"""Load the form data of a request object or return `missing` if no
value can be found.
"""
return missing

def load_headers(self, req: Request, schema: ma.Schema):
def load_headers(self, req: Request, schema: ma.Schema) -> typing.Any:
"""Load the headers or return `missing` if no value can be found."""
return missing

def load_cookies(self, req: Request, schema: ma.Schema):
def load_cookies(self, req: Request, schema: ma.Schema) -> typing.Any:
"""Load the cookies from the request or return `missing` if no value
can be found.
"""
return missing

def load_files(self, req: Request, schema: ma.Schema):
def load_files(self, req: Request, schema: ma.Schema) -> typing.Any:
"""Load files from the request or return `missing` if no values can be
found.
"""
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ commands = pre-commit run --all-files
[testenv:mypy]
deps = mypy==1.3.0
extras = frameworks
commands = mypy src/
commands = mypy src/ {posargs}

[testenv:docs]
extras = docs
Expand Down

0 comments on commit da79514

Please sign in to comment.