* Fix CI

* Fix CI

* Fix CI

* Fix CI

* APply black

* APply black

* Fix mypy

* Fix mypy errors in django-stubs

* Fix format

* Fix plugin

* Do not patch builtins by default

* Fix mypy

* Only run mypy on 3.10 for now

* Only run mypy on 3.10 for now

* WHAT THE HELL

* Enable strict mode in mypy

* Enable strict mode in mypy

* Fix tests

* Fix tests

* Debug

* Debug

* Fix tests

* Fix tests

* Add TYPE_CHECKING debug

* Caching maybe?

* Caching maybe?

* Try explicit `${{ matrix.python-version }}`

* Remove debug

* Fix typing

* Finally
This commit is contained in:
Nikita Sobolev
2022-08-26 13:22:55 +03:00
committed by GitHub
parent d2bfd3710b
commit 0bb1182c42
80 changed files with 223 additions and 582 deletions

View File

@@ -1,78 +0,0 @@
# Please, keep it in sync with `release-django-stubs-ext.yml`
name: Release django-stubs-ext
on:
push:
tags:
- 'django-stubs-ext@*'
jobs:
build:
environment:
name: django-stubs-ext-pypi
url: https://pypi.org/project/django-stubs-ext
if: github.repository == 'typeddjango/django-stubs'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install -U setuptools twine wheel
- name: Build package
run: |
cd ./django_stubs_ext
python setup.py --version
python setup.py sdist bdist_wheel
ls dist
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.DJANGO_STUBS_EXT_PYPI_API_TOKEN }}
packages_dir: django_stubs_ext/dist
print_hash: true
- name: Create release
uses: actions/github-script@v6
with:
script: |
const tagName = context.ref.replace(/^refs\/tags\//, '');
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
name: `Release ${tagName}`,
generate_release_notes: false,
});
if (release.status < 200 || release.status >= 300) {
core.setFailed(`Could not create release for tag '${tagName}'`);
return;
}
# https://github.community/t/run-github-actions-job-only-if-previous-job-has-failed/174786/2
create-issue-on-failure:
name: Create an issue if release failed
runs-on: ubuntu-latest
needs: [build]
if: ${{ github.repository == 'typeddjango/django-stubs' && always() && needs.build.result == 'failure' }}
permissions:
issues: write
steps:
- uses: actions/github-script@v6
with:
script: |
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Release of django-stubs-ext failure on ${new Date().toDateString()}`,
body: `Details: https://github.com/typeddjango/django-stubs/actions/workflows/release-django-stubs-ext.yml`,
})

View File

@@ -1,76 +0,0 @@
# Please, keep it in sync with `release-django-stubs-ext.yml`
name: Release django-stubs
on:
push:
tags:
- 'django-stubs@*'
jobs:
build:
environment:
name: django-stubs-pypi
url: https://pypi.org/project/django-stubs
if: github.repository == 'typeddjango/django-stubs'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install -U setuptools twine wheel
- name: Build package
run: |
python setup.py --version
python setup.py sdist bdist_wheel
ls dist
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.DJANGO_STUBS_PYPI_API_TOKEN }}
print_hash: true
- name: Create release
uses: actions/github-script@v6
with:
script: |
const tagName = context.ref.replace(/^refs\/tags\//, '');
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
name: `Release ${tagName}`,
generate_release_notes: true,
});
if (release.status < 200 || release.status >= 300) {
core.setFailed(`Could not create release for tag '${tagName}'`);
return;
}
# https://github.community/t/run-github-actions-job-only-if-previous-job-has-failed/174786/2
create-issue-on-failure:
name: Create an issue if release failed
runs-on: ubuntu-latest
needs: [build]
if: ${{ github.repository == 'typeddjango/django-stubs' && always() && needs.build.result == 'failure' }}
permissions:
issues: write
steps:
- uses: actions/github-script@v6
with:
script: |
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Release of django-stubs failure on ${new Date().toDateString()}`,
body: `Details: https://github.com/typeddjango/django-stubs/actions/workflows/release-django-stubs.yml`,
})

View File

@@ -27,11 +27,40 @@ jobs:
- name: Run pre-commit - name: Run pre-commit
run: pre-commit install && pre-commit run --all-files run: pre-commit install && pre-commit run --all-files
mypy-self-check:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10']
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install -U pip setuptools wheel
pip install -r ./requirements.txt
- name: Run mypy on plugin code
run: mypy --strict mypy_django_plugin
- name: Run mypy on ext code
run: mypy --strict django_stubs_ext
- name: Run mypy on scripts and utils
run: mypy --strict scripts
# TODO: run this check on versions, not only 3.10
- name: Run mypy on stubs
if: ${{ matrix.python-version }} == '3.10'
run: |
mypy --cache-dir=/dev/null --no-incremental django-stubs
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: ['3.8'] python-version: ['3.9']
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup system dependencies - name: Setup system dependencies
@@ -46,7 +75,7 @@ jobs:
pip install -r ./requirements.txt pip install -r ./requirements.txt
- name: Run tests - name: Run tests
run: pytest --mypy-ini-file=mypy.ini run: PYTHONPATH='.' pytest
typecheck: typecheck:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -16,17 +16,15 @@ repos:
rev: v2.37.3 rev: v2.37.3
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: ["--py36-plus"] args: ["--py37-plus"]
- repo: https://github.com/pre-commit/mirrors-isort - repo: https://github.com/pycqa/isort
rev: v5.10.1 rev: 5.10.1
hooks: hooks:
- id: isort - id: isort
name: isort (python) name: isort (python)
args: ["--profile", "black"]
- id: isort - id: isort
name: isort (pyi) name: isort (pyi)
types: [pyi] types: [pyi]
args: ["--profile", "black"]
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 22.6.0 rev: 22.6.0
hooks: hooks:
@@ -35,19 +33,3 @@ repos:
rev: 3.9.2 rev: 3.9.2
hooks: hooks:
- id: flake8 - id: flake8
- repo: local
hooks:
- id: mypy
name: mypy
entry: mypy
language: system
types: [ python ]
exclude: "scripts/|django_stubs_ext/"
args: [ "--config=mypy.ini", "--cache-dir=/dev/null", "--no-incremental" ]
- id: mypy
name: mypy (django_stubs_ext)
entry: mypy
language: system
types: [ python ]
files: "django_stubs_ext/|django_stubs_ext/tests/"
args: [ "--config=mypy.ini", "--cache-dir=/dev/null", "--no-incremental", "--strict" ]

View File

@@ -116,6 +116,8 @@ This happens because these Django classes do not support [`__class_getitem__`](h
Note: This monkey patching approach will only work when using Python 3.7 and higher, when the `__class_getitem__` magic method was introduced. Note: This monkey patching approach will only work when using Python 3.7 and higher, when the `__class_getitem__` magic method was introduced.
You can add extra types to patch with `django_stubs_ext.monkeypatch(extra_classes=[YourDesiredType])`
2. You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, this way it will work as a type for `mypy` and as a regular `str` in runtime. 2. You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, this way it will work as a type for `mypy` and as a regular `str` in runtime.
### How can I create a HttpRequest that's guaranteed to have an authenticated user? ### How can I create a HttpRequest that's guaranteed to have an authenticated user?

View File

@@ -1,4 +1,3 @@
import sys
from typing import Any, Callable, Dict, Iterable, Iterator, List, Mapping, Optional, Sequence, Tuple, Union from typing import Any, Callable, Dict, Iterable, Iterator, List, Mapping, Optional, Sequence, Tuple, Union
from django import forms from django import forms
@@ -10,11 +9,7 @@ from django.forms.models import ModelForm
from django.forms.utils import ErrorDict, ErrorList from django.forms.utils import ErrorDict, ErrorList
from django.forms.widgets import Media, Widget from django.forms.widgets import Media, Widget
from django.utils.safestring import SafeString from django.utils.safestring import SafeString
from typing_extensions import TypedDict
if sys.version_info < (3, 8):
from typing_extensions import TypedDict
else:
from typing import TypedDict
ACTION_CHECKBOX_NAME: str ACTION_CHECKBOX_NAME: str

View File

@@ -1,4 +1,3 @@
import sys
from typing import ( from typing import (
Any, Any,
Callable, Callable,
@@ -43,11 +42,7 @@ from django.template.response import _TemplateForResponseT
from django.urls.resolvers import URLPattern from django.urls.resolvers import URLPattern
from django.utils.datastructures import _ListOrTuple from django.utils.datastructures import _ListOrTuple
from django.utils.safestring import SafeString from django.utils.safestring import SafeString
from typing_extensions import Literal, TypedDict
if sys.version_info < (3, 8):
from typing_extensions import Literal, TypedDict
else:
from typing import Literal, TypedDict
IS_POPUP_VAR: str IS_POPUP_VAR: str
TO_FIELD_VAR: str TO_FIELD_VAR: str

View File

@@ -1,5 +1,4 @@
import datetime import datetime
import sys
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Type, Union, overload from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Type, Union, overload
from uuid import UUID from uuid import UUID
@@ -15,11 +14,7 @@ from django.forms.forms import BaseForm
from django.forms.formsets import BaseFormSet from django.forms.formsets import BaseFormSet
from django.http.request import HttpRequest from django.http.request import HttpRequest
from django.utils.datastructures import _IndexableCollection from django.utils.datastructures import _IndexableCollection
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
class FieldIsAForeignKeyColumnName(Exception): ... class FieldIsAForeignKeyColumnName(Exception): ...

View File

@@ -1,4 +1,3 @@
import sys
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type, Union from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type, Union
from django.contrib.admin.filters import ListFilter from django.contrib.admin.filters import ListFilter
@@ -11,11 +10,7 @@ from django.db.models.options import Options
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.forms.formsets import BaseFormSet from django.forms.formsets import BaseFormSet
from django.http.request import HttpRequest from django.http.request import HttpRequest
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
ALL_VAR: str ALL_VAR: str
ORDER_VAR: str ORDER_VAR: str

View File

@@ -1,15 +1,10 @@
import sys
from typing import Any, List, Optional, Tuple, TypeVar, Union, overload from typing import Any, List, Optional, Tuple, TypeVar, Union, overload
from django.db import models from django.db import models
from django.db.models.base import Model from django.db.models.base import Model
from django.db.models.expressions import Combinable from django.db.models.expressions import Combinable
from django.db.models.fields import BooleanField from django.db.models.fields import BooleanField
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
_T = TypeVar("_T", bound=Model) _T = TypeVar("_T", bound=Model)

View File

@@ -1,4 +1,3 @@
import sys
from typing import Any, Iterable, Optional, Set, Tuple, Type, TypeVar, Union from typing import Any, Iterable, Optional, Set, Tuple, Type, TypeVar, Union
from django.contrib.auth.base_user import AbstractBaseUser as AbstractBaseUser from django.contrib.auth.base_user import AbstractBaseUser as AbstractBaseUser
@@ -8,11 +7,7 @@ from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.db.models.base import Model from django.db.models.base import Model
from django.db.models.manager import EmptyManager from django.db.models.manager import EmptyManager
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
_AnyUser = Union[Model, "AnonymousUser"] _AnyUser = Union[Model, "AnonymousUser"]

View File

@@ -1,4 +1,4 @@
from django.core import validators from django.core.validators import RegexValidator
class ASCIIUsernameValidator(validators.RegexValidator): ... class ASCIIUsernameValidator(RegexValidator): ...
class UnicodeUsernameValidator(validators.RegexValidator): ... class UnicodeUsernameValidator(RegexValidator): ...

View File

@@ -1,4 +1,4 @@
from typing import Any, Dict, List, Optional, Tuple, Type from typing import Any, Dict, List, Tuple, Type
from django.apps.config import AppConfig from django.apps.config import AppConfig
from django.apps.registry import Apps from django.apps.registry import Apps

View File

@@ -1,6 +1,6 @@
from typing import Any from typing import Any
from django.contrib import admin as admin from django.contrib import admin
class FlatPageAdmin(admin.ModelAdmin): class FlatPageAdmin(admin.ModelAdmin):
form: Any = ... form: Any = ...

View File

@@ -1,7 +1,7 @@
from typing import Any from typing import Any
from django.contrib.gis.db import models as models from django.contrib.gis.db import models
from django.contrib.gis.db.backends.base.models import SpatialRefSysMixin as SpatialRefSysMixin from django.contrib.gis.db.backends.base.models import SpatialRefSysMixin
class OracleGeometryColumns(models.Model): class OracleGeometryColumns(models.Model):
table_name: Any = ... table_name: Any = ...

View File

@@ -1,15 +1,10 @@
import sys
from typing import Any, Union from typing import Any, Union
from django.contrib.gis.db.backends.base.operations import BaseSpatialOperations from django.contrib.gis.db.backends.base.operations import BaseSpatialOperations
from django.contrib.gis.db.backends.utils import SpatialOperator from django.contrib.gis.db.backends.utils import SpatialOperator
from django.db.backends.postgresql.operations import DatabaseOperations from django.db.backends.postgresql.operations import DatabaseOperations
from django.db.models import Func from django.db.models import Func
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
BILATERAL: Literal["bilateral"] BILATERAL: Literal["bilateral"]

View File

@@ -1,6 +1,6 @@
from typing import Any, Optional from typing import Any, Optional
from django import forms as forms from django import forms
class GeometryField(forms.Field): class GeometryField(forms.Field):
widget: Any = ... widget: Any = ...

View File

@@ -1,14 +1,9 @@
import sys
from typing import Any, Dict, Optional, Type from typing import Any, Dict, Optional, Type
from django.db import models from django.db import models
from django.db.models.lookups import PostgresOperatorLookup from django.db.models.lookups import PostgresOperatorLookup
from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange, Range from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange, Range
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
class RangeBoundary(models.Expression): class RangeBoundary(models.Expression):
lower: str lower: str

View File

@@ -1,8 +1,7 @@
from typing import Dict, Literal, Tuple
from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations import AddConstraint, AddIndex, RemoveIndex from django.db.migrations import AddConstraint, AddIndex, RemoveIndex
from django.db.migrations.operations.base import Operation from django.db.migrations.operations.base import Operation
from typing_extensions import Literal
class CreateExtension(Operation): class CreateExtension(Operation):
reversible: bool = ... reversible: bool = ...

View File

@@ -1,6 +1,6 @@
from typing import Any from typing import Any
from django.contrib import admin as admin from django.contrib import admin
class RedirectAdmin(admin.ModelAdmin): class RedirectAdmin(admin.ModelAdmin):
list_display: Any = ... list_display: Any = ...

View File

@@ -1,6 +1,6 @@
from typing import Any from typing import Any
from django.contrib import admin as admin from django.contrib import admin
class SiteAdmin(admin.ModelAdmin): class SiteAdmin(admin.ModelAdmin):
list_display: Any = ... list_display: Any = ...

View File

@@ -1,13 +1,8 @@
import sys
from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Tuple, Type, overload from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Tuple, Type, overload
from django.core.checks.messages import CheckMessage from django.core.checks.messages import CheckMessage
from django.core.files.storage import FileSystemStorage, Storage from django.core.files.storage import FileSystemStorage, Storage
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
searched_locations: Any searched_locations: Any

View File

@@ -1,13 +1,8 @@
import sys
from typing import Any, Callable, List, Optional, Sequence, Set, TypeVar, Union from typing import Any, Callable, List, Optional, Sequence, Set, TypeVar, Union
from django.apps.config import AppConfig from django.apps.config import AppConfig
from django.core.checks.messages import CheckMessage from django.core.checks.messages import CheckMessage
from typing_extensions import Protocol
if sys.version_info < (3, 8):
from typing_extensions import Protocol
else:
from typing import Protocol
class Tags: class Tags:
admin: str = ... admin: str = ...

View File

@@ -1,10 +1,6 @@
import sys
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
if sys.version_info < (3, 8): from typing_extensions import Literal
from typing_extensions import Literal
else:
from typing import Literal
class FieldDoesNotExist(Exception): ... class FieldDoesNotExist(Exception): ...
class AppRegistryNotReady(Exception): ... class AppRegistryNotReady(Exception): ...

View File

@@ -1,4 +1,3 @@
import sys
from argparse import ArgumentParser, HelpFormatter, Namespace from argparse import ArgumentParser, HelpFormatter, Namespace
from io import TextIOBase from io import TextIOBase
from typing import Any, Callable, Iterable, List, Optional, Sequence, Set, TextIO, Tuple, Union from typing import Any, Callable, Iterable, List, Optional, Sequence, Set, TextIO, Tuple, Union
@@ -6,11 +5,7 @@ from typing import Any, Callable, Iterable, List, Optional, Sequence, Set, TextI
from django.apps.config import AppConfig from django.apps.config import AppConfig
from django.core.management.color import Style from django.core.management.color import Style
from django.utils.datastructures import _ListOrTuple from django.utils.datastructures import _ListOrTuple
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
class CommandError(Exception): class CommandError(Exception):
def __init__(self, *args: Any, returncode: int = ..., **kwargs: Any) -> None: ... def __init__(self, *args: Any, returncode: int = ..., **kwargs: Any) -> None: ...

View File

@@ -2,7 +2,7 @@ from typing import Any
from django.utils.connection import ConnectionProxy from django.utils.connection import ConnectionProxy
from . import migrations from . import migrations as migrations
from .backends.base.base import BaseDatabaseWrapper from .backends.base.base import BaseDatabaseWrapper
from .utils import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS # Not exported in __all__ from .utils import DEFAULT_DB_ALIAS as DEFAULT_DB_ALIAS # Not exported in __all__
from .utils import DJANGO_VERSION_PICKLE_KEY as DJANGO_VERSION_PICKLE_KEY from .utils import DJANGO_VERSION_PICKLE_KEY as DJANGO_VERSION_PICKLE_KEY

View File

@@ -1,10 +1,6 @@
import sys
from typing import Any, Dict, List, Sequence from typing import Any, Dict, List, Sequence
if sys.version_info < (3, 8): from typing_extensions import Protocol
from typing_extensions import Protocol
else:
from typing import Protocol
class _QuoteCallable(Protocol): class _QuoteCallable(Protocol):
"""Get rid of `cannot assign to method`""" """Get rid of `cannot assign to method`"""

View File

@@ -1,7 +1,7 @@
import sys
from typing import Any, Dict, Optional, Type from typing import Any, Dict, Optional, Type
from django.db.backends.base.base import BaseDatabaseWrapper as BaseDatabaseWrapper from django.db.backends.base.base import BaseDatabaseWrapper as BaseDatabaseWrapper
from typing_extensions import Literal
from .client import DatabaseClient from .client import DatabaseClient
from .creation import DatabaseCreation from .creation import DatabaseCreation
@@ -10,11 +10,6 @@ from .introspection import DatabaseIntrospection
from .operations import DatabaseOperations from .operations import DatabaseOperations
from .validation import DatabaseValidation from .validation import DatabaseValidation
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
version: Any version: Any
django_conversions: Any django_conversions: Any
server_version_re: Any server_version_re: Any

View File

@@ -9,7 +9,8 @@ class SQLCompiler(compiler.SQLCompiler):
class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler): ... class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler): ...
class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler): class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
def as_sql(self) -> _AsSqlType: ... # https://github.com/django/django/blob/242499f2dc2bf24a9a5c855690a2e13d3303581a/django/db/backends/mysql/compiler.py#L26
def as_sql(self) -> _AsSqlType: ... # type: ignore[override]
class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler): ... class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler): ...
class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler): ... class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler): ...

View File

@@ -1,5 +1,4 @@
import datetime import datetime
import sys
import types import types
from contextlib import contextmanager from contextlib import contextmanager
from decimal import Decimal from decimal import Decimal
@@ -21,10 +20,7 @@ from typing import (
) )
from uuid import UUID from uuid import UUID
if sys.version_info < (3, 8): from typing_extensions import Literal
from typing_extensions import Literal
else:
from typing import Literal
logger: Logger logger: Logger

View File

@@ -1,18 +1,13 @@
import sys
from typing import List, Optional, Sequence, Set, Tuple, Union from typing import List, Optional, Sequence, Set, Tuple, Union
from django.db.backends.base.base import BaseDatabaseWrapper from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.migrations.migration import Migration from django.db.migrations.migration import Migration
from typing_extensions import Protocol
from .loader import MigrationLoader from .loader import MigrationLoader
from .recorder import MigrationRecorder from .recorder import MigrationRecorder
from .state import ProjectState from .state import ProjectState
if sys.version_info < (3, 8):
from typing_extensions import Protocol
else:
from typing import Protocol
class _ProgressCallbackT(Protocol): class _ProgressCallbackT(Protocol):
def __call__(self, __action: str, __migration: Optional[Migration] = ..., __fake: Optional[bool] = ...) -> None: ... def __call__(self, __action: str, __migration: Optional[Migration] = ..., __fake: Optional[bool] = ...) -> None: ...

View File

@@ -1,17 +1,12 @@
import sys
from typing import Any, Dict, Mapping, Optional, Sequence, Tuple, Union from typing import Any, Dict, Mapping, Optional, Sequence, Tuple, Union
from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps from django.db.migrations.state import StateApps
from django.utils.datastructures import _ListOrTuple from django.utils.datastructures import _ListOrTuple
from typing_extensions import Literal, Protocol
from .base import Operation from .base import Operation
if sys.version_info < (3, 8):
from typing_extensions import Literal, Protocol
else:
from typing import Literal, Protocol
class SeparateDatabaseAndState(Operation): class SeparateDatabaseAndState(Operation):
database_operations: Sequence[Operation] = ... database_operations: Sequence[Operation] = ...
state_operations: Sequence[Operation] = ... state_operations: Sequence[Operation] = ...

View File

@@ -1,14 +1,9 @@
import sys
from collections import namedtuple from collections import namedtuple
from typing import Iterator, Optional, Tuple, Union from typing import Iterator, Optional, Tuple, Union
from django.db.migrations.state import ModelState, ProjectState from django.db.migrations.state import ModelState, ProjectState
from django.db.models import Field from django.db.models import Field
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
def resolve_relation(model, app_label: Optional[str] = ..., model_name: Optional[str] = ...) -> Tuple[str, str]: ... def resolve_relation(model, app_label: Optional[str] = ..., model_name: Optional[str] = ...) -> Tuple[str, str]: ...

View File

@@ -1,5 +1,4 @@
import datetime import datetime
import sys
from decimal import Decimal from decimal import Decimal
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union
@@ -10,11 +9,7 @@ from django.db.models.lookups import Lookup, Transform
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.db.models.sql.compiler import SQLCompiler, _AsSqlType from django.db.models.sql.compiler import SQLCompiler, _AsSqlType
from django.db.models.sql.query import Query from django.db.models.sql.query import Query
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
class SQLiteNumericMixin: class SQLiteNumericMixin:
def as_sqlite(self, compiler: SQLCompiler, connection: BaseDatabaseWrapper, **extra_context: Any) -> _AsSqlType: ... def as_sqlite(self, compiler: SQLCompiler, connection: BaseDatabaseWrapper, **extra_context: Any) -> _AsSqlType: ...

View File

@@ -1,5 +1,4 @@
import decimal import decimal
import sys
import uuid import uuid
from datetime import date from datetime import date
from datetime import datetime as real_datetime from datetime import datetime as real_datetime
@@ -32,11 +31,7 @@ from django.forms import Field as FormField
from django.forms import Widget from django.forms import Widget
from django.utils.datastructures import DictWrapper from django.utils.datastructures import DictWrapper
from django.utils.functional import _Getter from django.utils.functional import _Getter
from typing_extensions import Protocol
if sys.version_info < (3, 8):
from typing_extensions import Protocol
else:
from typing import Protocol
class Empty: ... class Empty: ...
class NOT_PROVIDED: ... class NOT_PROVIDED: ...

View File

@@ -1,5 +1,3 @@
import sys
from pathlib import Path
from typing import Any, Callable, Iterable, Optional, Type, TypeVar, Union, overload from typing import Any, Callable, Iterable, Optional, Type, TypeVar, Union, overload
from django.core import validators # due to weird mypy.stubtest error from django.core import validators # due to weird mypy.stubtest error
@@ -10,11 +8,7 @@ from django.db.models.base import Model
from django.db.models.fields import Field, _ErrorMessagesT, _FieldChoices from django.db.models.fields import Field, _ErrorMessagesT, _FieldChoices
from django.db.models.query_utils import DeferredAttribute from django.db.models.query_utils import DeferredAttribute
from django.utils._os import _PathCompatible from django.utils._os import _PathCompatible
from typing_extensions import Protocol
if sys.version_info < (3, 8):
from typing_extensions import Protocol
else:
from typing import Protocol
class FieldFile(File): class FieldFile(File):
instance: Model = ... instance: Model = ...

View File

@@ -1,12 +1,6 @@
import sys
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type, TypeVar, Union, overload from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type, TypeVar, Union, overload
from uuid import UUID from uuid import UUID
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
from django.core import validators # due to weird mypy.stubtest error from django.core import validators # due to weird mypy.stubtest error
from django.db import models from django.db import models
from django.db.models.base import Model from django.db.models.base import Model
@@ -26,6 +20,7 @@ from django.db.models.fields.reverse_related import ManyToOneRel as ManyToOneRel
from django.db.models.fields.reverse_related import OneToOneRel as OneToOneRel from django.db.models.fields.reverse_related import OneToOneRel as OneToOneRel
from django.db.models.manager import RelatedManager from django.db.models.manager import RelatedManager
from django.db.models.query_utils import FilteredRelation, PathInfo, Q from django.db.models.query_utils import FilteredRelation, PathInfo, Q
from typing_extensions import Literal
_T = TypeVar("_T", bound=models.Model) _T = TypeVar("_T", bound=models.Model)
_F = TypeVar("_F", bound=models.Field) _F = TypeVar("_F", bound=models.Field)

View File

@@ -1,21 +1,15 @@
import sys from typing import Any, Callable, List, Optional, Sequence, Tuple, Type, Union
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union
from django.db.models import Q
from django.db.models.base import Model from django.db.models.base import Model
from django.db.models.fields import AutoField, Field, _AllLimitChoicesTo, _ChoicesList, _LimitChoicesTo from django.db.models.fields import AutoField, Field, _AllLimitChoicesTo, _ChoicesList, _LimitChoicesTo
from django.db.models.fields.related import ForeignKey, ForeignObject, ManyToManyField, OneToOneField from django.db.models.fields.related import ForeignKey, ForeignObject, ManyToManyField, OneToOneField
from django.db.models.lookups import BuiltinLookup, Lookup, StartsWith from django.db.models.lookups import Lookup, StartsWith
from django.db.models.query_utils import FilteredRelation, PathInfo from django.db.models.query_utils import FilteredRelation, PathInfo
from django.db.models.sql.where import WhereNode from django.db.models.sql.where import WhereNode
from typing_extensions import Literal
from .mixins import FieldCacheMixin from .mixins import FieldCacheMixin
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
# Common note: `model` and `through` can be of type `str` when passed to `__init__`. # Common note: `model` and `through` can be of type `str` when passed to `__init__`.
# When parent's `contribute_to_class` is called (during startup), # When parent's `contribute_to_class` is called (during startup),
# strings are resolved to real model classes. # strings are resolved to real model classes.

View File

@@ -1,16 +1,11 @@
import sys
from typing import Any, Generic, Iterable, List, Mapping, Optional, Tuple, Type, TypeVar, Union from typing import Any, Generic, Iterable, List, Mapping, Optional, Tuple, Type, TypeVar, Union
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
from django.db.backends.base.base import BaseDatabaseWrapper from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.models.expressions import Expression, Func from django.db.models.expressions import Expression, Func
from django.db.models.query_utils import RegisterLookupMixin from django.db.models.query_utils import RegisterLookupMixin
from django.db.models.sql.compiler import SQLCompiler, _AsSqlType, _ParamT from django.db.models.sql.compiler import SQLCompiler, _AsSqlType, _ParamT
from django.utils.datastructures import OrderedSet from django.utils.datastructures import OrderedSet
from typing_extensions import Literal
_L = TypeVar("_L", bound="Lookup") _L = TypeVar("_L", bound="Lookup")
_T = TypeVar("_T") _T = TypeVar("_T")

View File

@@ -1,4 +1,3 @@
import sys
from typing import Any, Dict, Generic, Iterable, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, overload from typing import Any, Dict, Generic, Iterable, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, overload
from django.apps.config import AppConfig from django.apps.config import AppConfig
@@ -13,11 +12,7 @@ from django.db.models.fields.reverse_related import ForeignObjectRel
from django.db.models.manager import Manager from django.db.models.manager import Manager
from django.db.models.query_utils import PathInfo from django.db.models.query_utils import PathInfo
from django.utils.datastructures import ImmutableList, _ListOrTuple from django.utils.datastructures import ImmutableList, _ListOrTuple
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
PROXY_PARENTS: object PROXY_PARENTS: object
EMPTY_RELATION_TREE: Any EMPTY_RELATION_TREE: Any

View File

@@ -1,4 +1,3 @@
import sys
from collections import namedtuple from collections import namedtuple
from typing import ( from typing import (
Any, Any,
@@ -17,23 +16,17 @@ from typing import (
Union, Union,
) )
from django.db.models.sql.compiler import _AsSqlType
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
from django.db.backends.base.base import BaseDatabaseWrapper from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.models.base import Model from django.db.models.base import Model
from django.db.models.expressions import BaseExpression from django.db.models.expressions import BaseExpression
from django.db.models.fields import Field from django.db.models.fields import Field
from django.db.models.fields.mixins import FieldCacheMixin from django.db.models.fields.mixins import FieldCacheMixin
from django.db.models.lookups import Lookup, Transform from django.db.models.lookups import Lookup, Transform
from django.db.models.sql.compiler import SQLCompiler from django.db.models.sql.compiler import SQLCompiler, _AsSqlType
from django.db.models.sql.query import Query from django.db.models.sql.query import Query
from django.db.models.sql.where import WhereNode from django.db.models.sql.where import WhereNode
from django.utils import tree from django.utils import tree
from typing_extensions import Literal
PathInfo = namedtuple( PathInfo = namedtuple(
"PathInfo", ["from_opts", "to_opts", "target_fields", "join_field", "m2m", "direct", "filtered_relation"] "PathInfo", ["from_opts", "to_opts", "target_fields", "join_field", "m2m", "direct", "filtered_relation"]

View File

@@ -1,21 +1,15 @@
import sys
from datetime import date, datetime from datetime import date, datetime
from decimal import Decimal from decimal import Decimal
from itertools import chain
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union, overload from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union, overload
from uuid import UUID from uuid import UUID
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
from django.db.backends.base.base import BaseDatabaseWrapper from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.backends.utils import CursorWrapper from django.db.backends.utils import CursorWrapper
from django.db.models.base import Model from django.db.models.base import Model
from django.db.models.expressions import BaseExpression, Expression from django.db.models.expressions import BaseExpression, Expression
from django.db.models.sql.query import Query from django.db.models.sql.query import Query
from django.db.models.sql.subqueries import AggregateQuery, DeleteQuery, InsertQuery, UpdateQuery from django.db.models.sql.subqueries import AggregateQuery, DeleteQuery, InsertQuery, UpdateQuery
from typing_extensions import Literal
_ParamT = Union[str, int] _ParamT = Union[str, int]
_ParamsT = List[_ParamT] _ParamsT = List[_ParamT]

View File

@@ -1,10 +1,6 @@
import sys from typing import Dict, Pattern, Tuple
from typing import Dict, Final, Pattern, Tuple
if sys.version_info < (3, 8): from typing_extensions import Final, Literal
from typing_extensions import Literal
else:
from typing import Literal
GET_ITERATOR_CHUNK_SIZE: Final[int] = ... GET_ITERATOR_CHUNK_SIZE: Final[int] = ...

View File

@@ -1,13 +1,7 @@
import collections import collections
import sys
from collections import namedtuple from collections import namedtuple
from typing import Any, Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union from typing import Any, Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
from django.db.backends.base.base import BaseDatabaseWrapper from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.backends.utils import CursorWrapper from django.db.backends.utils import CursorWrapper
from django.db.models import Field, FilteredRelation, Model, Q, QuerySet from django.db.models import Field, FilteredRelation, Model, Q, QuerySet
@@ -18,6 +12,7 @@ from django.db.models.query_utils import PathInfo, RegisterLookupMixin
from django.db.models.sql.compiler import SQLCompiler from django.db.models.sql.compiler import SQLCompiler
from django.db.models.sql.datastructures import BaseTable, Join from django.db.models.sql.datastructures import BaseTable, Join
from django.db.models.sql.where import WhereNode from django.db.models.sql.where import WhereNode
from typing_extensions import Literal
JoinInfo = namedtuple("JoinInfo", ("final_field", "targets", "opts", "joins", "path", "transform_function")) JoinInfo = namedtuple("JoinInfo", ("final_field", "targets", "opts", "joins", "path", "transform_function"))

View File

@@ -1,4 +1,3 @@
import sys
from typing import ( from typing import (
Any, Any,
Callable, Callable,
@@ -34,11 +33,7 @@ from django.forms.renderers import BaseRenderer
from django.forms.utils import ErrorList, _DataT, _FilesT from django.forms.utils import ErrorList, _DataT, _FilesT
from django.forms.widgets import ChoiceWidget, Input, Widget from django.forms.widgets import ChoiceWidget, Input, Widget
from django.utils.datastructures import _IndexableCollection, _ListOrTuple, _PropertyDescriptor from django.utils.datastructures import _IndexableCollection, _ListOrTuple, _PropertyDescriptor
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
ALL_FIELDS: Literal["__all__"] ALL_FIELDS: Literal["__all__"]

View File

@@ -1,5 +1,4 @@
import datetime import datetime
import sys
from typing import Any, Dict, Iterable, Iterator, List, Mapping, Optional, Sequence, Tuple, Type, Union from typing import Any, Dict, Iterable, Iterator, List, Mapping, Optional, Sequence, Tuple, Type, Union
from django.core.files.base import File from django.core.files.base import File
@@ -9,12 +8,7 @@ from django.forms.utils import _DataT, _FilesT
from django.utils.datastructures import _ListOrTuple from django.utils.datastructures import _ListOrTuple
from django.utils.functional import _Getter from django.utils.functional import _Getter
from django.utils.safestring import SafeString from django.utils.safestring import SafeString
from typing_extensions import Literal from typing_extensions import Literal, Protocol
if sys.version_info < (3, 8):
from typing_extensions import Protocol
else:
from typing import Protocol
_OptAttrs = Dict[str, Any] _OptAttrs = Dict[str, Any]

View File

@@ -1,20 +1,15 @@
import sys
from typing import IO, Any, Dict, Iterator, List, Mapping, Optional, Tuple, Union from typing import IO, Any, Dict, Iterator, List, Mapping, Optional, Tuple, Union
from django.http.request import QueryDict from django.http.request import QueryDict
from django.utils.datastructures import ImmutableList, MultiValueDict from django.utils.datastructures import ImmutableList, MultiValueDict
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
class MultiPartParserError(Exception): ... class MultiPartParserError(Exception): ...
class InputStreamExhausted(Exception): ... class InputStreamExhausted(Exception): ...
RAW: Literal["raw"] = "raw" RAW: Literal["raw"]
FILE: Literal["file"] = "file" FILE: Literal["file"]
FIELD: Literal["field"] = "field" FIELD: Literal["field"]
class MultiPartParser: class MultiPartParser:
def __init__( def __init__(

View File

@@ -1,4 +1,3 @@
import sys
from io import BytesIO from io import BytesIO
from typing import ( from typing import (
Any, Any,
@@ -25,11 +24,7 @@ from django.contrib.sites.models import Site
from django.core.files import uploadedfile, uploadhandler from django.core.files import uploadedfile, uploadhandler
from django.urls import ResolverMatch from django.urls import ResolverMatch
from django.utils.datastructures import CaseInsensitiveMapping, ImmutableList, MultiValueDict from django.utils.datastructures import CaseInsensitiveMapping, ImmutableList, MultiValueDict
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
RAISE_ERROR: object = ... RAISE_ERROR: object = ...
host_validation_re: Pattern[str] = ... host_validation_re: Pattern[str] = ...

View File

@@ -1,4 +1,3 @@
import sys
from typing import Any, Callable, List, Mapping, Optional, Protocol, Sequence, Type, TypeVar, Union, overload from typing import Any, Callable, List, Mapping, Optional, Protocol, Sequence, Type, TypeVar, Union, overload
from django.db.models import Manager, QuerySet from django.db.models import Manager, QuerySet
@@ -7,11 +6,7 @@ from django.http import HttpRequest
from django.http.response import HttpResponse as HttpResponse from django.http.response import HttpResponse as HttpResponse
from django.http.response import HttpResponsePermanentRedirect as HttpResponsePermanentRedirect from django.http.response import HttpResponsePermanentRedirect as HttpResponsePermanentRedirect
from django.http.response import HttpResponseRedirect as HttpResponseRedirect from django.http.response import HttpResponseRedirect as HttpResponseRedirect
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
def render( def render(
request: HttpRequest, request: HttpRequest,

View File

@@ -1,12 +1,7 @@
import sys
from typing import Any, Callable, Dict, Optional, Sequence, Union from typing import Any, Callable, Dict, Optional, Sequence, Union
from django.urls.resolvers import ResolverMatch from django.urls.resolvers import ResolverMatch
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
def resolve(path: str, urlconf: Optional[str] = ...) -> ResolverMatch: ... def resolve(path: str, urlconf: Optional[str] = ...) -> ResolverMatch: ...
def reverse( def reverse(

View File

@@ -1,4 +1,3 @@
import sys
from typing import ( from typing import (
Any, Any,
Collection, Collection,
@@ -16,10 +15,7 @@ from typing import (
overload, overload,
) )
if sys.version_info < (3, 8): from typing_extensions import Protocol
from typing_extensions import Protocol
else:
from typing import Protocol
_K = TypeVar("_K") _K = TypeVar("_K")
_V = TypeVar("_V") _V = TypeVar("_V")

View File

@@ -1,15 +1,10 @@
import sys
from datetime import date from datetime import date
from datetime import datetime as builtin_datetime from datetime import datetime as builtin_datetime
from datetime import time as builtin_time from datetime import time as builtin_time
from typing import Any, Optional, Pattern, Union from typing import Any, Optional, Pattern, Union
from django.utils.timezone import _TzInfoT from django.utils.timezone import _TzInfoT
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
re_formatchars: Pattern[str] re_formatchars: Pattern[str]
re_escaped: Pattern[str] re_escaped: Pattern[str]

View File

@@ -1,15 +1,9 @@
import datetime import datetime
import sys
from decimal import Decimal from decimal import Decimal
from typing import Any, TypeVar, Union, overload from typing import Any, TypeVar, Union, overload
if sys.version_info < (3, 10):
from typing_extensions import TypeGuard
else:
from typing import TypeGuard
from django.utils.functional import Promise from django.utils.functional import Promise
from typing_extensions import Literal from typing_extensions import Literal, TypeGuard
class DjangoUnicodeDecodeError(UnicodeDecodeError): class DjangoUnicodeDecodeError(UnicodeDecodeError):
obj: bytes = ... obj: bytes = ...

View File

@@ -1,13 +1,8 @@
import sys
from functools import wraps as wraps # noqa: F401 from functools import wraps as wraps # noqa: F401
from typing import Any, Callable, Generic, List, Optional, Tuple, Type, TypeVar, Union, overload from typing import Any, Callable, Generic, List, Optional, Tuple, Type, TypeVar, Union, overload
from django.db.models.base import Model from django.db.models.base import Model
from typing_extensions import Protocol
if sys.version_info < (3, 8):
from typing_extensions import Protocol
else:
from typing import Protocol
_T = TypeVar("_T") _T = TypeVar("_T")

View File

@@ -1,4 +1,3 @@
import sys
import types import types
from contextlib import ContextDecorator from contextlib import ContextDecorator
from datetime import date from datetime import date
@@ -9,18 +8,9 @@ from datetime import timezone
from datetime import tzinfo as tzinfo from datetime import tzinfo as tzinfo
from typing import Any, Optional, Type, Union, overload from typing import Any, Optional, Type, Union, overload
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
if sys.version_info < (3, 10):
from typing_extensions import TypeGuard
else:
from typing import TypeGuard
import pytz import pytz
from pytz import BaseTzInfo from pytz import BaseTzInfo
from typing_extensions import Literal, TypeGuard
_PytzTzInfoT = Union[pytz.tzinfo.BaseTzInfo, pytz._FixedOffset] _PytzTzInfoT = Union[pytz.tzinfo.BaseTzInfo, pytz._FixedOffset]
_TzInfoT = Union[_PytzTzInfoT, tzinfo] _TzInfoT = Union[_PytzTzInfoT, tzinfo]

View File

@@ -1,14 +1,9 @@
import gettext as gettext_module import gettext as gettext_module
import sys
from gettext import NullTranslations from gettext import NullTranslations
from typing import Any, Callable, Dict, Iterator, List, Optional, Pattern, Protocol, Tuple, TypeVar, Union from typing import Any, Dict, Iterator, List, Optional, Pattern, Protocol, Tuple, TypeVar, Union
from django.http.request import HttpRequest from django.http.request import HttpRequest
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
CONTEXT_SEPARATOR: Literal["\x04"] CONTEXT_SEPARATOR: Literal["\x04"]
accept_language_re: Pattern[str] accept_language_re: Pattern[str]

View File

@@ -1,4 +1,3 @@
import sys
from typing import Any, Dict, Generic, Optional, Type, TypeVar, Union from typing import Any, Dict, Generic, Optional, Type, TypeVar, Union
from django.db import models from django.db import models
@@ -8,11 +7,7 @@ from django.http import HttpRequest, HttpResponse
from django.utils.datastructures import _ListOrTuple from django.utils.datastructures import _ListOrTuple
from django.views.generic.base import ContextMixin, TemplateResponseMixin, View from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
from django.views.generic.detail import BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin from django.views.generic.detail import BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin
from typing_extensions import Literal
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
_FormT = TypeVar("_FormT", bound=BaseForm) _FormT = TypeVar("_FormT", bound=BaseForm)
_ModelFormT = TypeVar("_ModelFormT", bound=BaseModelForm) _ModelFormT = TypeVar("_ModelFormT", bound=BaseModelForm)

View File

@@ -1,3 +1,4 @@
import builtins
from typing import Any, Generic, Iterable, List, Optional, Tuple, Type, TypeVar from typing import Any, Generic, Iterable, List, Optional, Tuple, Type, TypeVar
from django import VERSION as VERSION from django import VERSION as VERSION
@@ -17,6 +18,8 @@ from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import DeletionMixin, FormMixin from django.views.generic.edit import DeletionMixin, FormMixin
from django.views.generic.list import MultipleObjectMixin from django.views.generic.list import MultipleObjectMixin
__all__ = ["monkeypatch"]
_T = TypeVar("_T") _T = TypeVar("_T")
_VersionSpec = Tuple[int, int] _VersionSpec = Tuple[int, int]
@@ -67,7 +70,7 @@ _need_generic: List[MPGeneric[Any]] = [
] ]
def monkeypatch(extra_classes: Optional[Iterable[type]] = None) -> None: def monkeypatch(extra_classes: Optional[Iterable[type]] = None, include_builtins: bool = True) -> None:
"""Monkey patch django as necessary to work properly with mypy.""" """Monkey patch django as necessary to work properly with mypy."""
# Add the __class_getitem__ dunder. # Add the __class_getitem__ dunder.
@@ -81,5 +84,7 @@ def monkeypatch(extra_classes: Optional[Iterable[type]] = None) -> None:
for cls in extra_classes: for cls in extra_classes:
cls.__class_getitem__ = classmethod(lambda cls, *args, **kwargs: cls) # type: ignore[attr-defined] cls.__class_getitem__ = classmethod(lambda cls, *args, **kwargs: cls) # type: ignore[attr-defined]
# Add `reveal_type` and `reveal_locals` helpers if needed:
__all__ = ["monkeypatch"] if include_builtins:
builtins.reveal_type = lambda _: None
builtins.reveal_locals = lambda: None

View File

@@ -1,10 +1,7 @@
import sys
from typing import Any from typing import Any
if sys.version_info < (3, 8): from typing_extensions import Protocol
from typing_extensions import Protocol
else:
from typing import Protocol
# Used internally by mypy_django_plugin. # Used internally by mypy_django_plugin.
class AnyAttrAllowed(Protocol): class AnyAttrAllowed(Protocol):

View File

@@ -12,7 +12,7 @@ dependencies = [
setup( setup(
name="django-stubs-ext", name="django-stubs-ext",
version="0.5.0", version="0.6.0",
description="Monkey-patching and extensions for django-stubs", description="Monkey-patching and extensions for django-stubs",
long_description=readme, long_description=readme,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
@@ -23,7 +23,7 @@ setup(
maintainer="Nikita Sobolev", maintainer="Nikita Sobolev",
maintainer_email="mail@sobolevn.me", maintainer_email="mail@sobolevn.me",
py_modules=[], py_modules=[],
python_requires=">=3.6", python_requires=">=3.7",
install_requires=dependencies, install_requires=dependencies,
packages=["django_stubs_ext", *find_packages(exclude=["scripts"])], packages=["django_stubs_ext", *find_packages(exclude=["scripts"])],
package_data={"django_stubs_ext": ["py.typed"]}, package_data={"django_stubs_ext": ["py.typed"]},
@@ -33,6 +33,7 @@ setup(
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Typing :: Typed", "Typing :: Typed",
"Framework :: Django", "Framework :: Django",
"Framework :: Django :: 2.2", "Framework :: Django :: 2.2",

View File

@@ -1,13 +1,12 @@
import builtins import builtins
from contextlib import suppress from contextlib import suppress
from typing import Iterable, Optional from typing import Iterable, List, Optional
import pytest import pytest
from _pytest.fixtures import FixtureRequest from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch from _pytest.monkeypatch import MonkeyPatch
from django.db.models import Model from django.db.models import Model
from django.forms.models import ModelForm from django.forms.models import ModelForm
from django.views import View
from typing_extensions import Protocol from typing_extensions import Protocol
import django_stubs_ext import django_stubs_ext
@@ -19,7 +18,10 @@ class _MakeGenericClasses(Protocol):
"""Used to represent a type of ``make_generic_classes`` fixture.""" """Used to represent a type of ``make_generic_classes`` fixture."""
def __call__( def __call__(
self, django_version: Optional[_VersionSpec] = None, extra_classes: Optional[Iterable[type]] = None self,
django_version: Optional[_VersionSpec] = None,
extra_classes: Optional[Iterable[type]] = None,
include_builtins: bool = True,
) -> None: ) -> None:
... ...
@@ -29,7 +31,7 @@ def make_generic_classes(
request: FixtureRequest, request: FixtureRequest,
monkeypatch: MonkeyPatch, monkeypatch: MonkeyPatch,
) -> _MakeGenericClasses: ) -> _MakeGenericClasses:
_extra_classes: list[type] = [] _extra_classes: List[type] = []
def fin() -> None: def fin() -> None:
for el in _need_generic: for el in _need_generic:
@@ -38,13 +40,22 @@ def make_generic_classes(
for cls in _extra_classes: for cls in _extra_classes:
with suppress(AttributeError): with suppress(AttributeError):
delattr(cls, "__class_getitem__") delattr(cls, "__class_getitem__")
_extra_classes.clear()
with suppress(AttributeError):
del builtins.reveal_type
with suppress(AttributeError):
del builtins.reveal_locals
def factory(django_version: Optional[_VersionSpec] = None, extra_classes: Optional[Iterable[type]] = None) -> None: def factory(
django_version: Optional[_VersionSpec] = None,
extra_classes: Optional[Iterable[type]] = None,
include_builtins: bool = True,
) -> None:
if extra_classes: if extra_classes:
_extra_classes.extend(extra_classes) _extra_classes.extend(extra_classes)
if django_version is not None: if django_version is not None:
monkeypatch.setattr(patch, "VERSION", django_version) monkeypatch.setattr(patch, "VERSION", django_version)
django_stubs_ext.monkeypatch(extra_classes) django_stubs_ext.monkeypatch(extra_classes=extra_classes, include_builtins=include_builtins)
request.addfinalizer(fin) request.addfinalizer(fin)
return factory return factory
@@ -64,14 +75,17 @@ def test_patched_generics(make_generic_classes: _MakeGenericClasses) -> None:
def test_patched_extra_classes_generics(make_generic_classes: _MakeGenericClasses) -> None: def test_patched_extra_classes_generics(make_generic_classes: _MakeGenericClasses) -> None:
"""Test that the generics actually get patched for extra classes.""" """Test that the generics actually get patched for extra classes."""
extra_classes = [View]
class _NotGeneric:
pass
extra_classes = [_NotGeneric]
make_generic_classes(django_version=None, extra_classes=extra_classes) make_generic_classes(django_version=None, extra_classes=extra_classes)
for cls in extra_classes: for cls in extra_classes:
assert cls[type] is cls # type: ignore[misc] assert cls[type] is cls # type: ignore[misc]
class TestView(View[Model]): # type: ignore[type-arg] class _TestGeneric(_NotGeneric[Model]): # type: ignore
pass pass
@@ -106,7 +120,17 @@ def test_mypy_builtins_not_patched_globally(
This should only happend during `django.setup()` This should only happend during `django.setup()`
(https://github.com/typeddjango/django-stubs/issues/609). (https://github.com/typeddjango/django-stubs/issues/609).
""" """
make_generic_classes() make_generic_classes(include_builtins=False)
assert not hasattr(builtins, "reveal_type") assert not hasattr(builtins, "reveal_type")
assert not hasattr(builtins, "reveal_locals") assert not hasattr(builtins, "reveal_locals")
def test_mypy_builtins_patched(
make_generic_classes: _MakeGenericClasses,
) -> None:
"""Ensures that builtins are patched by default."""
make_generic_classes()
assert hasattr(builtins, "reveal_type")
assert hasattr(builtins, "reveal_locals")

View File

@@ -3,6 +3,7 @@
# `main` is empty on purpose, because we test the `django.setup()` step. # `main` is empty on purpose, because we test the `django.setup()` step.
# It executes the `myapp/models.py` file and should not produce anything, # It executes the `myapp/models.py` file and should not produce anything,
# because `reveal_type` and `reveal_locals` are patched. # because `reveal_type` and `reveal_locals` are patched.
monkeypatch: true
installed_apps: installed_apps:
- myapp - myapp
files: files:

View File

@@ -1,10 +1,5 @@
# Regular configuration file (can be used as base in other projects, runs in CI) # Regular configuration file (can be used as base in other projects, runs in CI)
# NOTE: this config file is not used by pytest locally.
# See comment in mypy.ini.dev for explanation.
# WARNING: when changing this file, consider doing the same with mypy.ini.dev
[mypy] [mypy]
allow_redefinition = True allow_redefinition = True
check_untyped_defs = True check_untyped_defs = True

View File

@@ -1,30 +0,0 @@
# Configuration file to use during development
# Changes of plugin code are not detected by mypy, so
# incremental mode can be harmful during development
# (mypy cache is not always invalidated).
# However, incremental mode has to be supported by django-stubs,
# thus another config (mypy.ini) is used in CI.
# Incremental mode is recommended for regular usage.
# WARNING: when changing this file, consider doing the same with mypy.ini
[mypy]
allow_redefinition = True
check_untyped_defs = True
ignore_missing_imports = True
# Avoid caching between test runs
incremental = False
strict_optional = True
show_traceback = True
warn_no_return = False
warn_unused_ignores = True
warn_redundant_casts = True
warn_unused_configs = True
warn_unreachable = True
plugins =
mypy_django_plugin.main
[mypy.plugins.django-stubs]
django_settings_module = scripts.django_tests_settings

View File

@@ -1,9 +1,8 @@
import builtins
import os import os
import sys import sys
from collections import defaultdict from collections import defaultdict
from contextlib import contextmanager from contextlib import contextmanager
from typing import TYPE_CHECKING, Dict, Iterable, Iterator, Optional, Set, Tuple, Type, Union from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, Optional, Set, Tuple, Type, Union
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.db import models from django.db import models
@@ -39,7 +38,7 @@ if TYPE_CHECKING:
@contextmanager @contextmanager
def temp_environ(): def temp_environ() -> Iterator[None]:
"""Allow the ability to set os.environ temporarily""" """Allow the ability to set os.environ temporarily"""
environ = dict(os.environ) environ = dict(os.environ)
try: try:
@@ -56,19 +55,6 @@ def initialize_django(settings_module: str) -> Tuple["Apps", "LazySettings"]:
# add current directory to sys.path # add current directory to sys.path
sys.path.append(os.getcwd()) sys.path.append(os.getcwd())
def noop_class_getitem(cls, key):
return cls
from django.db import models
models.QuerySet.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
models.Manager.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
# Define mypy builtins, to not cause NameError during setting up Django.
# TODO: temporary/unpatch
builtins.reveal_type = lambda _: None
builtins.reveal_locals = lambda: None
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
@@ -80,8 +66,8 @@ def initialize_django(settings_module: str) -> Tuple["Apps", "LazySettings"]:
apps.populate(settings.INSTALLED_APPS) apps.populate(settings.INSTALLED_APPS)
assert apps.apps_ready assert apps.apps_ready, "Apps are not ready"
assert settings.configured assert settings.configured, "Settings are not configured"
return apps, settings return apps, settings
@@ -127,7 +113,7 @@ class DjangoContext:
return model_cls return model_cls
return None return None
def get_model_fields(self, model_cls: Type[Model]) -> Iterator[Field]: def get_model_fields(self, model_cls: Type[Model]) -> Iterator["Field[Any, Any]"]:
for field in model_cls._meta.get_fields(): for field in model_cls._meta.get_fields():
if isinstance(field, Field): if isinstance(field, Field):
yield field yield field
@@ -137,7 +123,9 @@ class DjangoContext:
if isinstance(field, ForeignObjectRel): if isinstance(field, ForeignObjectRel):
yield field yield field
def get_field_lookup_exact_type(self, api: TypeChecker, field: Union[Field, ForeignObjectRel]) -> MypyType: def get_field_lookup_exact_type(
self, api: TypeChecker, field: Union["Field[Any, Any]", ForeignObjectRel]
) -> MypyType:
if isinstance(field, (RelatedField, ForeignObjectRel)): if isinstance(field, (RelatedField, ForeignObjectRel)):
related_model_cls = field.related_model related_model_cls = field.related_model
primary_key_field = self.get_primary_key_field(related_model_cls) primary_key_field = self.get_primary_key_field(related_model_cls)
@@ -155,7 +143,7 @@ class DjangoContext:
return AnyType(TypeOfAny.explicit) return AnyType(TypeOfAny.explicit)
return helpers.get_private_descriptor_type(field_info, "_pyi_lookup_exact_type", is_nullable=field.null) return helpers.get_private_descriptor_type(field_info, "_pyi_lookup_exact_type", is_nullable=field.null)
def get_primary_key_field(self, model_cls: Type[Model]) -> Field: def get_primary_key_field(self, model_cls: Type[Model]) -> "Field[Any, Any]":
for field in model_cls._meta.get_fields(): for field in model_cls._meta.get_fields():
if isinstance(field, Field): if isinstance(field, Field):
if field.primary_key: if field.primary_key:
@@ -258,11 +246,11 @@ class DjangoContext:
def all_registered_model_class_fullnames(self) -> Set[str]: def all_registered_model_class_fullnames(self) -> Set[str]:
return {helpers.get_class_fullname(cls) for cls in self.all_registered_model_classes} return {helpers.get_class_fullname(cls) for cls in self.all_registered_model_classes}
def get_attname(self, field: Field) -> str: def get_attname(self, field: "Field[Any, Any]") -> str:
attname = field.attname attname = field.attname
return attname return attname
def get_field_nullability(self, field: Union[Field, ForeignObjectRel], method: Optional[str]) -> bool: def get_field_nullability(self, field: Union["Field[Any, Any]", ForeignObjectRel], method: Optional[str]) -> bool:
if method in ("values", "values_list"): if method in ("values", "values_list"):
return field.null return field.null
@@ -279,7 +267,9 @@ class DjangoContext:
return True return True
return nullable return nullable
def get_field_set_type(self, api: TypeChecker, field: Union[Field, ForeignObjectRel], *, method: str) -> MypyType: def get_field_set_type(
self, api: TypeChecker, field: Union["Field[Any, Any]", ForeignObjectRel], *, method: str
) -> MypyType:
"""Get a type of __set__ for this specific Django field.""" """Get a type of __set__ for this specific Django field."""
target_field = field target_field = field
if isinstance(field, ForeignKey): if isinstance(field, ForeignKey):
@@ -297,7 +287,9 @@ class DjangoContext:
field_set_type = helpers.convert_any_to_type(field_set_type, argument_field_type) field_set_type = helpers.convert_any_to_type(field_set_type, argument_field_type)
return field_set_type return field_set_type
def get_field_get_type(self, api: TypeChecker, field: Union[Field, ForeignObjectRel], *, method: str) -> MypyType: def get_field_get_type(
self, api: TypeChecker, field: Union["Field[Any, Any]", ForeignObjectRel], *, method: str
) -> MypyType:
"""Get a type of __get__ for this specific Django field.""" """Get a type of __get__ for this specific Django field."""
field_info = helpers.lookup_class_typeinfo(api, field.__class__) field_info = helpers.lookup_class_typeinfo(api, field.__class__)
if field_info is None: if field_info is None:
@@ -321,14 +313,16 @@ class DjangoContext:
else: else:
return helpers.get_private_descriptor_type(field_info, "_pyi_private_get_type", is_nullable=is_nullable) return helpers.get_private_descriptor_type(field_info, "_pyi_private_get_type", is_nullable=is_nullable)
def get_field_related_model_cls(self, field: Union[RelatedField, ForeignObjectRel]) -> Optional[Type[Model]]: def get_field_related_model_cls(
self, field: Union["RelatedField[Any, Any]", ForeignObjectRel]
) -> Optional[Type[Model]]:
if isinstance(field, RelatedField): if isinstance(field, RelatedField):
related_model_cls = field.remote_field.model related_model_cls = field.remote_field.model
else: else:
related_model_cls = field.field.model related_model_cls = field.field.model
if isinstance(related_model_cls, str): if isinstance(related_model_cls, str):
if related_model_cls == "self": # type: ignore[unreachable] if related_model_cls == "self": # type: ignore
# same model # same model
related_model_cls = field.model related_model_cls = field.model
elif "." not in related_model_cls: elif "." not in related_model_cls:
@@ -342,9 +336,9 @@ class DjangoContext:
def _resolve_field_from_parts( def _resolve_field_from_parts(
self, field_parts: Iterable[str], model_cls: Type[Model] self, field_parts: Iterable[str], model_cls: Type[Model]
) -> Union[Field, ForeignObjectRel]: ) -> Union["Field[Any, Any]", ForeignObjectRel]:
currently_observed_model = model_cls currently_observed_model = model_cls
field: Union[Field, ForeignObjectRel, GenericForeignKey, None] = None field: Union["Field[Any, Any]", ForeignObjectRel, GenericForeignKey, None] = None
for field_part in field_parts: for field_part in field_parts:
if field_part == "pk": if field_part == "pk":
field = self.get_primary_key_field(currently_observed_model) field = self.get_primary_key_field(currently_observed_model)
@@ -364,7 +358,9 @@ class DjangoContext:
assert isinstance(field, (Field, ForeignObjectRel)) assert isinstance(field, (Field, ForeignObjectRel))
return field return field
def resolve_lookup_into_field(self, model_cls: Type[Model], lookup: str) -> Union[Field, ForeignObjectRel]: def resolve_lookup_into_field(
self, model_cls: Type[Model], lookup: str
) -> Union["Field[Any, Any]", ForeignObjectRel]:
query = Query(model_cls) query = Query(model_cls)
lookup_parts, field_parts, is_expression = query.solve_lookup_type(lookup) lookup_parts, field_parts, is_expression = query.solve_lookup_type(lookup)

View File

@@ -95,7 +95,10 @@ def lookup_fully_qualified_typeinfo(api: Union[TypeChecker, SemanticAnalyzer], f
return node return node
def lookup_class_typeinfo(api: TypeChecker, klass: type) -> Optional[TypeInfo]: def lookup_class_typeinfo(api: TypeChecker, klass: Optional[type]) -> Optional[TypeInfo]:
if klass is None:
return None
fullname = get_class_fullname(klass) fullname = get_class_fullname(klass)
field_info = lookup_fully_qualified_typeinfo(api, fullname) field_info = lookup_fully_qualified_typeinfo(api, fullname)
return field_info return field_info
@@ -183,7 +186,7 @@ def get_private_descriptor_type(type_info: TypeInfo, private_field_name: str, is
return AnyType(TypeOfAny.explicit) return AnyType(TypeOfAny.explicit)
def get_field_lookup_exact_type(api: TypeChecker, field: Field) -> MypyType: def get_field_lookup_exact_type(api: TypeChecker, field: "Field[Any, Any]") -> MypyType:
if isinstance(field, (RelatedField, ForeignObjectRel)): if isinstance(field, (RelatedField, ForeignObjectRel)):
lookup_type_class = field.related_model lookup_type_class = field.related_model
rel_model_info = lookup_class_typeinfo(api, lookup_type_class) rel_model_info = lookup_class_typeinfo(api, lookup_type_class)
@@ -318,7 +321,7 @@ def resolve_string_attribute_value(attr_expr: Expression, django_context: "Djang
member_name = attr_expr.name member_name = attr_expr.name
if isinstance(attr_expr.expr, NameExpr) and attr_expr.expr.fullname == "django.conf.settings": if isinstance(attr_expr.expr, NameExpr) and attr_expr.expr.fullname == "django.conf.settings":
if hasattr(django_context.settings, member_name): if hasattr(django_context.settings, member_name):
return getattr(django_context.settings, member_name) return getattr(django_context.settings, member_name) # type: ignore
return None return None

View File

@@ -1,6 +1,6 @@
import sys import sys
from functools import partial from functools import partial
from typing import Callable, Dict, List, Optional, Tuple from typing import Callable, Dict, List, Optional, Tuple, Type
from django.db.models.fields.related import RelatedField from django.db.models.fields.related import RelatedField
from mypy.modulefinder import mypy_path from mypy.modulefinder import mypy_path
@@ -72,7 +72,7 @@ class NewSemanalDjangoPlugin(Plugin):
def _get_current_queryset_bases(self) -> Dict[str, int]: def _get_current_queryset_bases(self) -> Dict[str, int]:
model_sym = self.lookup_fully_qualified(fullnames.QUERYSET_CLASS_FULLNAME) model_sym = self.lookup_fully_qualified(fullnames.QUERYSET_CLASS_FULLNAME)
if model_sym is not None and isinstance(model_sym.node, TypeInfo): if model_sym is not None and isinstance(model_sym.node, TypeInfo):
return helpers.get_django_metadata(model_sym.node).setdefault( return helpers.get_django_metadata(model_sym.node).setdefault( # type: ignore[no-any-return]
"queryset_bases", {fullnames.QUERYSET_CLASS_FULLNAME: 1} "queryset_bases", {fullnames.QUERYSET_CLASS_FULLNAME: 1}
) )
else: else:
@@ -81,7 +81,7 @@ class NewSemanalDjangoPlugin(Plugin):
def _get_current_manager_bases(self) -> Dict[str, int]: def _get_current_manager_bases(self) -> Dict[str, int]:
model_sym = self.lookup_fully_qualified(fullnames.MANAGER_CLASS_FULLNAME) model_sym = self.lookup_fully_qualified(fullnames.MANAGER_CLASS_FULLNAME)
if model_sym is not None and isinstance(model_sym.node, TypeInfo): if model_sym is not None and isinstance(model_sym.node, TypeInfo):
return helpers.get_django_metadata(model_sym.node).setdefault( return helpers.get_django_metadata(model_sym.node).setdefault( # type: ignore[no-any-return]
"manager_bases", {fullnames.MANAGER_CLASS_FULLNAME: 1} "manager_bases", {fullnames.MANAGER_CLASS_FULLNAME: 1}
) )
else: else:
@@ -90,7 +90,7 @@ class NewSemanalDjangoPlugin(Plugin):
def _get_current_model_bases(self) -> Dict[str, int]: def _get_current_model_bases(self) -> Dict[str, int]:
model_sym = self.lookup_fully_qualified(fullnames.MODEL_CLASS_FULLNAME) model_sym = self.lookup_fully_qualified(fullnames.MODEL_CLASS_FULLNAME)
if model_sym is not None and isinstance(model_sym.node, TypeInfo): if model_sym is not None and isinstance(model_sym.node, TypeInfo):
return helpers.get_django_metadata(model_sym.node).setdefault( return helpers.get_django_metadata(model_sym.node).setdefault( # type: ignore[no-any-return]
"model_bases", {fullnames.MODEL_CLASS_FULLNAME: 1} "model_bases", {fullnames.MODEL_CLASS_FULLNAME: 1}
) )
else: else:
@@ -99,7 +99,7 @@ class NewSemanalDjangoPlugin(Plugin):
def _get_current_form_bases(self) -> Dict[str, int]: def _get_current_form_bases(self) -> Dict[str, int]:
model_sym = self.lookup_fully_qualified(fullnames.BASEFORM_CLASS_FULLNAME) model_sym = self.lookup_fully_qualified(fullnames.BASEFORM_CLASS_FULLNAME)
if model_sym is not None and isinstance(model_sym.node, TypeInfo): if model_sym is not None and isinstance(model_sym.node, TypeInfo):
return helpers.get_django_metadata(model_sym.node).setdefault( return helpers.get_django_metadata(model_sym.node).setdefault( # type: ignore[no-any-return]
"baseform_bases", "baseform_bases",
{ {
fullnames.BASEFORM_CLASS_FULLNAME: 1, fullnames.BASEFORM_CLASS_FULLNAME: 1,
@@ -305,5 +305,5 @@ class NewSemanalDjangoPlugin(Plugin):
return None return None
def plugin(version): def plugin(version: str) -> Type[NewSemanalDjangoPlugin]:
return NewSemanalDjangoPlugin return NewSemanalDjangoPlugin

View File

@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Optional, Tuple, Union, cast from typing import TYPE_CHECKING, Any, Optional, Tuple, Union, cast
from django.db.models.fields import AutoField, Field from django.db.models.fields import AutoField, Field
from django.db.models.fields.related import RelatedField from django.db.models.fields.related import RelatedField
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
def _get_current_field_from_assignment( def _get_current_field_from_assignment(
ctx: FunctionContext, django_context: DjangoContext ctx: FunctionContext, django_context: DjangoContext
) -> Optional[Union[Field, ForeignObjectRel, "GenericForeignKey"]]: ) -> Optional[Union["Field[Any, Any]", ForeignObjectRel, "GenericForeignKey"]]:
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class() outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
if outer_model_info is None or not helpers.is_model_subclass_info(outer_model_info, django_context): if outer_model_info is None or not helpers.is_model_subclass_info(outer_model_info, django_context):
return None return None
@@ -42,7 +42,7 @@ def _get_current_field_from_assignment(
return current_field return current_field
def reparametrize_related_field_type(related_field_type: Instance, set_type, get_type) -> Instance: def reparametrize_related_field_type(related_field_type: Instance, set_type: MypyType, get_type: MypyType) -> Instance:
args = [ args = [
helpers.convert_any_to_type(related_field_type.args[0], set_type), helpers.convert_any_to_type(related_field_type.args[0], set_type),
helpers.convert_any_to_type(related_field_type.args[1], get_type), helpers.convert_any_to_type(related_field_type.args[1], get_type),

View File

@@ -1,6 +1,6 @@
from typing import Optional, Union from typing import Optional, Union
from mypy.checker import TypeChecker, fill_typevars from mypy.checker import TypeChecker
from mypy.nodes import ( from mypy.nodes import (
GDEF, GDEF,
CallExpr, CallExpr,
@@ -19,6 +19,7 @@ from mypy.plugin import AttributeContext, DynamicClassDefContext, SemanticAnalyz
from mypy.types import AnyType, CallableType, Instance, ProperType from mypy.types import AnyType, CallableType, Instance, ProperType
from mypy.types import Type as MypyType from mypy.types import Type as MypyType
from mypy.types import TypeOfAny from mypy.types import TypeOfAny
from mypy.typevars import fill_typevars
from typing_extensions import Final from typing_extensions import Final
from mypy_django_plugin.lib import fullnames, helpers from mypy_django_plugin.lib import fullnames, helpers

View File

@@ -1,4 +1,4 @@
from typing import Dict, List, Optional, Type, Union, cast from typing import Any, Dict, List, Optional, Type, Union, cast
from django.db.models import Manager, Model from django.db.models import Manager, Model
from django.db.models.fields import DateField, DateTimeField, Field from django.db.models.fields import DateField, DateTimeField, Field
@@ -166,7 +166,7 @@ class ModelClassInitializer:
return queryset_info return queryset_info
def run_with_model_cls(self, model_cls): def run_with_model_cls(self, model_cls: Type[Model]) -> None:
raise NotImplementedError("Implement this in subclasses") raise NotImplementedError("Implement this in subclasses")
@@ -202,7 +202,7 @@ class AddDefaultPrimaryKey(ModelClassInitializer):
def create_autofield( def create_autofield(
self, self,
auto_field: Field, auto_field: "Field[Any, Any]",
dest_name: str, dest_name: str,
existing_field: bool, existing_field: bool,
) -> None: ) -> None:
@@ -394,7 +394,7 @@ class AddManagers(ModelClassInitializer):
return None return None
def get_dynamic_manager(self, fullname: str, manager: Manager) -> Optional[TypeInfo]: def get_dynamic_manager(self, fullname: str, manager: "Manager[Any]") -> Optional[TypeInfo]:
""" """
Try to get a dynamically defined manager Try to get a dynamically defined manager
""" """

View File

@@ -3,6 +3,7 @@ line-length = 120
include = '\.pyi?$' include = '\.pyi?$'
[tool.isort] [tool.isort]
profile = 'black'
line_length = 120 line_length = 120
multi_line_output = 3 multi_line_output = 3
include_trailing_comma = true include_trailing_comma = true

View File

@@ -7,5 +7,5 @@ addopts =
-s -s
-v -v
--cache-clear --cache-clear
--mypy-ini-file=./mypy.ini.dev --mypy-ini-file=mypy.ini
--mypy-extension-hook=scripts.tests_extension_hook.django_plugin_hook --mypy-extension-hook=scripts.tests_extension_hook.django_plugin_hook

View File

@@ -3,10 +3,10 @@ requests==2.28.1
gitpython==3.1.27 gitpython==3.1.27
pre-commit==2.20.0 pre-commit==2.20.0
pytest==7.1.2 pytest==7.1.2
pytest-mypy-plugins==1.9.3 pytest-mypy-plugins==1.10.0
psycopg2-binary psycopg2-binary
-e ./django_stubs_ext -e ./django_stubs_ext
-e .[compatible-mypy] -e .[compatible-mypy]
# Overrides: # Overrides:
mypy==0.961 mypy==0.971

View File

@@ -1,6 +1,7 @@
# Some errors occur for the test suite itself, and cannot be addressed via django-stubs. They should be ignored # Some errors occur for the test suite itself, and cannot be addressed via django-stubs. They should be ignored
# using this constant. # using this constant.
import re import re
from typing import Any, Dict, List
IGNORED_MODULES = { IGNORED_MODULES = {
"schema", "schema",
@@ -56,7 +57,7 @@ EXTERNAL_MODULES = [
"argon2", "argon2",
"xml.dom", "xml.dom",
] ]
IGNORED_ERRORS = { IGNORED_ERRORS: Dict[str, List[Any]] = {
"__common__": [ "__common__": [
*MOCK_OBJECTS, *MOCK_OBJECTS,
*EXTERNAL_MODULES, *EXTERNAL_MODULES,

View File

@@ -1,7 +1,8 @@
import shutil import shutil
from typing import Optional from typing import Optional, Union
from git import RemoteProgress, Repo from git.remote import RemoteProgress
from git.repo import Repo
from scripts.paths import DJANGO_SOURCE_DIRECTORY from scripts.paths import DJANGO_SOURCE_DIRECTORY
@@ -10,7 +11,9 @@ class ProgressPrinter(RemoteProgress):
def line_dropped(self, line: str) -> None: def line_dropped(self, line: str) -> None:
print(line) print(line)
def update(self, op_code, cur_count, max_count=None, message=""): def update(
self, op_code: int, cur_count: Union[str, float], max_count: Union[str, float, None] = None, message: str = ""
) -> None:
print(self._cur_line) print(self._cur_line)
@@ -22,7 +25,7 @@ def checkout_django_branch(django_version: str, commit_sha: Optional[str]) -> Re
repo = Repo.clone_from( repo = Repo.clone_from(
"https://github.com/django/django.git", "https://github.com/django/django.git",
DJANGO_SOURCE_DIRECTORY, DJANGO_SOURCE_DIRECTORY,
progress=ProgressPrinter(), progress=ProgressPrinter(), # type: ignore
branch=branch, branch=branch,
depth=100, depth=100,
) )

View File

@@ -5,6 +5,7 @@ from pytest_mypy_plugins.item import YamlTestItem
def django_plugin_hook(test_item: YamlTestItem) -> None: def django_plugin_hook(test_item: YamlTestItem) -> None:
custom_settings = test_item.parsed_test_data.get("custom_settings", "") custom_settings = test_item.parsed_test_data.get("custom_settings", "")
installed_apps = test_item.parsed_test_data.get("installed_apps", None) installed_apps = test_item.parsed_test_data.get("installed_apps", None)
monkeypatch = test_item.parsed_test_data.get("monkeypatch", False)
if installed_apps and custom_settings: if installed_apps and custom_settings:
raise ValueError('"installed_apps" and "custom_settings" are not compatible, please use one or the other') raise ValueError('"installed_apps" and "custom_settings" are not compatible, please use one or the other')
@@ -18,6 +19,9 @@ def django_plugin_hook(test_item: YamlTestItem) -> None:
if "SECRET_KEY" not in custom_settings: if "SECRET_KEY" not in custom_settings:
custom_settings = 'SECRET_KEY = "1"\n' + custom_settings custom_settings = 'SECRET_KEY = "1"\n' + custom_settings
if monkeypatch:
custom_settings = "import django_stubs_ext\ndjango_stubs_ext.monkeypatch()\n" + custom_settings
django_settings_section = "\n[mypy.plugins.django-stubs]\n" "django_settings_module = mysettings" django_settings_section = "\n[mypy.plugins.django-stubs]\n" "django_settings_module = mysettings"
if not test_item.additional_mypy_config: if not test_item.additional_mypy_config:
test_item.additional_mypy_config = django_settings_section test_item.additional_mypy_config = django_settings_section

View File

@@ -5,7 +5,7 @@ import sys
from argparse import ArgumentParser from argparse import ArgumentParser
from collections import defaultdict from collections import defaultdict
from distutils import spawn from distutils import spawn
from typing import Dict, List, Pattern, Union from typing import DefaultDict, List, Pattern, Union
from scripts.enabled_test_modules import EXTERNAL_MODULES, IGNORED_ERRORS, IGNORED_MODULES, MOCK_OBJECTS from scripts.enabled_test_modules import EXTERNAL_MODULES, IGNORED_ERRORS, IGNORED_MODULES, MOCK_OBJECTS
from scripts.git_helpers import checkout_django_branch from scripts.git_helpers import checkout_django_branch
@@ -18,8 +18,10 @@ DJANGO_COMMIT_REFS = {
} }
DEFAULT_DJANGO_VERSION = "3.2" DEFAULT_DJANGO_VERSION = "3.2"
_DictToSearch = DefaultDict[str, DefaultDict[Union[str, Pattern[str]], int]]
def get_unused_ignores(ignored_message_freq: Dict[str, Dict[Union[str, Pattern], int]]) -> List[str]:
def get_unused_ignores(ignored_message_freq: _DictToSearch) -> List[str]:
unused_ignores = [] unused_ignores = []
for root_key, patterns in IGNORED_ERRORS.items(): for root_key, patterns in IGNORED_ERRORS.items():
for pattern in patterns: for pattern in patterns:
@@ -30,7 +32,7 @@ def get_unused_ignores(ignored_message_freq: Dict[str, Dict[Union[str, Pattern],
return unused_ignores return unused_ignores
def does_pattern_fit(pattern: Union[Pattern, str], line: str): def does_pattern_fit(pattern: Union[Pattern[str], str], line: str) -> bool:
if isinstance(pattern, Pattern): if isinstance(pattern, Pattern):
if pattern.search(line): if pattern.search(line):
return True return True
@@ -40,7 +42,7 @@ def does_pattern_fit(pattern: Union[Pattern, str], line: str):
return False return False
def is_ignored(line: str, test_folder_name: str, *, ignored_message_freqs: Dict[str, Dict[str, int]]) -> bool: def is_ignored(line: str, test_folder_name: str, *, ignored_message_freqs: _DictToSearch) -> bool:
if "runtests" in line: if "runtests" in line:
return True return True
@@ -86,14 +88,14 @@ if __name__ == "__main__":
mypy_executable = spawn.find_executable("mypy") mypy_executable = spawn.find_executable("mypy")
mypy_argv = [mypy_executable, *mypy_options] mypy_argv = [mypy_executable, *mypy_options]
completed = subprocess.run( completed = subprocess.run(
mypy_argv, mypy_argv, # type: ignore
env={"PYTHONPATH": str(tests_root), "TYPECHECK_TESTS": "1"}, env={"PYTHONPATH": str(tests_root), "TYPECHECK_TESTS": "1"},
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
) )
output = completed.stdout.decode() output = completed.stdout.decode()
ignored_message_freqs = defaultdict(lambda: defaultdict(int)) ignored_message_freqs: _DictToSearch = defaultdict(lambda: defaultdict(int))
sorted_lines = sorted(output.splitlines()) sorted_lines = sorted(output.splitlines())
for line in sorted_lines: for line in sorted_lines:

View File

@@ -22,7 +22,7 @@ with open("README.md") as f:
dependencies = [ dependencies = [
"mypy>=0.930", "mypy>=0.930",
"django", "django",
"django-stubs-ext>=0.4.0", "django-stubs-ext>=0.6.0",
"tomli", "tomli",
# Types: # Types:
"typing-extensions", "typing-extensions",
@@ -31,7 +31,7 @@ dependencies = [
] ]
extras_require = { extras_require = {
"compatible-mypy": ["mypy>=0.930,<0.970"], "compatible-mypy": ["mypy>=0.930,<0.980"],
} }
setup( setup(
@@ -61,6 +61,7 @@ setup(
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Typing :: Typed", "Typing :: Typed",
"Framework :: Django", "Framework :: Django",
"Framework :: Django :: 2.2", "Framework :: Django :: 2.2",

View File

@@ -2,11 +2,7 @@
- case: annotated_should_not_iterfere - case: annotated_should_not_iterfere
main: | main: |
from dataclasses import dataclass from dataclasses import dataclass
import sys
if sys.version_info < (3, 9):
from typing_extensions import Annotated from typing_extensions import Annotated
else:
from typing import Annotated
class IntegerType: class IntegerType:
def __init__(self, min_value: int, max_value: int) -> None: def __init__(self, min_value: int, max_value: int) -> None:

View File

@@ -58,8 +58,6 @@
mypy_config: | mypy_config: |
[mypy.plugins.django-stubs] [mypy.plugins.django-stubs]
django_settings_module = mysettings django_settings_module = mysettings
env:
- MYPYPATH=./extras
files: files:
- path: extras/extra_module.py - path: extras/extra_module.py
content: | content: |