docs: Add documentation on mypy @overload.

We only use this in a few places, but they're really important places
for understanding the types in the codebase, and so it's worth having
a bit of expository documentation explaining how we use it.

(And I expect we'll add more with time).
This commit is contained in:
Tim Abbott
2020-06-21 10:51:35 -07:00
parent 02ea52fc18
commit dc2a045d3b
3 changed files with 42 additions and 1 deletions

View File

@@ -148,6 +148,42 @@ because a list can have many elements, which would make the output too large.
Similarly in dicts, one key's type and the corresponding value's type are printed.
So `{1: 'a', 2: 'b', 3: 'c'}` will be printed as `{int: str, ...}`.
## Using @overload to accurately describe variations
Sometimes, a function's type is most precisely expressed as a few
possibilites, and which possibility can be determined by looking at
the arguments. You can express that idea in a way mypy understands
using `@overload`. For example, `check_list` returns a `Validator`
function that verifies that an object is a list, raising an exception
if it isn't.
It supports being passed a `sub_validator`, which will verify that
each element in the list has a given type as well. One can express
the idea "If `sub_validator` validates that something is a `ResultT`,
`check_list(sub_validator)` validators that something is a
`List[ResultT]` as follows:
~~~ py
@overload
def check_list(sub_validator: None, length: Optional[int]=None) -> Validator[List[object]]:
...
@overload
def check_list(sub_validator: Validator[ResultT],
length: Optional[int]=None) -> Validator[List[ResultT]]:
...
def check_list(sub_validator: Optional[Validator[ResultT]]=None,
length: Optional[int]=None) -> Validator[List[ResultT]]:
~~~
The first overload expresses the types for the case where no
`sub_validator` is passed, in which case all we know is that it
returns a `Validator[List[object]]`; whereas the second defines the
type logic for the case where we are passed a `sub_validator`.
See the [mypy overloading documentation][mypy-overloads] for more details.
[mypy-overloads]: https://mypy.readthedocs.io/en/stable/more_types.html#function-overloading
## Troubleshooting advice
All of our linters, including mypy, are designed to only check files

View File

@@ -137,7 +137,10 @@ class _REQ(Generic[ResultT]):
# functions using has_request_variables. In reality, REQ returns an
# instance of class _REQ to enable the decorator to scan the parameter
# list for _REQ objects and patch the parameters as the true types.
#
# See also this documentation to learn how @overload helps here.
# https://zulip.readthedocs.io/en/latest/testing/mypy.html#using-overload-to-accurately-describe-variations
#
# Overload 1: converter
@overload
def REQ(

View File

@@ -186,6 +186,7 @@ def check_none_or(sub_validator: Validator[ResultT]) -> Validator[ResultT]:
return sub_validator(var_name, val)
return f
# https://zulip.readthedocs.io/en/latest/testing/mypy.html#using-overload-to-accurately-describe-variations
@overload
def check_list(sub_validator: None, length: Optional[int]=None) -> Validator[List[object]]:
...
@@ -220,6 +221,7 @@ def check_list(sub_validator: Optional[Validator[ResultT]]=None, length: Optiona
return cast(List[ResultT], val)
return f
# https://zulip.readthedocs.io/en/latest/testing/mypy.html#using-overload-to-accurately-describe-variations
@overload
def check_dict(required_keys: Iterable[Tuple[str, Validator[object]]]=[],
optional_keys: Iterable[Tuple[str, Validator[object]]]=[],