Skip to content

Commit

Permalink
Merge pull request #848 from sirosen/begin-disallow-untyped-defs
Browse files Browse the repository at this point in the history
Begin disallowing untyped defs, starting with `webargs.core`
  • Loading branch information
sirosen committed Jul 10, 2023
2 parents 6c9f9b1 + b4f7e0b commit 91a15d0
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 @@ -59,7 +62,7 @@ def _record_arg_name(f: typing.Callable[..., typing.Any], argname: str | None) -
f.__webargs_argnames__ += (argname,) # type: ignore[attr-defined]


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 @@ -185,13 +188,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 @@ -222,7 +231,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 @@ -348,7 +359,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 @@ -387,7 +398,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 @@ -446,7 +457,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 @@ -504,10 +515,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 @@ -592,7 +603,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 @@ -614,8 +627,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 @@ -749,8 +762,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 @@ -779,7 +792,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 @@ -793,7 +806,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 @@ -809,29 +822,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 91a15d0

Please sign in to comment.