|
2 | 2 | import sys
|
3 | 3 | from collections import defaultdict
|
4 | 4 | from contextlib import contextmanager
|
5 |
| -from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, Optional, Set, Tuple, Type, Union |
| 5 | +from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, Optional, Sequence, Set, Tuple, Type, Union |
6 | 6 |
|
7 | 7 | from django.core.exceptions import FieldError
|
8 | 8 | from django.db import models
|
9 | 9 | from django.db.models.base import Model
|
| 10 | +from django.db.models.expressions import Expression |
10 | 11 | from django.db.models.fields import AutoField, CharField, Field
|
11 | 12 | from django.db.models.fields.related import ForeignKey, RelatedField
|
12 | 13 | from django.db.models.fields.reverse_related import ForeignObjectRel
|
|
19 | 20 | from mypy.types import AnyType, Instance
|
20 | 21 | from mypy.types import Type as MypyType
|
21 | 22 | from mypy.types import TypeOfAny, UnionType
|
| 23 | +from typing_extensions import Literal |
22 | 24 |
|
23 | 25 | from mypy_django_plugin.lib import fullnames, helpers
|
24 | 26 | from mypy_django_plugin.lib.fullnames import WITH_ANNOTATIONS_FULLNAME
|
@@ -375,30 +377,40 @@ def _resolve_field_from_parts(
|
375 | 377 | assert isinstance(field, (Field, ForeignObjectRel))
|
376 | 378 | return field
|
377 | 379 |
|
378 |
| - def resolve_lookup_into_field( |
| 380 | + def solve_lookup_type( |
379 | 381 | self, model_cls: Type[Model], lookup: str
|
380 |
| - ) -> Union["Field[Any, Any]", ForeignObjectRel]: |
| 382 | + ) -> Optional[Tuple[Sequence[str], Sequence[str], Union[Expression, Literal[False]]]]: |
381 | 383 | query = Query(model_cls)
|
382 |
| - lookup_parts, field_parts, is_expression = query.solve_lookup_type(lookup) |
| 384 | + if (lookup == "pk" or lookup.startswith("pk__")) and query.get_meta().pk is None: |
| 385 | + # Primary key lookup when no primary key field is found, model is presumably |
| 386 | + # abstract and we can't say anything about 'pk'. |
| 387 | + return None |
| 388 | + return query.solve_lookup_type(lookup) |
383 | 389 |
|
| 390 | + def resolve_lookup_into_field( |
| 391 | + self, model_cls: Type[Model], lookup: str |
| 392 | + ) -> Union["Field[Any, Any]", ForeignObjectRel, None]: |
| 393 | + solved_lookup = self.solve_lookup_type(model_cls, lookup) |
| 394 | + if solved_lookup is None: |
| 395 | + return None |
| 396 | + lookup_parts, field_parts, is_expression = solved_lookup |
384 | 397 | if lookup_parts:
|
385 | 398 | raise LookupsAreUnsupported()
|
386 | 399 | return self._resolve_field_from_parts(field_parts, model_cls)
|
387 | 400 |
|
388 | 401 | def resolve_lookup_expected_type(self, ctx: MethodContext, model_cls: Type[Model], lookup: str) -> MypyType:
|
389 |
| - query = Query(model_cls) |
390 |
| - if lookup == "pk" or lookup.startswith("pk__") and query.get_meta().pk is None: |
391 |
| - # Primary key lookup when no primary key field is found, model is presumably |
392 |
| - # abstract and we can't say anything about 'pk'. |
393 |
| - return AnyType(TypeOfAny.implementation_artifact) |
394 | 402 | try:
|
395 |
| - lookup_parts, field_parts, is_expression = query.solve_lookup_type(lookup) |
396 |
| - if is_expression: |
397 |
| - return AnyType(TypeOfAny.explicit) |
| 403 | + solved_lookup = self.solve_lookup_type(model_cls, lookup) |
398 | 404 | except FieldError as exc:
|
399 | 405 | ctx.api.fail(exc.args[0], ctx.context)
|
400 | 406 | return AnyType(TypeOfAny.from_error)
|
401 | 407 |
|
| 408 | + if solved_lookup is None: |
| 409 | + return AnyType(TypeOfAny.implementation_artifact) |
| 410 | + lookup_parts, field_parts, is_expression = solved_lookup |
| 411 | + if is_expression: |
| 412 | + return AnyType(TypeOfAny.explicit) |
| 413 | + |
402 | 414 | field = self._resolve_field_from_parts(field_parts, model_cls)
|
403 | 415 |
|
404 | 416 | lookup_cls = None
|
|
0 commit comments