Skip to content

Commit 5bff9f8

Browse files
authored
Add type-checking to Manager.acreate (typeddjango#2477)
1 parent 93a6ef7 commit 5bff9f8

File tree

3 files changed

+39
-0
lines changed

3 files changed

+39
-0
lines changed

mypy_django_plugin/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ def manager_and_queryset_method_hooks(self) -> dict[str, Callable[[MethodContext
172172
"alias": partial(querysets.extract_proper_type_queryset_annotate, django_context=self.django_context),
173173
"annotate": partial(querysets.extract_proper_type_queryset_annotate, django_context=self.django_context),
174174
"create": partial(init_create.redefine_and_typecheck_model_create, django_context=self.django_context),
175+
"acreate": partial(init_create.redefine_and_typecheck_model_acreate, django_context=self.django_context),
175176
"filter": typecheck_filtering_method,
176177
"get": typecheck_filtering_method,
177178
"exclude": typecheck_filtering_method,

mypy_django_plugin/transformers/init_create.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,23 @@ def redefine_and_typecheck_model_create(ctx: MethodContext, django_context: Djan
7676
return ctx.default_return_type
7777

7878
return typecheck_model_method(ctx, django_context, model_cls, "create")
79+
80+
81+
def redefine_and_typecheck_model_acreate(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
82+
default_return_type = get_proper_type(ctx.default_return_type)
83+
84+
if not isinstance(default_return_type, Instance):
85+
# only work with ctx.default_return_type = model Instance
86+
return ctx.default_return_type
87+
88+
# default_return_type at this point should be of type Coroutine[Any, Any, <Model>]
89+
model = get_proper_type(default_return_type.args[-1])
90+
if not isinstance(model, Instance):
91+
return ctx.default_return_type
92+
93+
model_fullname = model.type.fullname
94+
model_cls = django_context.get_model_class_by_fullname(model_fullname)
95+
if model_cls is None:
96+
return ctx.default_return_type
97+
98+
return typecheck_model_method(ctx, django_context, model_cls, "acreate")

tests/typecheck/models/test_create.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,21 @@
155155
id = models.IntegerField(primary_key=True)
156156
class MyModel3(models.Model):
157157
default = models.IntegerField(default=return_int)
158+
159+
- case: default_manager_acreate_is_typechecked
160+
main: |
161+
import asyncio
162+
from myapp.models import User
163+
async def amain() -> None:
164+
reveal_type(await User.objects.acreate(pk=1, name='Max', age=10)) # N: Revealed type is "myapp.models.User"
165+
await User.objects.acreate(age=[]) # E: Incompatible type for "age" of "User" (got "List[Any]", expected "Union[float, int, str, Combinable]") [misc]
166+
installed_apps:
167+
- myapp
168+
files:
169+
- path: myapp/__init__.py
170+
- path: myapp/models.py
171+
content: |
172+
from django.db import models
173+
class User(models.Model):
174+
name = models.CharField(max_length=100)
175+
age = models.IntegerField()

0 commit comments

Comments
 (0)