Skip to content

Commit e3c131b

Browse files
authored
Add type hints to all test code (#1217)
* Add type hints to all test code * Fixes * Fix indentation * Review fixes
1 parent 9b4162b commit e3c131b

File tree

16 files changed

+72
-53
lines changed

16 files changed

+72
-53
lines changed

tests/test_error_handling.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import tempfile
2-
import typing
32
import uuid
43
from contextlib import contextmanager
4+
from typing import Any, Generator, List, Optional
55

66
import pytest
77

@@ -27,7 +27,7 @@
2727

2828

2929
@contextmanager
30-
def write_to_file(file_contents: str, suffix: typing.Optional[str] = None) -> typing.Generator[str, None, None]:
30+
def write_to_file(file_contents: str, suffix: Optional[str] = None) -> Generator[str, None, None]:
3131
with tempfile.NamedTemporaryFile(mode="w+", suffix=suffix) as config_file:
3232
config_file.write(file_contents)
3333
config_file.seek(0)
@@ -54,8 +54,7 @@ def write_to_file(file_contents: str, suffix: typing.Optional[str] = None) -> ty
5454
),
5555
],
5656
)
57-
def test_misconfiguration_handling(capsys, config_file_contents, message_part):
58-
# type: (typing.Any, typing.List[str], str) -> None
57+
def test_misconfiguration_handling(capsys: Any, config_file_contents: List[str], message_part: str) -> None:
5958
"""Invalid configuration raises `SystemExit` with a precise error message."""
6059
contents = "\n".join(config_file_contents).expandtabs(4)
6160
with write_to_file(contents) as filename:
@@ -74,7 +73,7 @@ def test_misconfiguration_handling(capsys, config_file_contents, message_part):
7473
pytest.param(None, id="as none"),
7574
],
7675
)
77-
def test_handles_filename(capsys, filename: str):
76+
def test_handles_filename(capsys: Any, filename: str) -> None:
7877
with pytest.raises(SystemExit, match="2"):
7978
DjangoPluginConfig(filename)
8079

@@ -116,7 +115,7 @@ def test_handles_filename(capsys, filename: str):
116115
),
117116
],
118117
)
119-
def test_toml_misconfiguration_handling(capsys, config_file_contents, message_part):
118+
def test_toml_misconfiguration_handling(capsys: Any, config_file_contents, message_part) -> None:
120119
with write_to_file(config_file_contents, suffix=".toml") as filename:
121120
with pytest.raises(SystemExit, match="2"):
122121
DjangoPluginConfig(filename)

tests/typecheck/contrib/admin/test_options.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
# this will fail if `model` has a type other than the generic specified in the class declaration
8080
model = TestModel
8181
82-
def a_method_action(self, request, queryset):
82+
def a_method_action(self, request: HttpRequest, queryset: QuerySet) -> None:
8383
pass
8484
8585
# This test is here to make sure we're not running into a mypy issue which is
Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
- case: login_required_bare
22
main: |
3+
from typing import Any
34
from django.contrib.auth.decorators import login_required
5+
from django.http import HttpRequest, HttpResponse
46
@login_required
5-
def view_func(request): ...
6-
reveal_type(view_func) # N: Revealed type is "def (request: Any) -> Any"
7+
def view_func(request: HttpRequest) -> HttpResponse: ...
8+
reveal_type(view_func) # N: Revealed type is "def (request: django.http.request.HttpRequest) -> django.http.response.HttpResponse"
79
- case: login_required_fancy
810
main: |
911
from django.contrib.auth.decorators import login_required
@@ -15,29 +17,33 @@
1517
- case: login_required_weird
1618
main: |
1719
from django.contrib.auth.decorators import login_required
20+
from django.http import HttpRequest, HttpResponse
1821
# This is non-conventional usage, but covered in Django tests, so we allow it.
19-
def view_func(request): ...
22+
def view_func(request: HttpRequest) -> HttpResponse: ...
2023
wrapped_view = login_required(view_func, redirect_field_name='a', login_url='b')
21-
reveal_type(wrapped_view) # N: Revealed type is "def (request: Any) -> Any"
24+
reveal_type(wrapped_view) # N: Revealed type is "def (request: django.http.request.HttpRequest) -> django.http.response.HttpResponse"
2225
- case: login_required_incorrect_return
2326
main: |
27+
from typing import Any
2428
from django.contrib.auth.decorators import login_required
2529
@login_required() # E: Value of type variable "_VIEW" of function cannot be "Callable[[Any], str]"
26-
def view_func2(request) -> str: ...
30+
def view_func2(request: Any) -> str: ...
2731
- case: user_passes_test
2832
main: |
2933
from django.contrib.auth.decorators import user_passes_test
34+
from django.http import HttpRequest, HttpResponse
3035
@user_passes_test(lambda u: u.get_username().startswith('super'))
31-
def view_func(request): ...
32-
reveal_type(view_func) # N: Revealed type is "def (request: Any) -> Any"
36+
def view_func(request: HttpRequest) -> HttpResponse: ...
37+
reveal_type(view_func) # N: Revealed type is "def (request: django.http.request.HttpRequest) -> django.http.response.HttpResponse"
3338
- case: user_passes_test_bare_is_error
3439
main: |
35-
from django.http.response import HttpResponse
40+
from django.http import HttpRequest, HttpResponse
3641
from django.contrib.auth.decorators import user_passes_test
37-
@user_passes_test # E: Argument 1 to "user_passes_test" has incompatible type "Callable[[Any], HttpResponse]"; expected "Callable[[Union[AbstractBaseUser, AnonymousUser]], bool]"
38-
def view_func(request) -> HttpResponse: ...
42+
@user_passes_test # E: Argument 1 to "user_passes_test" has incompatible type "Callable[[HttpRequest], HttpResponse]"; expected "Callable[[Union[AbstractBaseUser, AnonymousUser]], bool]"
43+
def view_func(request: HttpRequest) -> HttpResponse: ...
3944
- case: permission_required
4045
main: |
4146
from django.contrib.auth.decorators import permission_required
47+
from django.http import HttpRequest, HttpResponse
4248
@permission_required('polls.can_vote')
43-
def view_func(request): ...
49+
def view_func(request: HttpRequest) -> HttpResponse: ...

tests/typecheck/db/test_transaction.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
reveal_type(func) # N: Revealed type is "def (x: builtins.int) -> builtins.list[Any]"
1313
- case: non_atomic_requests_bare
1414
main: |
15+
from typing import Any
1516
from django.db.transaction import non_atomic_requests
17+
from django.http import HttpRequest, HttpResponse
1618
@non_atomic_requests
17-
def view_func(request): ...
18-
reveal_type(view_func) # N: Revealed type is "def (request: Any) -> Any"
19+
def view_func(request: HttpRequest) -> HttpResponse: ...
20+
reveal_type(view_func) # N: Revealed type is "def (request: django.http.request.HttpRequest) -> django.http.response.HttpResponse"
1921
2022
- case: non_atomic_requests_args
2123
main: |

tests/typecheck/fields/test_related.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@
543543
from django.db import models
544544
class User(models.Model):
545545
pass
546-
def get_user_model_name():
546+
def get_user_model_name() -> str:
547547
return 'myapp.User'
548548
class Profile(models.Model):
549549
user = models.ForeignKey(to=get_user_model_name(), on_delete=models.CASCADE)
@@ -703,7 +703,7 @@
703703
def custom(self) -> None:
704704
pass
705705
706-
def TransactionManager():
706+
def TransactionManager() -> BaseManager:
707707
return BaseManager.from_queryset(TransactionQuerySet)()
708708
709709
class Transaction(models.Model):

tests/typecheck/managers/querysets/test_annotate.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,12 @@
164164
qs = User.objects.annotate(Count('id'))
165165
annotated_user = qs.get()
166166
167-
def animals_only(param: Animal):
167+
def animals_only(param: Animal) -> None:
168168
pass
169169
# Make sure that even though attr access falls back to Any, the type is still checked
170170
animals_only(annotated_user) # E: Argument 1 to "animals_only" has incompatible type "WithAnnotations[myapp__models__User]"; expected "Animal"
171171
172-
def users_allowed(param: User):
172+
def users_allowed(param: User) -> None:
173173
# But this function accepts only the original User type, so any attr access is not allowed within this function
174174
param.foo # E: "User" has no attribute "foo"
175175
# Passing in the annotated User to a function taking a (unannotated) User is OK

tests/typecheck/managers/querysets/test_iteration.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
main: |
2020
from myapp.models import User
2121
22-
async def main():
22+
async def main() -> None:
2323
async for user in User.objects.all():
2424
reveal_type(user) # N: Revealed type is "myapp.models.User"
2525
installed_apps:

tests/typecheck/managers/test_managers.yml

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
4747
_T = TypeVar('_T', bound=models.Model)
4848
class Base(Generic[_T]):
49-
def __init__(self, model_cls: Type[_T]):
49+
def __init__(self, model_cls: Type[_T]) -> None:
5050
self.model_cls = model_cls
5151
reveal_type(self.model_cls._default_manager) # N: Revealed type is "django.db.models.manager.BaseManager[_T`1]"
5252
class MyModel(models.Model):
@@ -71,7 +71,7 @@
7171
7272
_T = TypeVar('_T', bound=models.Model)
7373
class Base(Generic[_T]):
74-
def __init__(self, model_cls: Type[_T]):
74+
def __init__(self, model_cls: Type[_T]) -> None:
7575
self.model_cls = model_cls
7676
reveal_type(self.model_cls._base_manager) # N: Revealed type is "django.db.models.manager.BaseManager[_T`1]"
7777
class MyModel(models.Model):
@@ -350,11 +350,12 @@
350350
- path: myapp/__init__.py
351351
- path: myapp/models.py
352352
content: |
353+
from typing import Any
353354
from django.db import models
354355
class MyManager(models.Manager):
355356
def get_instance(self) -> int:
356357
pass
357-
def get_instance_untyped(self, name):
358+
def get_instance_untyped(self, name: str):
358359
pass
359360
class User(models.Model):
360361
objects = MyManager()
@@ -389,21 +390,22 @@
389390
installed_apps:
390391
- myapp
391392
out: |
392-
myapp/models:4: error: Return type "MyModel" of "create" incompatible with return type "_T" in supertype "BaseManager"
393-
myapp/models:5: error: Incompatible return value type (got "_T", expected "MyModel")
393+
myapp/models:5: error: Return type "MyModel" of "create" incompatible with return type "_T" in supertype "BaseManager"
394+
myapp/models:6: error: Incompatible return value type (got "_T", expected "MyModel")
394395
files:
395396
- path: myapp/__init__.py
396397
- path: myapp/models.py
397398
content: |
399+
from typing import Any
398400
from django.db import models
399401
class MyModelManager(models.Manager):
400402
401-
def create(self, **kwargs) -> 'MyModel':
402-
return super().create(**kwargs)
403+
def create(self, **kwargs: Any) -> 'MyModel':
404+
return super().create(**kwargs)
403405
404406
405407
class MyModel(models.Model):
406-
objects = MyModelManager()
408+
objects = MyModelManager()
407409
408410
409411
- case: override_manager_create2
@@ -416,15 +418,15 @@
416418
- path: myapp/__init__.py
417419
- path: myapp/models.py
418420
content: |
421+
from typing import Any
419422
from django.db import models
420423
class MyModelManager(models.Manager['MyModel']):
421424
422-
def create(self, **kwargs) -> 'MyModel':
423-
return super().create(**kwargs)
425+
def create(self, **kwargs: Any) -> 'MyModel':
426+
return super().create(**kwargs)
424427
425428
class MyModel(models.Model):
426-
427-
objects = MyModelManager()
429+
objects = MyModelManager()
428430
429431
- case: regression_manager_scope_foreign
430432
main: |
@@ -488,7 +490,7 @@
488490
class InvisibleUnresolvable(AbstractUnresolvable):
489491
text = models.TextField()
490492
491-
def process_booking(user: User):
493+
def process_booking(user: User) -> None:
492494
reveal_type(User.objects)
493495
reveal_type(User._default_manager)
494496

tests/typecheck/models/test_create.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@
124124
- path: myapp/models.py
125125
content: |
126126
from django.db import models
127-
def return_int():
127+
def return_int() -> int:
128128
return 0
129129
class MyModel(models.Model):
130130
id = models.IntegerField(primary_key=True, default=return_int)

tests/typecheck/models/test_primary_key.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
content: |
1515
from django.db import models
1616
class MyModel(models.Model):
17-
def __str__(self):
17+
def __str__(self) -> str:
1818
reveal_type(self.id) # N: Revealed type is "builtins.int"
1919
reveal_type(self.pk) # N: Revealed type is "builtins.int"
20+
return ''
2021
2122
2223
- case: test_access_to_id_field_through_self_if_primary_key_is_defined
@@ -36,9 +37,10 @@
3637
from django.db import models
3738
class MyModel(models.Model):
3839
id = models.CharField(max_length=10, primary_key=True)
39-
def __str__(self):
40+
def __str__(self) -> str:
4041
reveal_type(self.id) # N: Revealed type is "builtins.str"
4142
reveal_type(self.pk) # N: Revealed type is "builtins.str"
43+
return self.id
4244
4345
4446
- case: test_access_to_id_field_through_self_if_primary_key_has_different_name
@@ -60,7 +62,8 @@
6062
from django.db import models
6163
class MyModel(models.Model):
6264
primary = models.CharField(max_length=10, primary_key=True)
63-
def __str__(self):
65+
def __str__(self) -> str:
6466
reveal_type(self.primary) # N: Revealed type is "builtins.str"
6567
reveal_type(self.pk) # N: Revealed type is "builtins.str"
6668
self.id # E: "MyModel" has no attribute "id"
69+
return self.primary

0 commit comments

Comments
 (0)