django-stubs
django-stubs copied to clipboard
Drop dict and Optional from argument type
I have made things!
I have corrected the type definition for the context argument to django.template.base.Template.render. The argument is no longer typed as optional and does no longer accept a dict value.
Related issues
- Closes #1147
(I meant to click "Comment", not "Approve")
I'd like to make some hints conditional by Django version, if that could be possible... perhaps the mypy plugin could provide the current version?
As far as I know - custom builtins are not supported. https://github.com/python/mypy/issues/12172
We can do something like:
_DjangoVersion4_1 = _DjangoVersion[4, 1]
Inside our plugin we can return Literal[True] for this using get_type_analyze_hook() from https://mypy.readthedocs.io/en/stable/extending_mypy.html
But, this is quite complex!
I was thinking about typing django.VERSION conditionally, so we could use the same syntax as at runtime:
if django.VERSION >= (4 ,1):
... # new stuff
else:
... # old stuff
It seems though that Mypy cannot type narrow on comparisons of literal ints in tuples, like below:
from typing import Literal
from typing_extensions import assert_never
version: tuple[Literal[4], Literal[1]] = (4, 1)
if version >= (4, 1):
reveal_type(version)
else:
assert_never(version)
It doesn't work:
$ mypy example.py
example.py:7: note: Revealed type is "Tuple[Literal[4], Literal[1]]"
example.py:9: note: Revealed type is "Tuple[Literal[4], Literal[1]]"
example.py:10: error: Argument 1 to "assert_never" has incompatible type "Tuple[Literal[4], Literal[1]]"; expected "NoReturn"
Found 1 error in 1 file (checked 1 source file)
It seems this limitation also applies to literal ints in general:
from typing import Literal
from typing_extensions import assert_never
version: Literal[4] = 4
if version >= 4:
reveal_type(version)
else:
reveal_type(version)
assert_never(version)
Perhaps the better approach is as you suggested, some alternative true/false constants. Perhaps we fix to the latest version but override that in the plugin based on the installed version of django that we find, or some config.
sys.version_info check is hard-coded. This is why I thought about Literal[True] / Literal[False] hacks with analyzed dynamic types.
Also found this related issue: https://github.com/python/typing/issues/693
Following on from Adam's example, I did note that == and != with a literal number do work as expected:
from typing import Literal
from typing_extensions import assert_never
version: Literal[4] = 4
if version == 4:
reveal_type(version)
else:
reveal_type(version)
assert_never(version)
if version != 4:
reveal_type(version)
assert_never(version)
else:
reveal_type(version)
if version == 4 or version == 5:
reveal_type(version)
else:
reveal_type(version)
assert_never(version)
This yields the following:
❯ mypy test.py
test.py:7: note: Revealed type is "Literal[4]"
test.py:16: note: Revealed type is "Literal[4]"
test.py:19: note: Revealed type is "Literal[4]"
Success: no issues found in 1 source file
So you could possibly construct something very verbose to achieve this by exposing major and minor versions as independent variables and thrashing out all combinations.
I'm currently looking into whether we can extend mypy to allow for checking additional things as well as sys.version_info when inferring condition values. It looks like it should be relatively straightforward to piggy back off the existing logic and provide a callback... I'll have to have a go.
So you could possibly construct something very verbose to achieve this by exposing major and minor versions as independent variables and thrashing out all combinations.
We could also use a single numbers: 41 for Django 4.1, 42 for Django 4.2, etc.
I'm currently looking into whether we can extend
mypyto allow for checking additional things as well assys.version_infowhen inferring condition values. It looks like it should be relatively straightforward to piggy back off the existing logic and provide a callback... I'll have to have a go.
Sick
Removing this completely means that older versions of django become unsupported.
Note that this is not as bad as it actually looks.
If you get your template via the django.template.loader.get_template() or get_template() method of the template engine, you still get a Template whose render() supports dict and None.
As noted in my other comment:
Oh, wait. There are two
Templateclasses in Django
django.template.base.Template.render()doesn't supportdict, butdjango.template.backends.django.Template.render()is a wrapper class that does supportdictandNone.
django.template.backends.django.DjangoTemplates.get_template()wraps the first Template in the latter wrapper class. Confusing!
We could also use a single numbers: 41 for Django 4.1, 42 for Django 4.2, etc.
Yep! Or a NamedTuple if we really needed this specificity:
from typing import Literal, NamedTuple
from typing_extensions import assert_never
class Version(NamedTuple):
major: Literal[4]
minor: Literal[1]
version = Version(4, 1)
if version.major == 4:
reveal_type(version.major)
else:
reveal_type(version.major)
assert_never(version)
if version.major != 4:
reveal_type(version.major)
assert_never(version)
else:
reveal_type(version.major)
if version.major == 4 or version.major == 5:
reveal_type(version.major)
else:
reveal_type(version.major)
assert_never(version)
I opened an issue about conditional types based on Django version: #1244
Let's merge it right after 1.13.1 release
I haven't followed along completely but if I understand correctly, I could resolve the conflicts and this is still to be merged?
Yes :)
Thanks for the PR and sorry for being so super slow getting it merged