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. 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, ...}`. 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 ## Troubleshooting advice
All of our linters, including mypy, are designed to only check files 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 # functions using has_request_variables. In reality, REQ returns an
# instance of class _REQ to enable the decorator to scan the parameter # instance of class _REQ to enable the decorator to scan the parameter
# list for _REQ objects and patch the parameters as the true types. # 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 1: converter
@overload @overload
def REQ( 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 sub_validator(var_name, val)
return f return f
# https://zulip.readthedocs.io/en/latest/testing/mypy.html#using-overload-to-accurately-describe-variations
@overload @overload
def check_list(sub_validator: None, length: Optional[int]=None) -> Validator[List[object]]: 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 cast(List[ResultT], val)
return f return f
# https://zulip.readthedocs.io/en/latest/testing/mypy.html#using-overload-to-accurately-describe-variations
@overload @overload
def check_dict(required_keys: Iterable[Tuple[str, Validator[object]]]=[], def check_dict(required_keys: Iterable[Tuple[str, Validator[object]]]=[],
optional_keys: Iterable[Tuple[str, Validator[object]]]=[], optional_keys: Iterable[Tuple[str, Validator[object]]]=[],