mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-10 22:11:54 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cb10390cf | ||
|
|
c1640b619f | ||
|
|
a08ad80a0d | ||
|
|
f30cd092f1 | ||
|
|
dcd9ee0bb8 | ||
|
|
26a80a8279 | ||
|
|
82de0a8791 | ||
|
|
79ebe20f2e | ||
|
|
587c2c484b | ||
|
|
4a22da29cb | ||
|
|
70378b8f40 | ||
|
|
b7f7713c5a | ||
|
|
2720b74242 | ||
|
|
563c0add5e | ||
|
|
3191740c6b |
@@ -26,7 +26,7 @@ in your `mypy.ini` file.
|
||||
|
||||
## Configuration
|
||||
|
||||
In order to specify config file, set `MYPY_DJANGO_CONFIG` environment variable with path to the config file.
|
||||
In order to specify config file, set `MYPY_DJANGO_CONFIG` environment variable with path to the config file. Default is `./mypy_django.ini`
|
||||
|
||||
Config file format (.ini):
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Type
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Iterator
|
||||
|
||||
from django.contrib.admin.options import ModelAdmin
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
@@ -16,7 +16,7 @@ class ListFilter:
|
||||
self, request: WSGIRequest, params: Dict[str, str], model: Type[Model], model_admin: ModelAdmin
|
||||
) -> None: ...
|
||||
def has_output(self) -> bool: ...
|
||||
def choices(self, changelist: Any) -> None: ...
|
||||
def choices(self, changelist: Any) -> Optional[Iterator[Dict[str, Any]]]: ...
|
||||
def queryset(self, request: Any, queryset: QuerySet) -> Optional[QuerySet]: ...
|
||||
def expected_parameters(self) -> Optional[List[str]]: ...
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, Optional, Tuple, List
|
||||
from typing import Any, Optional, Tuple, List, overload
|
||||
|
||||
from django.db import models
|
||||
|
||||
@@ -30,4 +30,8 @@ class AbstractBaseUser(models.Model):
|
||||
@classmethod
|
||||
def get_email_field_name(cls) -> str: ...
|
||||
@classmethod
|
||||
@overload
|
||||
def normalize_username(cls, username: str) -> str: ...
|
||||
@classmethod
|
||||
@overload
|
||||
def normalize_username(cls, username: Any) -> Any: ...
|
||||
|
||||
@@ -4,12 +4,7 @@ from django.db import models
|
||||
from django.db.models.base import Model
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
class ContentTypeManager(models.Manager):
|
||||
creation_counter: int
|
||||
model: None
|
||||
name: None
|
||||
use_in_migrations: bool = ...
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
||||
class ContentTypeManager(models.Manager["ContentType"]):
|
||||
def get_by_natural_key(self, app_label: str, model: str) -> ContentType: ...
|
||||
def get_for_model(self, model: Union[Type[Model], Model], for_concrete_model: bool = ...) -> ContentType: ...
|
||||
def get_for_models(self, *models: Any, for_concrete_models: bool = ...) -> Dict[Type[Model], ContentType]: ...
|
||||
@@ -18,9 +13,9 @@ class ContentTypeManager(models.Manager):
|
||||
|
||||
class ContentType(models.Model):
|
||||
id: int
|
||||
app_label: str = ...
|
||||
model: str = ...
|
||||
objects: Any = ...
|
||||
app_label: models.CharField = ...
|
||||
model: models.CharField = ...
|
||||
objects: ContentTypeManager = ...
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
def model_class(self) -> Optional[Type[Model]]: ...
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
from typing import Any, Optional
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
from django.db import models
|
||||
|
||||
class FlatPage(models.Model):
|
||||
id: None
|
||||
url: str = ...
|
||||
title: str = ...
|
||||
content: str = ...
|
||||
enable_comments: bool = ...
|
||||
template_name: str = ...
|
||||
registration_required: bool = ...
|
||||
sites: Any = ...
|
||||
url: models.CharField = ...
|
||||
title: models.CharField = ...
|
||||
content: models.TextField = ...
|
||||
enable_comments: models.BooleanField = ...
|
||||
template_name: models.CharField = ...
|
||||
registration_required: models.BooleanField = ...
|
||||
sites: models.ManyToManyField[Site] = ...
|
||||
def get_absolute_url(self) -> str: ...
|
||||
|
||||
@@ -1,40 +1,43 @@
|
||||
# Stubs for django.core.files.uploadedfile (Python 3.5)
|
||||
|
||||
from typing import Any, Dict, IO, Iterator, Optional, Union
|
||||
from django.core.files import temp as tempfile
|
||||
from django.core.files.base import File
|
||||
|
||||
class UploadedFile(File):
|
||||
content_type = ... # type: Optional[str]
|
||||
charset = ... # type: Optional[str]
|
||||
content_type_extra = ... # type: Optional[Dict[str, str]]
|
||||
content_type: Optional[str] = ...
|
||||
charset: Optional[str] = ...
|
||||
content_type_extra: Optional[Dict[str, str]] = ...
|
||||
def __init__(
|
||||
self,
|
||||
file: IO,
|
||||
name: str = None,
|
||||
content_type: str = None,
|
||||
size: int = None,
|
||||
charset: str = None,
|
||||
content_type_extra: Dict[str, str] = None,
|
||||
file: Optional[IO] = ...,
|
||||
name: Optional[str] = ...,
|
||||
content_type: Optional[str] = ...,
|
||||
size: Optional[int] = ...,
|
||||
charset: Optional[str] = ...,
|
||||
content_type_extra: Optional[Dict[str, str]] = ...,
|
||||
) -> None: ...
|
||||
|
||||
class TemporaryUploadedFile(UploadedFile):
|
||||
def __init__(
|
||||
self, name: str, content_type: str, size: int, charset: str, content_type_extra: Dict[str, str] = None
|
||||
self,
|
||||
name: Optional[str],
|
||||
content_type: Optional[str],
|
||||
size: Optional[int],
|
||||
charset: Optional[str],
|
||||
content_type_extra: Optional[Dict[str, str]] = ...,
|
||||
) -> None: ...
|
||||
def temporary_file_path(self) -> str: ...
|
||||
|
||||
class InMemoryUploadedFile(UploadedFile):
|
||||
field_name = ... # type: Optional[str]
|
||||
field_name: Optional[str] = ...
|
||||
def __init__(
|
||||
self,
|
||||
file: IO,
|
||||
field_name: Optional[str],
|
||||
name: str,
|
||||
name: Optional[str],
|
||||
content_type: Optional[str],
|
||||
size: int,
|
||||
size: Optional[int],
|
||||
charset: Optional[str],
|
||||
content_type_extra: Dict[str, str] = None,
|
||||
content_type_extra: Dict[str, str] = ...,
|
||||
) -> None: ...
|
||||
def chunks(self, chunk_size: int = None) -> Iterator[bytes]: ...
|
||||
def multiple_chunks(self, chunk_size: int = None) -> bool: ...
|
||||
|
||||
@@ -168,12 +168,12 @@ class RawSQL(Expression):
|
||||
def __init__(self, sql: str, params: Sequence[Any], output_field: Optional[_OutputField] = ...) -> None: ...
|
||||
|
||||
class Func(SQLiteNumericMixin, Expression):
|
||||
function: Any = ...
|
||||
function: str = ...
|
||||
template: str = ...
|
||||
arg_joiner: str = ...
|
||||
arity: Any = ...
|
||||
arity: int = ...
|
||||
source_expressions: List[Expression] = ...
|
||||
extra: Any = ...
|
||||
extra: Dict[Any, Any] = ...
|
||||
def __init__(self, *expressions: Any, output_field: Optional[_OutputField] = ..., **extra: Any) -> None: ...
|
||||
def get_source_expressions(self) -> List[Combinable]: ...
|
||||
def set_source_expressions(self, exprs: List[Expression]) -> None: ...
|
||||
|
||||
@@ -207,6 +207,7 @@ class GenericIPAddressField(Field):
|
||||
validators: Iterable[_ValidatorCallable] = ...,
|
||||
error_messages: Optional[_ErrorMessagesToOverride] = ...,
|
||||
) -> None: ...
|
||||
def __set__(self, instance, value: Union[str, int, Callable[..., Any], Combinable]): ...
|
||||
def __get__(self, instance, owner) -> str: ...
|
||||
|
||||
class DateTimeCheckMixin: ...
|
||||
@@ -269,7 +270,7 @@ class DateTimeField(DateField):
|
||||
def __get__(self, instance, owner) -> datetime: ...
|
||||
|
||||
class UUIDField(Field):
|
||||
def __set__(self, instance, value: Any) -> None: ...
|
||||
def __set__(self, instance, value: Union[str, uuid.UUID]) -> None: ...
|
||||
def __get__(self, instance, owner) -> uuid.UUID: ...
|
||||
|
||||
class FilePathField(Field):
|
||||
|
||||
@@ -33,7 +33,7 @@ from django.db.models.fields.reverse_related import (
|
||||
)
|
||||
from django.db.models.query_utils import PathInfo, Q
|
||||
|
||||
from django.db.models.expressions import F
|
||||
from django.db.models.expressions import Combinable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.db.models.manager import RelatedManager
|
||||
@@ -105,11 +105,12 @@ class ForeignObject(RelatedField):
|
||||
|
||||
class ForeignKey(RelatedField, Generic[_T]):
|
||||
def __init__(self, to: Union[Type[_T], str], on_delete: Any, related_name: str = ..., **kwargs): ...
|
||||
def __set__(self, instance, value: Union[Model, F]) -> None: ...
|
||||
def __set__(self, instance, value: Union[Model, Combinable]) -> None: ...
|
||||
def __get__(self, instance, owner) -> _T: ...
|
||||
|
||||
class OneToOneField(RelatedField, Generic[_T]):
|
||||
def __init__(self, to: Union[Type[_T], str], on_delete: Any, related_name: str = ..., **kwargs): ...
|
||||
def __set__(self, instance, value: Union[Model, Combinable]) -> None: ...
|
||||
def __get__(self, instance, owner) -> _T: ...
|
||||
|
||||
class ManyToManyField(RelatedField, Generic[_T]):
|
||||
|
||||
@@ -9,22 +9,19 @@ class CumeDist(Func):
|
||||
window_compatible: bool = ...
|
||||
|
||||
class DenseRank(Func):
|
||||
extra: Dict[Any, Any]
|
||||
source_expressions: List[Any]
|
||||
function: str = ...
|
||||
name: str = ...
|
||||
output_field: Any = ...
|
||||
window_compatible: bool = ...
|
||||
|
||||
class FirstValue(Func):
|
||||
arity: int = ...
|
||||
function: str = ...
|
||||
name: str = ...
|
||||
window_compatible: bool = ...
|
||||
|
||||
class LagLeadFunction(Func):
|
||||
window_compatible: bool = ...
|
||||
def __init__(self, expression: Optional[str], offset: int = ..., default: None = ..., **extra: Any) -> Any: ...
|
||||
def __init__(
|
||||
self, expression: Optional[str], offset: int = ..., default: Optional[int] = ..., **extra: Any
|
||||
) -> None: ...
|
||||
|
||||
class Lag(LagLeadFunction):
|
||||
function: str = ...
|
||||
|
||||
@@ -17,7 +17,9 @@ class BaseManager(QuerySet[_T]):
|
||||
def deconstruct(self) -> Tuple[bool, str, None, Tuple, Dict[str, int]]: ...
|
||||
def check(self, **kwargs: Any) -> List[Any]: ...
|
||||
@classmethod
|
||||
def from_queryset(cls: Type[_Self], queryset_class: Any, class_name: Optional[Any] = ...) -> Type[_Self]: ...
|
||||
def from_queryset(
|
||||
cls: Type[_Self], queryset_class: Type[QuerySet], class_name: Optional[str] = ...
|
||||
) -> Type[_Self]: ...
|
||||
@classmethod
|
||||
def _get_queryset_methods(cls, queryset_class: type) -> Dict[str, Any]: ...
|
||||
def contribute_to_class(self, model: Type[Model], name: str) -> None: ...
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from io import BytesIO
|
||||
from io import BytesIO, StringIO
|
||||
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
|
||||
|
||||
from django.http.request import QueryDict
|
||||
@@ -11,7 +11,7 @@ class MultiPartParser:
|
||||
def __init__(
|
||||
self,
|
||||
META: Dict[str, Any],
|
||||
input_data: BytesIO,
|
||||
input_data: Union[StringIO, BytesIO],
|
||||
upload_handlers: Union[List[Any], ImmutableList],
|
||||
encoding: Optional[str] = ...,
|
||||
) -> None: ...
|
||||
|
||||
@@ -27,7 +27,7 @@ class HttpResponseBase(Iterable[AnyStr]):
|
||||
charset: Optional[str] = ...,
|
||||
) -> None: ...
|
||||
def serialize_headers(self) -> bytes: ...
|
||||
def __setitem__(self, header: Union[str, bytes], value: Union[str, bytes]) -> None: ...
|
||||
def __setitem__(self, header: Union[str, bytes], value: Union[str, bytes, int]) -> None: ...
|
||||
def __delitem__(self, header: Union[str, bytes]) -> None: ...
|
||||
def __getitem__(self, header: Union[str, bytes]) -> str: ...
|
||||
def has_header(self, header: str) -> bool: ...
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import Any, Optional
|
||||
|
||||
from django.http.request import HttpRequest
|
||||
from django.http.response import HttpResponse, HttpResponseNotFound, HttpResponsePermanentRedirect
|
||||
from django.http.response import HttpResponseBase, HttpResponsePermanentRedirect
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
class CommonMiddleware(MiddlewareMixin):
|
||||
@@ -9,9 +9,9 @@ class CommonMiddleware(MiddlewareMixin):
|
||||
def process_request(self, request: HttpRequest) -> Optional[HttpResponsePermanentRedirect]: ...
|
||||
def should_redirect_with_slash(self, request: HttpRequest) -> bool: ...
|
||||
def get_full_path_with_slash(self, request: HttpRequest) -> str: ...
|
||||
def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse: ...
|
||||
def process_response(self, request: HttpRequest, response: HttpResponseBase) -> HttpResponseBase: ...
|
||||
|
||||
class BrokenLinkEmailsMiddleware(MiddlewareMixin):
|
||||
def process_response(self, request: HttpRequest, response: HttpResponseNotFound) -> HttpResponseNotFound: ...
|
||||
def process_response(self, request: HttpRequest, response: HttpResponseBase) -> HttpResponseBase: ...
|
||||
def is_internal_request(self, domain: str, referer: str) -> bool: ...
|
||||
def is_ignorable_request(self, request: HttpRequest, uri: str, domain: str, referer: str) -> bool: ...
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from typing import Any, Callable, Dict, List, Optional, Type, Union, Sequence, Protocol
|
||||
from typing import Any, Callable, Dict, List, Optional, Protocol, Sequence, Type, TypeVar, Union
|
||||
|
||||
from django.db.models import Manager, QuerySet
|
||||
from django.db.models.base import Model
|
||||
from django.http.response import HttpResponse as HttpResponse, HttpResponseRedirect as HttpResponseRedirect
|
||||
|
||||
from django.db.models import Manager, QuerySet
|
||||
from django.http import HttpRequest
|
||||
|
||||
def render_to_response(
|
||||
@@ -28,6 +28,9 @@ class SupportsGetAbsoluteUrl(Protocol):
|
||||
def redirect(
|
||||
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: bool = ..., **kwargs: Any
|
||||
) -> HttpResponseRedirect: ...
|
||||
def get_object_or_404(klass: Union[Type[Model], Manager, QuerySet], *args: Any, **kwargs: Any) -> Model: ...
|
||||
def get_list_or_404(klass: Union[Type[Model], Manager, QuerySet], *args: Any, **kwargs: Any) -> List[Model]: ...
|
||||
|
||||
_T = TypeVar("_T", bound=Model)
|
||||
|
||||
def get_object_or_404(klass: Union[Type[_T], Manager[_T], QuerySet[_T]], *args: Any, **kwargs: Any) -> _T: ...
|
||||
def get_list_or_404(klass: Union[Type[_T], Manager[_T], QuerySet[_T]], *args: Any, **kwargs: Any) -> List[_T]: ...
|
||||
def resolve_url(to: Union[Callable, Model, str], *args: Any, **kwargs: Any) -> str: ...
|
||||
|
||||
@@ -53,7 +53,7 @@ class Template:
|
||||
nodelist: NodeList = ...
|
||||
def __init__(
|
||||
self,
|
||||
template_string: str,
|
||||
template_string: Union[Template, str],
|
||||
origin: Optional[Origin] = ...,
|
||||
name: Optional[str] = ...,
|
||||
engine: Optional[Engine] = ...,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from datetime import datetime
|
||||
from datetime import datetime, date, time
|
||||
from decimal import Decimal
|
||||
from typing import Any, Iterator, List, Optional, Union
|
||||
|
||||
@@ -8,14 +8,16 @@ FORMAT_SETTINGS: Any
|
||||
def reset_format_cache() -> None: ...
|
||||
def iter_format_modules(lang: str, format_module_path: Optional[Union[List[str], str]] = ...) -> Iterator[Any]: ...
|
||||
def get_format_modules(lang: Optional[str] = ..., reverse: bool = ...) -> List[Any]: ...
|
||||
def get_format(
|
||||
format_type: str, lang: Optional[str] = ..., use_l10n: Optional[bool] = ...
|
||||
) -> Union[List[str], int, str]: ...
|
||||
def get_format(format_type: str, lang: Optional[str] = ..., use_l10n: Optional[bool] = ...) -> str: ...
|
||||
|
||||
get_format_lazy: Any
|
||||
|
||||
def date_format(value: Union[datetime, str], format: Optional[str] = ..., use_l10n: Optional[bool] = ...) -> str: ...
|
||||
def time_format(value: Union[datetime, str], format: Optional[str] = ..., use_l10n: None = ...) -> str: ...
|
||||
def date_format(
|
||||
value: Union[date, datetime, str], format: Optional[str] = ..., use_l10n: Optional[bool] = ...
|
||||
) -> str: ...
|
||||
def time_format(
|
||||
value: Union[time, datetime, str], format: Optional[str] = ..., use_l10n: Optional[bool] = ...
|
||||
) -> str: ...
|
||||
def number_format(
|
||||
value: Union[Decimal, float, str],
|
||||
decimal_pos: Optional[int] = ...,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import logging.config
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
from logging import LogRecord
|
||||
from typing import Any, Callable, Dict, Optional, Union
|
||||
|
||||
from django.core.mail.backends.locmem import EmailBackend
|
||||
from django.core.management.color import Style
|
||||
@@ -20,9 +21,13 @@ class AdminEmailHandler(logging.Handler):
|
||||
class CallbackFilter(logging.Filter):
|
||||
callback: Callable = ...
|
||||
def __init__(self, callback: Callable) -> None: ...
|
||||
def filter(self, record: Union[str, LogRecord]) -> bool: ...
|
||||
|
||||
class RequireDebugFalse(logging.Filter): ...
|
||||
class RequireDebugTrue(logging.Filter): ...
|
||||
class RequireDebugFalse(logging.Filter):
|
||||
def filter(self, record: Union[str, LogRecord]) -> bool: ...
|
||||
|
||||
class RequireDebugTrue(logging.Filter):
|
||||
def filter(self, record: Union[str, LogRecord]) -> bool: ...
|
||||
|
||||
class ServerFormatter(logging.Formatter):
|
||||
datefmt: None
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from decimal import Decimal
|
||||
from typing import Any, Optional, Tuple, Union
|
||||
from typing import Optional, Sequence, Union
|
||||
|
||||
def format(
|
||||
number: Union[Decimal, float, str],
|
||||
decimal_sep: str,
|
||||
decimal_pos: Optional[int] = ...,
|
||||
grouping: Union[Tuple[int, int, int], int] = ...,
|
||||
grouping: Union[int, Sequence[int]] = ...,
|
||||
thousand_sep: str = ...,
|
||||
force_grouping: bool = ...,
|
||||
use_l10n: Optional[bool] = ...,
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
from typing import Any, Dict, List, Optional, Type
|
||||
from typing import Any, Callable, Dict, Optional, Sequence, Type, Union
|
||||
|
||||
from django.db import models
|
||||
from django.forms import models as model_forms, Form # type: ignore # This will be solved when adding forms module
|
||||
from django.http import HttpResponse, HttpRequest
|
||||
from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
|
||||
from django.views.generic.detail import BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin
|
||||
from typing_extensions import Literal
|
||||
|
||||
from django.db import models
|
||||
from django.forms import Form
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
|
||||
class FormMixin(ContextMixin):
|
||||
initial = ... # type: Dict[str, object]
|
||||
form_class = ... # type: Optional[Type[Form]]
|
||||
success_url = ... # type: Optional[str]
|
||||
prefix = ... # type: Optional[str]
|
||||
request = ... # type: HttpRequest
|
||||
def render_to_response(self, context: Dict[str, object], **response_kwargs: object) -> HttpResponse: ...
|
||||
def get_initial(self) -> Dict[str, object]: ...
|
||||
initial: Dict[str, Any] = ...
|
||||
form_class: Optional[Type[Form]] = ...
|
||||
success_url: Optional[Union[str, Callable[..., Any]]] = ...
|
||||
prefix: Optional[str] = ...
|
||||
request: HttpRequest = ...
|
||||
def render_to_response(self, context: Dict[str, Any], **response_kwargs: object) -> HttpResponse: ...
|
||||
def get_initial(self) -> Dict[str, Any]: ...
|
||||
def get_prefix(self) -> Optional[str]: ...
|
||||
def get_form_class(self) -> Type[Form]: ...
|
||||
def get_form(self, form_class: Type[Form] = None) -> Form: ...
|
||||
@@ -24,8 +26,8 @@ class FormMixin(ContextMixin):
|
||||
def get_context_data(self, **kwargs: object) -> Dict[str, Any]: ...
|
||||
|
||||
class ModelFormMixin(FormMixin, SingleObjectMixin):
|
||||
fields = ... # type: Optional[List[str]]
|
||||
object = ... # type: models.Model
|
||||
fields: Optional[Union[Sequence[str], Literal["__all__"]]] = ...
|
||||
object: models.Model = ...
|
||||
def get_form_class(self) -> Type[Form]: ...
|
||||
def get_form_kwargs(self) -> Dict[str, object]: ...
|
||||
def get_success_url(self) -> str: ...
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from configparser import ConfigParser
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
@@ -10,12 +10,16 @@ class Config:
|
||||
ignore_missing_settings: bool = False
|
||||
|
||||
@classmethod
|
||||
def from_config_file(self, fpath: str) -> 'Config':
|
||||
def from_config_file(cls, fpath: str) -> 'Config':
|
||||
ini_config = ConfigParser()
|
||||
ini_config.read(fpath)
|
||||
if not ini_config.has_section('mypy_django_plugin'):
|
||||
raise ValueError('Invalid config file: no [mypy_django_plugin] section')
|
||||
return Config(django_settings_module=ini_config.get('mypy_django_plugin', 'django_settings',
|
||||
fallback=None),
|
||||
|
||||
django_settings = ini_config.get('mypy_django_plugin', 'django_settings',
|
||||
fallback=None)
|
||||
if django_settings:
|
||||
django_settings = django_settings.strip()
|
||||
return Config(django_settings_module=django_settings,
|
||||
ignore_missing_settings=ini_config.get('mypy_django_plugin', 'ignore_missing_settings',
|
||||
fallback=False))
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import typing
|
||||
from typing import Dict, Optional
|
||||
|
||||
from mypy.nodes import Expression, ImportedName, MypyFile, NameExpr, SymbolNode, TypeInfo
|
||||
from mypy.checker import TypeChecker
|
||||
from mypy.nodes import AssignmentStmt, ClassDef, Expression, FuncDef, ImportedName, Lvalue, MypyFile, NameExpr, SymbolNode, \
|
||||
TypeInfo
|
||||
from mypy.plugin import FunctionContext
|
||||
from mypy.types import AnyType, Instance, Type, TypeOfAny, TypeVarType
|
||||
from mypy.types import AnyType, CallableType, Instance, Type, TypeOfAny, TypeVarType, UnionType
|
||||
|
||||
MODEL_CLASS_FULLNAME = 'django.db.models.base.Model'
|
||||
FIELD_FULLNAME = 'django.db.models.fields.Field'
|
||||
@@ -146,3 +148,109 @@ def get_argument_type_by_name(ctx: FunctionContext, name: str) -> Optional[Type]
|
||||
# Either an error or no value passed.
|
||||
return None
|
||||
return arg_types[0]
|
||||
|
||||
|
||||
def get_setting_expr(api: TypeChecker, setting_name: str) -> Optional[Expression]:
|
||||
try:
|
||||
settings_sym = api.modules['django.conf'].names['settings']
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
settings_type: TypeInfo = settings_sym.type.type
|
||||
auth_user_model_sym = settings_type.get(setting_name)
|
||||
if not auth_user_model_sym:
|
||||
return None
|
||||
|
||||
module, _, name = auth_user_model_sym.fullname.rpartition('.')
|
||||
if module not in api.modules:
|
||||
return None
|
||||
|
||||
module_file = api.modules.get(module)
|
||||
for name_expr, value_expr in iter_over_assignments(module_file):
|
||||
if isinstance(name_expr, NameExpr) and name_expr.name == setting_name:
|
||||
return value_expr
|
||||
return None
|
||||
|
||||
|
||||
def iter_over_assignments(
|
||||
class_or_module: typing.Union[ClassDef, MypyFile]) -> typing.Iterator[typing.Tuple[Lvalue, Expression]]:
|
||||
if isinstance(class_or_module, ClassDef):
|
||||
statements = class_or_module.defs.body
|
||||
else:
|
||||
statements = class_or_module.defs
|
||||
|
||||
for stmt in statements:
|
||||
if not isinstance(stmt, AssignmentStmt):
|
||||
continue
|
||||
if len(stmt.lvalues) > 1:
|
||||
# not supported yet
|
||||
continue
|
||||
yield stmt.lvalues[0], stmt.rvalue
|
||||
|
||||
|
||||
def extract_field_setter_type(tp: Instance) -> Optional[Type]:
|
||||
if not isinstance(tp, Instance):
|
||||
return None
|
||||
if tp.type.has_base(FIELD_FULLNAME):
|
||||
set_method = tp.type.get_method('__set__')
|
||||
if isinstance(set_method, FuncDef) and isinstance(set_method.type, CallableType):
|
||||
if 'value' in set_method.type.arg_names:
|
||||
set_value_type = set_method.type.arg_types[set_method.type.arg_names.index('value')]
|
||||
if isinstance(set_value_type, Instance):
|
||||
set_value_type = fill_typevars(tp, set_value_type)
|
||||
return set_value_type
|
||||
elif isinstance(set_value_type, UnionType):
|
||||
items_no_typevars = []
|
||||
for item in set_value_type.items:
|
||||
if isinstance(item, Instance):
|
||||
item = fill_typevars(tp, item)
|
||||
items_no_typevars.append(item)
|
||||
return UnionType(items_no_typevars)
|
||||
|
||||
field_getter_type = extract_field_getter_type(tp)
|
||||
if field_getter_type:
|
||||
return field_getter_type
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def extract_field_getter_type(tp: Instance) -> Optional[Type]:
|
||||
if not isinstance(tp, Instance):
|
||||
return None
|
||||
if tp.type.has_base(FIELD_FULLNAME):
|
||||
get_method = tp.type.get_method('__get__')
|
||||
if isinstance(get_method, FuncDef) and isinstance(get_method.type, CallableType):
|
||||
return get_method.type.ret_type
|
||||
# GenericForeignKey
|
||||
if tp.type.has_base(GENERIC_FOREIGN_KEY_FULLNAME):
|
||||
return AnyType(TypeOfAny.special_form)
|
||||
return None
|
||||
|
||||
|
||||
def get_django_metadata(model: TypeInfo) -> Dict[str, typing.Any]:
|
||||
return model.metadata.setdefault('django', {})
|
||||
|
||||
|
||||
def get_related_field_primary_key_names(base_model: TypeInfo) -> typing.List[str]:
|
||||
django_metadata = get_django_metadata(base_model)
|
||||
return django_metadata.setdefault('related_field_primary_keys', [])
|
||||
|
||||
|
||||
def get_fields_metadata(model: TypeInfo) -> Dict[str, typing.Any]:
|
||||
return get_django_metadata(model).setdefault('fields', {})
|
||||
|
||||
|
||||
def extract_primary_key_type_for_set(model: TypeInfo) -> Optional[Type]:
|
||||
for field_name, props in get_fields_metadata(model).items():
|
||||
is_primary_key = props.get('primary_key', False)
|
||||
if is_primary_key:
|
||||
return extract_field_setter_type(model.names[field_name].type)
|
||||
return None
|
||||
|
||||
|
||||
def extract_primary_key_type_for_get(model: TypeInfo) -> Optional[Type]:
|
||||
for field_name, props in get_fields_metadata(model).items():
|
||||
is_primary_key = props.get('primary_key', False)
|
||||
if is_primary_key:
|
||||
return extract_field_getter_type(model.names[field_name].type)
|
||||
return None
|
||||
|
||||
@@ -2,19 +2,18 @@ import os
|
||||
from typing import Callable, Dict, Optional, cast
|
||||
|
||||
from mypy.checker import TypeChecker
|
||||
from mypy.nodes import TypeInfo
|
||||
from mypy.nodes import MemberExpr, TypeInfo
|
||||
from mypy.options import Options
|
||||
from mypy.plugin import ClassDefContext, FunctionContext, MethodContext, Plugin
|
||||
from mypy.types import Instance, Type
|
||||
|
||||
from mypy.plugin import AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin
|
||||
from mypy.types import AnyType, Instance, Type, TypeOfAny, TypeType
|
||||
from mypy_django_plugin import helpers, monkeypatch
|
||||
from mypy_django_plugin.config import Config
|
||||
from mypy_django_plugin.plugins import init_create
|
||||
from mypy_django_plugin.plugins.fields import determine_type_of_array_field, record_field_properties_into_outer_model_class
|
||||
from mypy_django_plugin.plugins.init_create import redefine_and_typecheck_model_init, redefine_and_typecheck_model_create
|
||||
from mypy_django_plugin.plugins.migrations import determine_model_cls_from_string_for_migrations
|
||||
from mypy_django_plugin.plugins.migrations import determine_model_cls_from_string_for_migrations, get_string_value_from_expr
|
||||
from mypy_django_plugin.plugins.models import process_model_class
|
||||
from mypy_django_plugin.plugins.related_fields import extract_to_parameter_as_get_ret_type_for_related_field, reparametrize_with
|
||||
from mypy_django_plugin.plugins.settings import AddSettingValuesToDjangoConfObject
|
||||
from mypy_django_plugin.plugins.settings import AddSettingValuesToDjangoConfObject, get_settings_metadata
|
||||
|
||||
|
||||
def transform_model_class(ctx: ClassDefContext) -> None:
|
||||
@@ -56,6 +55,75 @@ def determine_proper_manager_type(ctx: FunctionContext) -> Type:
|
||||
return ret
|
||||
|
||||
|
||||
def return_user_model_hook(ctx: FunctionContext) -> Type:
|
||||
api = cast(TypeChecker, ctx.api)
|
||||
setting_expr = helpers.get_setting_expr(api, 'AUTH_USER_MODEL')
|
||||
if setting_expr is None:
|
||||
return ctx.default_return_type
|
||||
|
||||
model_path = get_string_value_from_expr(setting_expr)
|
||||
if model_path is None:
|
||||
return ctx.default_return_type
|
||||
|
||||
app_label, _, model_class_name = model_path.rpartition('.')
|
||||
if app_label is None:
|
||||
return ctx.default_return_type
|
||||
|
||||
model_fullname = helpers.get_model_fullname(app_label, model_class_name,
|
||||
all_modules=api.modules)
|
||||
if model_fullname is None:
|
||||
api.fail(f'"{app_label}.{model_class_name}" model class is not imported so far. Try to import it '
|
||||
f'(under if TYPE_CHECKING) at the beginning of the current file',
|
||||
context=ctx.context)
|
||||
return ctx.default_return_type
|
||||
|
||||
model_info = helpers.lookup_fully_qualified_generic(model_fullname,
|
||||
all_modules=api.modules)
|
||||
if model_info is None or not isinstance(model_info, TypeInfo):
|
||||
return ctx.default_return_type
|
||||
return TypeType(Instance(model_info, []))
|
||||
|
||||
|
||||
def extract_and_return_primary_key_of_bound_related_field_parameter(ctx: AttributeContext) -> Type:
|
||||
if not isinstance(ctx.default_attr_type, Instance) or not (ctx.default_attr_type.type.fullname() == 'builtins.int'):
|
||||
return ctx.default_attr_type
|
||||
|
||||
if not isinstance(ctx.type, Instance) or not ctx.type.type.has_base(helpers.MODEL_CLASS_FULLNAME):
|
||||
return ctx.default_attr_type
|
||||
|
||||
field_name = ctx.context.name.split('_')[0]
|
||||
sym = ctx.type.type.get(field_name)
|
||||
if sym and isinstance(sym.type, Instance) and len(sym.type.args) > 0:
|
||||
to_arg = sym.type.args[0]
|
||||
if isinstance(to_arg, AnyType):
|
||||
return AnyType(TypeOfAny.special_form)
|
||||
|
||||
model_type: TypeInfo = to_arg.type
|
||||
primary_key_type = helpers.extract_primary_key_type_for_get(model_type)
|
||||
if primary_key_type:
|
||||
return primary_key_type
|
||||
return ctx.default_attr_type
|
||||
|
||||
|
||||
class ExtractSettingType:
|
||||
def __init__(self, module_fullname: str):
|
||||
self.module_fullname = module_fullname
|
||||
|
||||
def __call__(self, ctx: AttributeContext) -> Type:
|
||||
api = cast(TypeChecker, ctx.api)
|
||||
original_module = api.modules.get(self.module_fullname)
|
||||
if original_module is None:
|
||||
return ctx.default_attr_type
|
||||
|
||||
definition = ctx.context
|
||||
if isinstance(definition, MemberExpr):
|
||||
sym = original_module.names.get(definition.name)
|
||||
if sym and sym.type:
|
||||
return sym.type
|
||||
|
||||
return ctx.default_attr_type
|
||||
|
||||
|
||||
class DjangoPlugin(Plugin):
|
||||
def __init__(self, options: Options) -> None:
|
||||
super().__init__(options)
|
||||
@@ -63,20 +131,20 @@ class DjangoPlugin(Plugin):
|
||||
monkeypatch.restore_original_load_graph()
|
||||
monkeypatch.restore_original_dependencies_handling()
|
||||
|
||||
config_fpath = os.environ.get('MYPY_DJANGO_CONFIG')
|
||||
if config_fpath:
|
||||
config_fpath = os.environ.get('MYPY_DJANGO_CONFIG', 'mypy_django.ini')
|
||||
if config_fpath and os.path.exists(config_fpath):
|
||||
self.config = Config.from_config_file(config_fpath)
|
||||
self.django_settings = self.config.django_settings_module
|
||||
self.django_settings_module = self.config.django_settings_module
|
||||
else:
|
||||
self.config = Config()
|
||||
self.django_settings = None
|
||||
self.django_settings_module = None
|
||||
|
||||
if 'DJANGO_SETTINGS_MODULE' in os.environ:
|
||||
self.django_settings = os.environ['DJANGO_SETTINGS_MODULE']
|
||||
self.django_settings_module = os.environ['DJANGO_SETTINGS_MODULE']
|
||||
|
||||
settings_modules = ['django.conf.global_settings']
|
||||
if self.django_settings:
|
||||
settings_modules.append(self.django_settings)
|
||||
if self.django_settings_module:
|
||||
settings_modules.append(self.django_settings_module)
|
||||
|
||||
monkeypatch.add_modules_as_a_source_seed_files(settings_modules)
|
||||
monkeypatch.inject_modules_as_dependencies_for_django_conf_settings(settings_modules)
|
||||
@@ -105,6 +173,9 @@ class DjangoPlugin(Plugin):
|
||||
|
||||
def get_function_hook(self, fullname: str
|
||||
) -> Optional[Callable[[FunctionContext], Type]]:
|
||||
if fullname == 'django.contrib.auth.get_user_model':
|
||||
return return_user_model_hook
|
||||
|
||||
if fullname in {helpers.FOREIGN_KEY_FULLNAME,
|
||||
helpers.ONETOONE_FIELD_FULLNAME,
|
||||
helpers.MANYTOMANY_FIELD_FULLNAME}:
|
||||
@@ -121,15 +192,16 @@ class DjangoPlugin(Plugin):
|
||||
if sym and isinstance(sym.node, TypeInfo):
|
||||
if sym.node.has_base(helpers.FIELD_FULLNAME):
|
||||
return record_field_properties_into_outer_model_class
|
||||
|
||||
if sym.node.metadata.get('django', {}).get('generated_init'):
|
||||
return redefine_and_typecheck_model_init
|
||||
return init_create.redefine_and_typecheck_model_init
|
||||
|
||||
def get_method_hook(self, fullname: str
|
||||
) -> Optional[Callable[[MethodContext], Type]]:
|
||||
manager_classes = self._get_current_manager_bases()
|
||||
class_fullname, _, method_name = fullname.rpartition('.')
|
||||
if class_fullname in manager_classes and method_name == 'create':
|
||||
return redefine_and_typecheck_model_create
|
||||
return init_create.redefine_and_typecheck_model_create
|
||||
|
||||
if fullname in {'django.apps.registry.Apps.get_model',
|
||||
'django.db.migrations.state.StateApps.get_model'}:
|
||||
@@ -143,8 +215,8 @@ class DjangoPlugin(Plugin):
|
||||
|
||||
if fullname == helpers.DUMMY_SETTINGS_BASE_CLASS:
|
||||
settings_modules = ['django.conf.global_settings']
|
||||
if self.django_settings:
|
||||
settings_modules.append(self.django_settings)
|
||||
if self.django_settings_module:
|
||||
settings_modules.append(self.django_settings_module)
|
||||
return AddSettingValuesToDjangoConfObject(settings_modules,
|
||||
self.config.ignore_missing_settings)
|
||||
|
||||
@@ -153,6 +225,17 @@ class DjangoPlugin(Plugin):
|
||||
|
||||
return None
|
||||
|
||||
def get_attribute_hook(self, fullname: str
|
||||
) -> Optional[Callable[[AttributeContext], Type]]:
|
||||
module, _, name = fullname.rpartition('.')
|
||||
sym = self.lookup_fully_qualified('django.conf.LazySettings')
|
||||
if sym and isinstance(sym.node, TypeInfo):
|
||||
metadata = get_settings_metadata(sym.node)
|
||||
if module == 'builtins.object' and name in metadata:
|
||||
return ExtractSettingType(module_fullname=metadata[name])
|
||||
|
||||
return extract_and_return_primary_key_of_bound_related_field_parameter
|
||||
|
||||
|
||||
def plugin(version):
|
||||
return DjangoPlugin
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from typing import Dict, Optional, Set, cast, Any
|
||||
from typing import Dict, Optional, Set, cast
|
||||
|
||||
from mypy.checker import TypeChecker
|
||||
from mypy.nodes import FuncDef, TypeInfo, Var
|
||||
from mypy.nodes import TypeInfo, Var
|
||||
from mypy.plugin import FunctionContext, MethodContext
|
||||
from mypy.types import AnyType, CallableType, Instance, Type, TypeOfAny, UnionType
|
||||
from mypy.types import AnyType, Instance, Type, TypeOfAny, UnionType
|
||||
|
||||
from mypy_django_plugin import helpers
|
||||
from mypy_django_plugin.helpers import extract_field_setter_type, extract_primary_key_type_for_set, get_fields_metadata
|
||||
|
||||
|
||||
def extract_base_pointer_args(model: TypeInfo) -> Set[str]:
|
||||
@@ -69,11 +70,14 @@ def redefine_and_typecheck_model_init(ctx: FunctionContext) -> Type:
|
||||
def redefine_and_typecheck_model_create(ctx: MethodContext) -> Type:
|
||||
api = cast(TypeChecker, ctx.api)
|
||||
if isinstance(ctx.type, Instance) and len(ctx.type.args) > 0:
|
||||
model: TypeInfo = ctx.type.args[0].type
|
||||
model_generic_arg = ctx.type.args[0]
|
||||
else:
|
||||
if isinstance(ctx.default_return_type, AnyType):
|
||||
return ctx.default_return_type
|
||||
model: TypeInfo = ctx.default_return_type.type
|
||||
model_generic_arg = ctx.default_return_type
|
||||
|
||||
if isinstance(model_generic_arg, AnyType):
|
||||
return ctx.default_return_type
|
||||
|
||||
model: TypeInfo = model_generic_arg.type
|
||||
|
||||
# extract name of base models for _ptr
|
||||
base_pointer_args = extract_base_pointer_args(model)
|
||||
@@ -100,46 +104,6 @@ def redefine_and_typecheck_model_create(ctx: MethodContext) -> Type:
|
||||
return ctx.default_return_type
|
||||
|
||||
|
||||
def extract_field_setter_type(tp: Instance) -> Optional[Type]:
|
||||
if not isinstance(tp, Instance):
|
||||
return None
|
||||
if tp.type.has_base(helpers.FIELD_FULLNAME):
|
||||
set_method = tp.type.get_method('__set__')
|
||||
if isinstance(set_method, FuncDef) and isinstance(set_method.type, CallableType):
|
||||
if 'value' in set_method.type.arg_names:
|
||||
set_value_type = set_method.type.arg_types[set_method.type.arg_names.index('value')]
|
||||
if isinstance(set_value_type, Instance):
|
||||
set_value_type = helpers.fill_typevars(tp, set_value_type)
|
||||
return set_value_type
|
||||
elif isinstance(set_value_type, UnionType):
|
||||
items_no_typevars = []
|
||||
for item in set_value_type.items:
|
||||
if isinstance(item, Instance):
|
||||
item = helpers.fill_typevars(tp, item)
|
||||
items_no_typevars.append(item)
|
||||
return UnionType(items_no_typevars)
|
||||
|
||||
get_method = tp.type.get_method('__get__')
|
||||
if isinstance(get_method, FuncDef) and isinstance(get_method.type, CallableType):
|
||||
return get_method.type.ret_type
|
||||
# GenericForeignKey
|
||||
if tp.type.has_base(helpers.GENERIC_FOREIGN_KEY_FULLNAME):
|
||||
return AnyType(TypeOfAny.special_form)
|
||||
return None
|
||||
|
||||
|
||||
def get_fields_metadata(model: TypeInfo) -> Dict[str, Any]:
|
||||
return model.metadata.setdefault('django', {}).setdefault('fields', {})
|
||||
|
||||
|
||||
def extract_primary_key_type(model: TypeInfo) -> Optional[Type]:
|
||||
for field_name, props in get_fields_metadata(model).items():
|
||||
is_primary_key = props.get('primary_key', False)
|
||||
if is_primary_key:
|
||||
return extract_field_setter_type(model.names[field_name].type)
|
||||
return None
|
||||
|
||||
|
||||
def extract_choices_type(model: TypeInfo, field_name: str) -> Optional[str]:
|
||||
field_metadata = get_fields_metadata(model).get(field_name, {})
|
||||
if 'choices' in field_metadata:
|
||||
@@ -150,33 +114,39 @@ def extract_choices_type(model: TypeInfo, field_name: str) -> Optional[str]:
|
||||
def extract_expected_types(ctx: FunctionContext, model: TypeInfo) -> Dict[str, Type]:
|
||||
expected_types: Dict[str, Type] = {}
|
||||
|
||||
primary_key_type = extract_primary_key_type(model)
|
||||
primary_key_type = extract_primary_key_type_for_set(model)
|
||||
if not primary_key_type:
|
||||
# no explicit primary key, set pk to Any and add id
|
||||
primary_key_type = AnyType(TypeOfAny.special_form)
|
||||
expected_types['id'] = ctx.api.named_generic_type('builtins.int', [])
|
||||
|
||||
expected_types['pk'] = primary_key_type
|
||||
|
||||
for base in model.mro:
|
||||
for name, sym in base.names.items():
|
||||
if isinstance(sym.node, Var) and isinstance(sym.node.type, Instance):
|
||||
tp = sym.node.type
|
||||
field_type = extract_field_setter_type(tp)
|
||||
if field_type is None:
|
||||
continue
|
||||
# do not redefine special attrs
|
||||
if name in {'_meta', 'pk'}:
|
||||
continue
|
||||
if isinstance(sym.node, Var):
|
||||
if sym.node.type is None or isinstance(sym.node.type, AnyType):
|
||||
# types are not ready, fallback to Any
|
||||
expected_types[name] = AnyType(TypeOfAny.from_unimported_type)
|
||||
expected_types[name + '_id'] = AnyType(TypeOfAny.from_unimported_type)
|
||||
|
||||
choices_type_fullname = extract_choices_type(model, name)
|
||||
if choices_type_fullname:
|
||||
field_type = UnionType([field_type, ctx.api.named_generic_type(choices_type_fullname, [])])
|
||||
elif isinstance(sym.node.type, Instance):
|
||||
tp = sym.node.type
|
||||
field_type = extract_field_setter_type(tp)
|
||||
if field_type is None:
|
||||
continue
|
||||
|
||||
if tp.type.fullname() in {helpers.FOREIGN_KEY_FULLNAME, helpers.ONETOONE_FIELD_FULLNAME}:
|
||||
ref_to_model = tp.args[0]
|
||||
if isinstance(ref_to_model, Instance) and ref_to_model.type.has_base(helpers.MODEL_CLASS_FULLNAME):
|
||||
primary_key_type = extract_primary_key_type(ref_to_model.type)
|
||||
if not primary_key_type:
|
||||
primary_key_type = AnyType(TypeOfAny.special_form)
|
||||
if tp.type.fullname() in {helpers.FOREIGN_KEY_FULLNAME, helpers.ONETOONE_FIELD_FULLNAME}:
|
||||
ref_to_model = tp.args[0]
|
||||
primary_key_type = AnyType(TypeOfAny.special_form)
|
||||
if isinstance(ref_to_model, Instance) and ref_to_model.type.has_base(helpers.MODEL_CLASS_FULLNAME):
|
||||
typ = extract_primary_key_type_for_set(ref_to_model.type)
|
||||
if typ:
|
||||
primary_key_type = typ
|
||||
expected_types[name + '_id'] = primary_key_type
|
||||
if field_type:
|
||||
expected_types[name] = field_type
|
||||
if field_type:
|
||||
expected_types[name] = field_type
|
||||
|
||||
return expected_types
|
||||
|
||||
@@ -2,7 +2,7 @@ from abc import ABCMeta, abstractmethod
|
||||
from typing import Dict, Iterator, List, Optional, Tuple, cast
|
||||
|
||||
import dataclasses
|
||||
from mypy.nodes import ARG_STAR, ARG_STAR2, Argument, AssignmentStmt, CallExpr, ClassDef, Context, Expression, IndexExpr, \
|
||||
from mypy.nodes import ARG_STAR, ARG_STAR2, Argument, CallExpr, ClassDef, Context, Expression, IndexExpr, \
|
||||
Lvalue, MDEF, MemberExpr, MypyFile, NameExpr, StrExpr, SymbolTableNode, TypeInfo, Var
|
||||
from mypy.plugin import ClassDefContext
|
||||
from mypy.plugins.common import add_method
|
||||
@@ -10,6 +10,7 @@ from mypy.semanal import SemanticAnalyzerPass2
|
||||
from mypy.types import AnyType, Instance, NoneTyp, TypeOfAny
|
||||
|
||||
from mypy_django_plugin import helpers
|
||||
from mypy_django_plugin.helpers import iter_over_assignments
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
@@ -55,16 +56,6 @@ class ModelClassInitializer(metaclass=ABCMeta):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def iter_over_assignments(klass: ClassDef) -> Iterator[Tuple[Lvalue, Expression]]:
|
||||
for stmt in klass.defs.body:
|
||||
if not isinstance(stmt, AssignmentStmt):
|
||||
continue
|
||||
if len(stmt.lvalues) > 1:
|
||||
# not supported yet
|
||||
continue
|
||||
yield stmt.lvalues[0], stmt.rvalue
|
||||
|
||||
|
||||
def iter_call_assignments(klass: ClassDef) -> Iterator[Tuple[Lvalue, CallExpr]]:
|
||||
for lvalue, rvalue in iter_over_assignments(klass):
|
||||
if isinstance(rvalue, CallExpr):
|
||||
@@ -83,8 +74,11 @@ def iter_over_one_to_n_related_fields(klass: ClassDef) -> Iterator[Tuple[NameExp
|
||||
class SetIdAttrsForRelatedFields(ModelClassInitializer):
|
||||
def run(self) -> None:
|
||||
for lvalue, rvalue in iter_over_one_to_n_related_fields(self.model_classdef):
|
||||
self.add_new_node_to_model_class(lvalue.name + '_id',
|
||||
typ=self.api.named_type('__builtins__.int'))
|
||||
# base_model_info = self.api.named_type('builtins.object').type
|
||||
# helpers.get_related_field_primary_key_names(base_model_info).append(node_name)
|
||||
node_name = lvalue.name + '_id'
|
||||
self.add_new_node_to_model_class(name=node_name,
|
||||
typ=self.api.builtin_type('builtins.int'))
|
||||
|
||||
|
||||
class InjectAnyAsBaseForNestedMeta(ModelClassInitializer):
|
||||
@@ -188,15 +182,17 @@ class AddRelatedManagers(ModelClassInitializer):
|
||||
return None
|
||||
|
||||
if self.model_classdef.fullname == ref_to_fullname:
|
||||
related_manager_name = defn.name.lower() + '_set'
|
||||
if 'related_name' in rvalue.arg_names:
|
||||
related_name_expr = rvalue.args[rvalue.arg_names.index('related_name')]
|
||||
if not isinstance(related_name_expr, StrExpr):
|
||||
return None
|
||||
related_name = related_name_expr.value
|
||||
typ = get_related_field_type(rvalue, self.api, defn.info)
|
||||
if typ is None:
|
||||
return None
|
||||
self.add_new_node_to_model_class(related_name, typ)
|
||||
related_manager_name = related_name_expr.value
|
||||
|
||||
typ = get_related_field_type(rvalue, self.api, defn.info)
|
||||
if typ is None:
|
||||
return None
|
||||
self.add_new_node_to_model_class(related_manager_name, typ)
|
||||
|
||||
|
||||
def iter_over_classdefs(module_file: MypyFile) -> Iterator[ClassDef]:
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import List, Optional, cast
|
||||
from typing import Iterable, List, Optional, cast
|
||||
|
||||
from mypy.nodes import ClassDef, Context, MypyFile, SymbolNode, SymbolTableNode, Var
|
||||
from mypy.nodes import ClassDef, Context, ImportAll, MypyFile, SymbolNode, SymbolTableNode, TypeInfo, Var
|
||||
from mypy.plugin import ClassDefContext
|
||||
from mypy.semanal import SemanticAnalyzerPass2
|
||||
from mypy.types import Instance, NoneTyp, Type, UnionType
|
||||
from mypy.types import AnyType, Instance, NoneTyp, Type, TypeOfAny, UnionType
|
||||
from mypy.util import correct_relative_import
|
||||
|
||||
|
||||
def get_error_context(node: SymbolNode) -> Context:
|
||||
@@ -36,14 +37,44 @@ def make_sym_copy_of_setting(sym: SymbolTableNode) -> Optional[SymbolTableNode]:
|
||||
return None
|
||||
|
||||
|
||||
def load_settings_from_module(settings_classdef: ClassDef, module: MypyFile) -> None:
|
||||
for name, sym in module.names.items():
|
||||
if name.isupper() and isinstance(sym.node, Var):
|
||||
if sym.type is not None:
|
||||
copied = make_sym_copy_of_setting(sym)
|
||||
if copied is None:
|
||||
continue
|
||||
settings_classdef.info.names[name] = copied
|
||||
def get_settings_metadata(lazy_settings_info: TypeInfo):
|
||||
return lazy_settings_info.metadata.setdefault('django', {}).setdefault('settings', {})
|
||||
|
||||
|
||||
def load_settings_from_names(settings_classdef: ClassDef,
|
||||
modules: Iterable[MypyFile],
|
||||
api: SemanticAnalyzerPass2) -> None:
|
||||
settings_metadata = get_settings_metadata(settings_classdef.info)
|
||||
|
||||
for module in modules:
|
||||
for name, sym in module.names.items():
|
||||
if name.isupper() and isinstance(sym.node, Var):
|
||||
if sym.type is not None:
|
||||
copied = make_sym_copy_of_setting(sym)
|
||||
if copied is None:
|
||||
continue
|
||||
settings_classdef.info.names[name] = copied
|
||||
else:
|
||||
var = Var(name, AnyType(TypeOfAny.unannotated))
|
||||
var.info = api.named_type('__builtins__.object').type
|
||||
settings_classdef.info.names[name] = SymbolTableNode(sym.kind, var)
|
||||
|
||||
settings_metadata[name] = module.fullname()
|
||||
|
||||
|
||||
def get_import_star_modules(api: SemanticAnalyzerPass2, module: MypyFile) -> List[str]:
|
||||
import_star_modules = []
|
||||
for module_import in module.imports:
|
||||
# relative import * are not resolved by mypy
|
||||
if isinstance(module_import, ImportAll) and module_import.relative:
|
||||
absolute_import_path, correct = correct_relative_import(module.fullname(), module_import.relative, module_import.id,
|
||||
is_cur_package_init_file=False)
|
||||
if not correct:
|
||||
return []
|
||||
for path in [absolute_import_path] + get_import_star_modules(api, module=api.modules.get(absolute_import_path)):
|
||||
if path not in import_star_modules:
|
||||
import_star_modules.append(path)
|
||||
return import_star_modules
|
||||
|
||||
|
||||
class AddSettingValuesToDjangoConfObject:
|
||||
@@ -55,7 +86,9 @@ class AddSettingValuesToDjangoConfObject:
|
||||
api = cast(SemanticAnalyzerPass2, ctx.api)
|
||||
for module_name in self.settings_modules:
|
||||
module = api.modules[module_name]
|
||||
load_settings_from_module(ctx.cls, module=module)
|
||||
star_deps = [api.modules[star_dep]
|
||||
for star_dep in reversed(get_import_star_modules(api, module))]
|
||||
load_settings_from_names(ctx.cls, modules=star_deps + [module], api=api)
|
||||
|
||||
if self.ignore_missing_settings:
|
||||
ctx.cls.info.fallback_to_any = True
|
||||
|
||||
4
release.xsh
Normal file
4
release.xsh
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/local/bin/xonsh
|
||||
|
||||
python setup.py sdist
|
||||
twine upload dist/*
|
||||
@@ -19,7 +19,7 @@ DJANGO_COMMIT_SHA = '03219b5f709dcd5b0bfacd963508625557ec1ef0'
|
||||
|
||||
# Some errors occur for the test suite itself, and cannot be addressed via django-stubs. They should be ignored
|
||||
# using this constant.
|
||||
MOCK_OBJECTS = ['MockRequest', 'MockCompiler', 'modelz', 'call_count', 'call_args_list', 'call_args']
|
||||
MOCK_OBJECTS = ['MockRequest', 'MockCompiler', 'modelz', 'call_count', 'call_args_list', 'call_args', 'MockUser']
|
||||
IGNORED_ERRORS = {
|
||||
'__common__': [
|
||||
*MOCK_OBJECTS,
|
||||
@@ -28,6 +28,7 @@ IGNORED_ERRORS = {
|
||||
'Need type annotation for',
|
||||
'Invalid value for a to= parameter',
|
||||
'already defined (possibly by an import)',
|
||||
'gets multiple values for keyword argument',
|
||||
'Cannot assign to a type',
|
||||
re.compile(r'Cannot assign to class variable "[a-z_]+" via instance'),
|
||||
# forms <-> models plugin support
|
||||
@@ -135,6 +136,9 @@ IGNORED_ERRORS = {
|
||||
'dispatch': [
|
||||
'Argument 1 to "connect" of "Signal" has incompatible type "object"; expected "Callable[..., Any]"'
|
||||
],
|
||||
'deprecation': [
|
||||
re.compile('"(old|new)" undefined in superclass')
|
||||
],
|
||||
'db_typecasts': [
|
||||
'"object" has no attribute "__iter__"; maybe "__str__" or "__dir__"? (not iterable)'
|
||||
],
|
||||
@@ -147,13 +151,18 @@ IGNORED_ERRORS = {
|
||||
'field_deconstruction': [
|
||||
'Incompatible types in assignment (expression has type "ForeignKey[Any]", variable has type "CharField")'
|
||||
],
|
||||
'file_uploads': [
|
||||
'"handle_uncaught_exception" undefined in superclass'
|
||||
],
|
||||
'fixtures': [
|
||||
'Incompatible types in assignment (expression has type "int", target has type "Iterable[str]")'
|
||||
],
|
||||
'get_object_or_404': [
|
||||
'Argument 1 to "get_object_or_404" has incompatible type "str"; '
|
||||
+ 'expected "Union[Type[Model], Manager[Any], QuerySet[Any]]"',
|
||||
'Argument 1 to "get_object_or_404" has incompatible type "Type[CustomClass]"; '
|
||||
+ 'expected "Union[Type[Model], Manager[Any], QuerySet[Any]]"',
|
||||
+ 'expected "Union[Type[<nothing>], Manager[<nothing>], QuerySet[<nothing>]]"',
|
||||
'Argument 1 to "get_list_or_404" has incompatible type "List[Type[Article]]"; '
|
||||
+ 'expected "Union[Type[Model], Manager[Any], QuerySet[Any]]"'
|
||||
+ 'expected "Union[Type[<nothing>], Manager[<nothing>], QuerySet[<nothing>]]"',
|
||||
'CustomClass'
|
||||
],
|
||||
'get_or_create': [
|
||||
'Argument 1 to "update_or_create" of "QuerySet" has incompatible type "**Dict[str, object]"; expected "MutableMapping[str, Any]"'
|
||||
@@ -165,6 +174,9 @@ IGNORED_ERRORS = {
|
||||
'Argument "max_length" to "CharField" has incompatible type "str"; expected "Optional[int]"',
|
||||
'Argument "choices" to "CharField" has incompatible type "str"'
|
||||
],
|
||||
'logging_tests': [
|
||||
re.compile('"(setUpClass|tearDownClass)" undefined in superclass')
|
||||
],
|
||||
'model_inheritance_regress': [
|
||||
'Incompatible types in assignment (expression has type "List[Supplier]", variable has type "QuerySet[Supplier]")'
|
||||
],
|
||||
@@ -176,6 +188,8 @@ IGNORED_ERRORS = {
|
||||
'Incompatible types in assignment (expression has type "Type[Person]", variable has type',
|
||||
'Unexpected keyword argument "name" for "Person"',
|
||||
'Cannot assign multiple types to name "PersonTwoImages" without an explicit "Type[...]" annotation',
|
||||
'Incompatible types in assignment (expression has type "Type[Person]", '
|
||||
+ 'base class "ImageFieldTestMixin" defined the type as "Type[PersonWithHeightAndWidth]")'
|
||||
],
|
||||
'model_regress': [
|
||||
'Too many arguments for "Worker"',
|
||||
@@ -201,6 +215,13 @@ IGNORED_ERRORS = {
|
||||
'FakeLoader',
|
||||
'Argument 1 to "append" of "list" has incompatible type "AddIndex"; expected "CreateModel"'
|
||||
],
|
||||
'middleware_exceptions': [
|
||||
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any]"; expected "str"'
|
||||
],
|
||||
'multiple_database': [
|
||||
'Unexpected attribute "extra_arg" for model "Book"',
|
||||
'Too many arguments for "create" of "QuerySet"'
|
||||
],
|
||||
'queryset_pickle': [
|
||||
'"None" has no attribute "somefield"'
|
||||
],
|
||||
@@ -295,6 +316,13 @@ IGNORED_ERRORS = {
|
||||
'select_related_onetoone': [
|
||||
'"None" has no attribute'
|
||||
],
|
||||
'servers': [
|
||||
re.compile('Argument [0-9] to "WSGIRequestHandler"')
|
||||
],
|
||||
'sitemaps_tests': [
|
||||
'Incompatible types in assignment (expression has type "str", '
|
||||
+ 'base class "Sitemap" defined the type as "Callable[[Sitemap, Model], str]")'
|
||||
],
|
||||
'view_tests': [
|
||||
'"Handler" has no attribute "include_html"',
|
||||
'"EmailMessage" has no attribute "alternatives"'
|
||||
@@ -329,10 +357,10 @@ TESTS_DIRS = [
|
||||
'builtin_server',
|
||||
'bulk_create',
|
||||
# TODO: 'cache',
|
||||
# TODO: 'check_framework',
|
||||
'check_framework',
|
||||
'choices',
|
||||
'conditional_processing',
|
||||
# TODO: 'contenttypes_tests',
|
||||
'contenttypes_tests',
|
||||
'context_processors',
|
||||
'csrf_tests',
|
||||
'custom_columns',
|
||||
@@ -348,36 +376,36 @@ TESTS_DIRS = [
|
||||
'db_typecasts',
|
||||
'db_utils',
|
||||
'dbshell',
|
||||
# TODO: 'decorators',
|
||||
'decorators',
|
||||
'defer',
|
||||
# TODO: 'defer_regress',
|
||||
'defer_regress',
|
||||
'delete',
|
||||
'delete_regress',
|
||||
# TODO: 'deprecation',
|
||||
'deprecation',
|
||||
'dispatch',
|
||||
'distinct_on_fields',
|
||||
'empty',
|
||||
'expressions',
|
||||
'expressions_case',
|
||||
# TODO: 'expressions_window',
|
||||
'expressions_window',
|
||||
# TODO: 'extra_regress',
|
||||
'field_deconstruction',
|
||||
'field_defaults',
|
||||
'field_subclassing',
|
||||
# TODO: 'file_storage',
|
||||
# TODO: 'file_uploads',
|
||||
'file_uploads',
|
||||
# TODO: 'files',
|
||||
'filtered_relation',
|
||||
# TODO: 'fixtures',
|
||||
'fixtures',
|
||||
'fixtures_model_package',
|
||||
# TODO: 'fixtures_regress',
|
||||
# TODO: 'flatpages_tests',
|
||||
'fixtures_regress',
|
||||
'flatpages_tests',
|
||||
'force_insert_update',
|
||||
'foreign_object',
|
||||
# TODO: 'forms_tests',
|
||||
'from_db_value',
|
||||
# TODO: 'generic_inline_admin',
|
||||
# TODO: 'generic_relations',
|
||||
'generic_inline_admin',
|
||||
'generic_relations',
|
||||
'generic_relations_regress',
|
||||
# TODO: 'generic_views',
|
||||
'get_earliest_or_latest',
|
||||
@@ -398,7 +426,7 @@ TESTS_DIRS = [
|
||||
# 'invalid_models_tests',
|
||||
|
||||
'known_related_objects',
|
||||
# TODO: 'logging_tests',
|
||||
'logging_tests',
|
||||
'lookup',
|
||||
'm2m_and_m2o',
|
||||
'm2m_intermediary',
|
||||
@@ -416,13 +444,13 @@ TESTS_DIRS = [
|
||||
'many_to_one_null',
|
||||
'max_lengths',
|
||||
# TODO: 'messages_tests',
|
||||
# TODO: 'middleware',
|
||||
# TODO: 'middleware_exceptions',
|
||||
'middleware',
|
||||
'middleware_exceptions',
|
||||
'migrate_signals',
|
||||
'migration_test_data_persistence',
|
||||
'migrations',
|
||||
'migrations2',
|
||||
# TODO: 'model_fields',
|
||||
'model_fields',
|
||||
# TODO: 'model_forms',
|
||||
'model_formsets',
|
||||
'model_formsets_regress',
|
||||
@@ -434,7 +462,7 @@ TESTS_DIRS = [
|
||||
'model_package',
|
||||
'model_regress',
|
||||
'modeladmin',
|
||||
# TODO: 'multiple_database',
|
||||
'multiple_database',
|
||||
'mutually_referential',
|
||||
'nested_foreign_keys',
|
||||
'no_models',
|
||||
@@ -452,7 +480,7 @@ TESTS_DIRS = [
|
||||
'properties',
|
||||
'proxy_model_inheritance',
|
||||
# TODO: 'proxy_models',
|
||||
# TODO: 'queries',
|
||||
'queries',
|
||||
'queryset_pickle',
|
||||
'raw_query',
|
||||
'redirects_tests',
|
||||
@@ -468,7 +496,7 @@ TESTS_DIRS = [
|
||||
'select_related_onetoone',
|
||||
'select_related_regress',
|
||||
# TODO: 'serializers',
|
||||
# TODO: 'servers',
|
||||
'servers',
|
||||
'sessions_tests',
|
||||
'settings_tests',
|
||||
'shell',
|
||||
@@ -504,8 +532,7 @@ TESTS_DIRS = [
|
||||
# TODO: 'urlpatterns_reverse',
|
||||
'user_commands',
|
||||
# TODO: 'utils_tests',
|
||||
# not annotatable without annotation in test
|
||||
# TODO: 'validation',
|
||||
'validation',
|
||||
'validators',
|
||||
'version',
|
||||
'view_tests',
|
||||
|
||||
2
setup.py
2
setup.py
@@ -31,7 +31,7 @@ if sys.version_info[:2] < (3, 7):
|
||||
|
||||
setup(
|
||||
name="django-stubs",
|
||||
version="0.3.0",
|
||||
version="0.5.0",
|
||||
description='Django mypy stubs',
|
||||
long_description=readme,
|
||||
long_description_content_type='text/markdown',
|
||||
|
||||
@@ -20,4 +20,16 @@ django_settings = mysettings
|
||||
|
||||
[file mysettings.py]
|
||||
MY_SETTING: int = 1
|
||||
[out]
|
||||
[out]
|
||||
|
||||
[CASE mypy_django_ini_in_current_directory_is_a_default]
|
||||
from django.conf import settings
|
||||
reveal_type(settings.MY_SETTING) # E: Revealed type is 'builtins.int'
|
||||
|
||||
[file mypy_django.ini]
|
||||
[[mypy_django_plugin]
|
||||
django_settings = mysettings
|
||||
|
||||
[file mysettings.py]
|
||||
MY_SETTING: int = 1
|
||||
[out]
|
||||
|
||||
@@ -155,3 +155,19 @@ class MyModel(models.Model):
|
||||
day = models.CharField(max_length=3, choices=((1, 'Fri'), (2, 'Sat')))
|
||||
MyModel(day=1)
|
||||
[out]
|
||||
|
||||
[CASE if_there_is_no_data_for_base_classes_of_fields_and_ignore_unresolved_attributes_set_to_true_to_not_fail]
|
||||
from decimal import Decimal
|
||||
from django.db import models
|
||||
from fields2 import MoneyField
|
||||
|
||||
class InvoiceRow(models.Model):
|
||||
base_amount = MoneyField(max_digits=10, decimal_places=2)
|
||||
vat_rate = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
InvoiceRow(1, Decimal(0), Decimal(0))
|
||||
InvoiceRow(base_amount=Decimal(0), vat_rate=Decimal(0))
|
||||
InvoiceRow.objects.create(base_amount=Decimal(0), vat_rate=Decimal(0))
|
||||
[out]
|
||||
main:3: error: Cannot find module named 'fields2'
|
||||
main:3: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
|
||||
|
||||
|
||||
@@ -22,13 +22,11 @@ class Publisher(models.Model):
|
||||
|
||||
class Book(models.Model):
|
||||
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
|
||||
class StylesheetError(Exception):
|
||||
pass
|
||||
owner = models.ForeignKey(db_column='model_id', to='db.Unknown', on_delete=models.CASCADE)
|
||||
|
||||
book = Book()
|
||||
reveal_type(book.publisher_id) # E: Revealed type is 'builtins.int'
|
||||
reveal_type(book.owner_id) # E: Revealed type is 'builtins.int'
|
||||
reveal_type(book.owner_id) # E: Revealed type is 'Any'
|
||||
|
||||
[CASE test_foreign_key_field_different_order_of_params]
|
||||
from django.db import models
|
||||
@@ -68,7 +66,7 @@ from django.db import models
|
||||
class Publisher(models.Model):
|
||||
pass
|
||||
|
||||
[CASE test_to_parameter_as_string_with_application_name__fallbacks_to_any_if_model_not_present_in_dependency_graph]
|
||||
[CASE test_to_parameter_as_string_with_application_name_fallbacks_to_any_if_model_not_present_in_dependency_graph]
|
||||
from django.db import models
|
||||
|
||||
class Book(models.Model):
|
||||
@@ -76,6 +74,9 @@ class Book(models.Model):
|
||||
|
||||
book = Book()
|
||||
reveal_type(book.publisher) # E: Revealed type is 'Any'
|
||||
reveal_type(book.publisher_id) # E: Revealed type is 'Any'
|
||||
Book(publisher_id=1)
|
||||
Book.objects.create(publisher_id=1)
|
||||
|
||||
[file myapp/__init__.py]
|
||||
[file myapp/models.py]
|
||||
@@ -239,3 +240,38 @@ class ParkingSpot(BaseModel):
|
||||
class Booking(BaseModel):
|
||||
parking_spot = models.ForeignKey(to=ParkingSpot, null=True, on_delete=models.SET_NULL)
|
||||
[out]
|
||||
|
||||
[CASE if_no_related_name_is_passed_create_default_related_managers]
|
||||
from django.db import models
|
||||
class Publisher(models.Model):
|
||||
pass
|
||||
class Book(models.Model):
|
||||
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
|
||||
reveal_type(Publisher().book_set) # E: Revealed type is 'django.db.models.manager.RelatedManager[main.Book]'
|
||||
|
||||
[CASE underscore_id_attribute_has_set_type_of_primary_key_if_explicit]
|
||||
from django.db import models
|
||||
import datetime
|
||||
class Publisher(models.Model):
|
||||
mypk = models.CharField(max_length=100, primary_key=True)
|
||||
class Book(models.Model):
|
||||
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
|
||||
|
||||
reveal_type(Book().publisher_id) # E: Revealed type is 'builtins.str'
|
||||
Book(publisher_id=1)
|
||||
Book(publisher_id='hello')
|
||||
Book(publisher_id=datetime.datetime.now()) # E: Incompatible type for "publisher_id" of "Book" (got "datetime", expected "Union[str, int, Combinable]")
|
||||
Book.objects.create(publisher_id=1)
|
||||
Book.objects.create(publisher_id='hello')
|
||||
|
||||
class Publisher2(models.Model):
|
||||
mypk = models.IntegerField(primary_key=True)
|
||||
class Book2(models.Model):
|
||||
publisher = models.ForeignKey(to=Publisher2, on_delete=models.CASCADE)
|
||||
|
||||
reveal_type(Book2().publisher_id) # E: Revealed type is 'builtins.int'
|
||||
Book2(publisher_id=1)
|
||||
Book2(publisher_id='hello') # E: Incompatible type for "publisher_id" of "Book2" (got "str", expected "Union[int, Combinable, Literal['']]")
|
||||
Book2.objects.create(publisher_id=1)
|
||||
Book2.objects.create(publisher_id='hello') # E: Incompatible type for "publisher_id" of "Book2" (got "str", expected "Union[int, Combinable, Literal['']]")
|
||||
[out]
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
from django.conf import settings
|
||||
|
||||
reveal_type(settings.ROOT_DIR) # E: Revealed type is 'builtins.str'
|
||||
reveal_type(settings.APPS_DIR) # E: Revealed type is 'pathlib.Path'
|
||||
reveal_type(settings.OBJ) # E: Revealed type is 'django.utils.functional.LazyObject'
|
||||
reveal_type(settings.NUMBERS) # E: Revealed type is 'builtins.list[builtins.str]'
|
||||
reveal_type(settings.DICT) # E: Revealed type is 'builtins.dict[Any, Any]'
|
||||
[env DJANGO_SETTINGS_MODULE=mysettings]
|
||||
[file mysettings.py]
|
||||
SECRET_KEY = 112233
|
||||
[file base.py]
|
||||
from pathlib import Path
|
||||
ROOT_DIR = '/etc'
|
||||
APPS_DIR = Path(ROOT_DIR)
|
||||
[file mysettings.py]
|
||||
from base import *
|
||||
SECRET_KEY = 112233
|
||||
NUMBERS = ['one', 'two']
|
||||
DICT = {} # type: ignore
|
||||
from django.utils.functional import LazyObject
|
||||
|
||||
56
test-data/typecheck/shortcuts.test
Normal file
56
test-data/typecheck/shortcuts.test
Normal file
@@ -0,0 +1,56 @@
|
||||
[CASE get_object_or_404_returns_proper_types]
|
||||
from django.shortcuts import get_object_or_404, get_list_or_404
|
||||
from django.db import models
|
||||
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
reveal_type(get_object_or_404(MyModel)) # E: Revealed type is 'main.MyModel*'
|
||||
reveal_type(get_object_or_404(MyModel.objects)) # E: Revealed type is 'main.MyModel*'
|
||||
reveal_type(get_object_or_404(MyModel.objects.get_queryset())) # E: Revealed type is 'main.MyModel*'
|
||||
|
||||
reveal_type(get_list_or_404(MyModel)) # E: Revealed type is 'builtins.list[main.MyModel*]'
|
||||
reveal_type(get_list_or_404(MyModel.objects)) # E: Revealed type is 'builtins.list[main.MyModel*]'
|
||||
reveal_type(get_list_or_404(MyModel.objects.get_queryset())) # E: Revealed type is 'builtins.list[main.MyModel*]'
|
||||
[out]
|
||||
|
||||
[CASE get_user_model_returns_proper_class]
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from myapp.models import MyUser
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
UserModel = get_user_model()
|
||||
reveal_type(UserModel.objects) # E: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyUser]'
|
||||
|
||||
[env DJANGO_SETTINGS_MODULE=mysettings]
|
||||
[file mysettings.py]
|
||||
INSTALLED_APPS = ('myapp',)
|
||||
AUTH_USER_MODEL = 'myapp.MyUser'
|
||||
|
||||
[file myapp/__init__.py]
|
||||
[file myapp/models.py]
|
||||
from django.db import models
|
||||
class MyUser(models.Model):
|
||||
pass
|
||||
[out]
|
||||
|
||||
[CASE return_type_model_and_show_error_if_model_not_yet_imported]
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
UserModel = get_user_model()
|
||||
reveal_type(UserModel.objects)
|
||||
|
||||
[env DJANGO_SETTINGS_MODULE=mysettings]
|
||||
[file mysettings.py]
|
||||
INSTALLED_APPS = ('myapp',)
|
||||
AUTH_USER_MODEL = 'myapp.MyUser'
|
||||
|
||||
[file myapp/__init__.py]
|
||||
[file myapp/models.py]
|
||||
from django.db import models
|
||||
class MyUser(models.Model):
|
||||
pass
|
||||
[out]
|
||||
main:3: error: "myapp.MyUser" model class is not imported so far. Try to import it (under if TYPE_CHECKING) at the beginning of the current file
|
||||
main:4: error: Revealed type is 'Any'
|
||||
main:4: error: "Type[Model]" has no attribute "objects"
|
||||
Reference in New Issue
Block a user