diff --git a/.github/workflows/release-django-stubs-ext.yml b/.github/workflows/release-django-stubs-ext.yml deleted file mode 100644 index 75c5239..0000000 --- a/.github/workflows/release-django-stubs-ext.yml +++ /dev/null @@ -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`, - }) diff --git a/.github/workflows/release-django-stubs.yml b/.github/workflows/release-django-stubs.yml deleted file mode 100644 index 36653e9..0000000 --- a/.github/workflows/release-django-stubs.yml +++ /dev/null @@ -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`, - }) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee8387b..f2b0f4c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,11 +27,40 @@ jobs: - name: Run pre-commit 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: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8'] + python-version: ['3.9'] steps: - uses: actions/checkout@v3 - name: Setup system dependencies @@ -46,7 +75,7 @@ jobs: pip install -r ./requirements.txt - name: Run tests - run: pytest --mypy-ini-file=mypy.ini + run: PYTHONPATH='.' pytest typecheck: runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 13ebc0e..11581dd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,17 +16,15 @@ repos: rev: v2.37.3 hooks: - id: pyupgrade - args: ["--py36-plus"] - - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.10.1 + args: ["--py37-plus"] + - repo: https://github.com/pycqa/isort + rev: 5.10.1 hooks: - id: isort name: isort (python) - args: ["--profile", "black"] - id: isort name: isort (pyi) types: [pyi] - args: ["--profile", "black"] - repo: https://github.com/psf/black rev: 22.6.0 hooks: @@ -35,19 +33,3 @@ repos: rev: 3.9.2 hooks: - 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" ] diff --git a/README.md b/README.md index 01bb360..51582f0 100644 --- a/README.md +++ b/README.md @@ -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. + 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. ### How can I create a HttpRequest that's guaranteed to have an authenticated user? diff --git a/django-stubs/contrib/admin/helpers.pyi b/django-stubs/contrib/admin/helpers.pyi index 13d2ed1..6753005 100644 --- a/django-stubs/contrib/admin/helpers.pyi +++ b/django-stubs/contrib/admin/helpers.pyi @@ -1,4 +1,3 @@ -import sys from typing import Any, Callable, Dict, Iterable, Iterator, List, Mapping, Optional, Sequence, Tuple, Union from django import forms @@ -10,11 +9,7 @@ from django.forms.models import ModelForm from django.forms.utils import ErrorDict, ErrorList from django.forms.widgets import Media, Widget from django.utils.safestring import SafeString - -if sys.version_info < (3, 8): - from typing_extensions import TypedDict -else: - from typing import TypedDict +from typing_extensions import TypedDict ACTION_CHECKBOX_NAME: str diff --git a/django-stubs/contrib/admin/options.pyi b/django-stubs/contrib/admin/options.pyi index 6140936..b3ccd41 100644 --- a/django-stubs/contrib/admin/options.pyi +++ b/django-stubs/contrib/admin/options.pyi @@ -1,4 +1,3 @@ -import sys from typing import ( Any, Callable, @@ -43,11 +42,7 @@ from django.template.response import _TemplateForResponseT from django.urls.resolvers import URLPattern from django.utils.datastructures import _ListOrTuple from django.utils.safestring import SafeString - -if sys.version_info < (3, 8): - from typing_extensions import Literal, TypedDict -else: - from typing import Literal, TypedDict +from typing_extensions import Literal, TypedDict IS_POPUP_VAR: str TO_FIELD_VAR: str diff --git a/django-stubs/contrib/admin/utils.pyi b/django-stubs/contrib/admin/utils.pyi index 7848900..0c89f68 100644 --- a/django-stubs/contrib/admin/utils.pyi +++ b/django-stubs/contrib/admin/utils.pyi @@ -1,5 +1,4 @@ import datetime -import sys from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Type, Union, overload from uuid import UUID @@ -15,11 +14,7 @@ from django.forms.forms import BaseForm from django.forms.formsets import BaseFormSet from django.http.request import HttpRequest from django.utils.datastructures import _IndexableCollection - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal class FieldIsAForeignKeyColumnName(Exception): ... diff --git a/django-stubs/contrib/admin/views/main.pyi b/django-stubs/contrib/admin/views/main.pyi index c824ed7..3d2d7ee 100644 --- a/django-stubs/contrib/admin/views/main.pyi +++ b/django-stubs/contrib/admin/views/main.pyi @@ -1,4 +1,3 @@ -import sys from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type, Union 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.forms.formsets import BaseFormSet from django.http.request import HttpRequest - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal ALL_VAR: str ORDER_VAR: str diff --git a/django-stubs/contrib/auth/base_user.pyi b/django-stubs/contrib/auth/base_user.pyi index 6659b23..960108d 100644 --- a/django-stubs/contrib/auth/base_user.pyi +++ b/django-stubs/contrib/auth/base_user.pyi @@ -1,15 +1,10 @@ -import sys from typing import Any, List, Optional, Tuple, TypeVar, Union, overload from django.db import models from django.db.models.base import Model from django.db.models.expressions import Combinable from django.db.models.fields import BooleanField - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal _T = TypeVar("_T", bound=Model) diff --git a/django-stubs/contrib/auth/models.pyi b/django-stubs/contrib/auth/models.pyi index 9b0123b..46d7b0f 100644 --- a/django-stubs/contrib/auth/models.pyi +++ b/django-stubs/contrib/auth/models.pyi @@ -1,4 +1,3 @@ -import sys from typing import Any, Iterable, Optional, Set, Tuple, Type, TypeVar, Union 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.models.base import Model from django.db.models.manager import EmptyManager - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal _AnyUser = Union[Model, "AnonymousUser"] diff --git a/django-stubs/contrib/auth/validators.pyi b/django-stubs/contrib/auth/validators.pyi index bbb530f..bb7c913 100644 --- a/django-stubs/contrib/auth/validators.pyi +++ b/django-stubs/contrib/auth/validators.pyi @@ -1,4 +1,4 @@ -from django.core import validators +from django.core.validators import RegexValidator -class ASCIIUsernameValidator(validators.RegexValidator): ... -class UnicodeUsernameValidator(validators.RegexValidator): ... +class ASCIIUsernameValidator(RegexValidator): ... +class UnicodeUsernameValidator(RegexValidator): ... diff --git a/django-stubs/contrib/contenttypes/management/__init__.pyi b/django-stubs/contrib/contenttypes/management/__init__.pyi index 768883b..06f5a27 100644 --- a/django-stubs/contrib/contenttypes/management/__init__.pyi +++ b/django-stubs/contrib/contenttypes/management/__init__.pyi @@ -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.registry import Apps diff --git a/django-stubs/contrib/flatpages/admin.pyi b/django-stubs/contrib/flatpages/admin.pyi index a11f296..90b045f 100644 --- a/django-stubs/contrib/flatpages/admin.pyi +++ b/django-stubs/contrib/flatpages/admin.pyi @@ -1,6 +1,6 @@ from typing import Any -from django.contrib import admin as admin +from django.contrib import admin class FlatPageAdmin(admin.ModelAdmin): form: Any = ... diff --git a/django-stubs/contrib/gis/db/backends/oracle/models.pyi b/django-stubs/contrib/gis/db/backends/oracle/models.pyi index 7d18ee3..03050df 100644 --- a/django-stubs/contrib/gis/db/backends/oracle/models.pyi +++ b/django-stubs/contrib/gis/db/backends/oracle/models.pyi @@ -1,7 +1,7 @@ from typing import Any -from django.contrib.gis.db import models as models -from django.contrib.gis.db.backends.base.models import SpatialRefSysMixin as SpatialRefSysMixin +from django.contrib.gis.db import models +from django.contrib.gis.db.backends.base.models import SpatialRefSysMixin class OracleGeometryColumns(models.Model): table_name: Any = ... diff --git a/django-stubs/contrib/gis/db/backends/postgis/operations.pyi b/django-stubs/contrib/gis/db/backends/postgis/operations.pyi index 6dc4d3f..653595f 100644 --- a/django-stubs/contrib/gis/db/backends/postgis/operations.pyi +++ b/django-stubs/contrib/gis/db/backends/postgis/operations.pyi @@ -1,15 +1,10 @@ -import sys from typing import Any, Union from django.contrib.gis.db.backends.base.operations import BaseSpatialOperations from django.contrib.gis.db.backends.utils import SpatialOperator from django.db.backends.postgresql.operations import DatabaseOperations from django.db.models import Func - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal BILATERAL: Literal["bilateral"] diff --git a/django-stubs/contrib/gis/forms/fields.pyi b/django-stubs/contrib/gis/forms/fields.pyi index 8141faf..10f668f 100644 --- a/django-stubs/contrib/gis/forms/fields.pyi +++ b/django-stubs/contrib/gis/forms/fields.pyi @@ -1,6 +1,6 @@ from typing import Any, Optional -from django import forms as forms +from django import forms class GeometryField(forms.Field): widget: Any = ... diff --git a/django-stubs/contrib/postgres/fields/ranges.pyi b/django-stubs/contrib/postgres/fields/ranges.pyi index 75c3ffe..5e38c39 100644 --- a/django-stubs/contrib/postgres/fields/ranges.pyi +++ b/django-stubs/contrib/postgres/fields/ranges.pyi @@ -1,14 +1,9 @@ -import sys from typing import Any, Dict, Optional, Type from django.db import models from django.db.models.lookups import PostgresOperatorLookup from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange, Range - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal class RangeBoundary(models.Expression): lower: str diff --git a/django-stubs/contrib/postgres/operations.pyi b/django-stubs/contrib/postgres/operations.pyi index b901495..11e5261 100644 --- a/django-stubs/contrib/postgres/operations.pyi +++ b/django-stubs/contrib/postgres/operations.pyi @@ -1,8 +1,7 @@ -from typing import Dict, Literal, Tuple - from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.migrations import AddConstraint, AddIndex, RemoveIndex from django.db.migrations.operations.base import Operation +from typing_extensions import Literal class CreateExtension(Operation): reversible: bool = ... diff --git a/django-stubs/contrib/redirects/admin.pyi b/django-stubs/contrib/redirects/admin.pyi index dc71b2d..e4dbdbd 100644 --- a/django-stubs/contrib/redirects/admin.pyi +++ b/django-stubs/contrib/redirects/admin.pyi @@ -1,6 +1,6 @@ from typing import Any -from django.contrib import admin as admin +from django.contrib import admin class RedirectAdmin(admin.ModelAdmin): list_display: Any = ... diff --git a/django-stubs/contrib/sites/admin.pyi b/django-stubs/contrib/sites/admin.pyi index c80181f..0f8db71 100644 --- a/django-stubs/contrib/sites/admin.pyi +++ b/django-stubs/contrib/sites/admin.pyi @@ -1,6 +1,6 @@ from typing import Any -from django.contrib import admin as admin +from django.contrib import admin class SiteAdmin(admin.ModelAdmin): list_display: Any = ... diff --git a/django-stubs/contrib/staticfiles/finders.pyi b/django-stubs/contrib/staticfiles/finders.pyi index 02691bd..25aef21 100644 --- a/django-stubs/contrib/staticfiles/finders.pyi +++ b/django-stubs/contrib/staticfiles/finders.pyi @@ -1,13 +1,8 @@ -import sys from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Tuple, Type, overload from django.core.checks.messages import CheckMessage from django.core.files.storage import FileSystemStorage, Storage - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal searched_locations: Any diff --git a/django-stubs/core/checks/registry.pyi b/django-stubs/core/checks/registry.pyi index 2bb9e4d..a6d1234 100644 --- a/django-stubs/core/checks/registry.pyi +++ b/django-stubs/core/checks/registry.pyi @@ -1,13 +1,8 @@ -import sys from typing import Any, Callable, List, Optional, Sequence, Set, TypeVar, Union from django.apps.config import AppConfig from django.core.checks.messages import CheckMessage - -if sys.version_info < (3, 8): - from typing_extensions import Protocol -else: - from typing import Protocol +from typing_extensions import Protocol class Tags: admin: str = ... diff --git a/django-stubs/core/exceptions.pyi b/django-stubs/core/exceptions.pyi index 3ccd139..43211be 100644 --- a/django-stubs/core/exceptions.pyi +++ b/django-stubs/core/exceptions.pyi @@ -1,10 +1,6 @@ -import sys from typing import Any, Dict, Iterator, List, Optional, Tuple, Union -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal class FieldDoesNotExist(Exception): ... class AppRegistryNotReady(Exception): ... diff --git a/django-stubs/core/management/base.pyi b/django-stubs/core/management/base.pyi index 9026b30..a591f3c 100644 --- a/django-stubs/core/management/base.pyi +++ b/django-stubs/core/management/base.pyi @@ -1,4 +1,3 @@ -import sys from argparse import ArgumentParser, HelpFormatter, Namespace from io import TextIOBase 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.core.management.color import Style from django.utils.datastructures import _ListOrTuple - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal class CommandError(Exception): def __init__(self, *args: Any, returncode: int = ..., **kwargs: Any) -> None: ... diff --git a/django-stubs/db/__init__.pyi b/django-stubs/db/__init__.pyi index 1d32e71..5633b9e 100644 --- a/django-stubs/db/__init__.pyi +++ b/django-stubs/db/__init__.pyi @@ -2,7 +2,7 @@ from typing import Any from django.utils.connection import ConnectionProxy -from . import migrations +from . import migrations as migrations from .backends.base.base import BaseDatabaseWrapper 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 diff --git a/django-stubs/db/backends/ddl_references.pyi b/django-stubs/db/backends/ddl_references.pyi index b1f8e98..c0ae483 100644 --- a/django-stubs/db/backends/ddl_references.pyi +++ b/django-stubs/db/backends/ddl_references.pyi @@ -1,10 +1,6 @@ -import sys from typing import Any, Dict, List, Sequence -if sys.version_info < (3, 8): - from typing_extensions import Protocol -else: - from typing import Protocol +from typing_extensions import Protocol class _QuoteCallable(Protocol): """Get rid of `cannot assign to method`""" diff --git a/django-stubs/db/backends/mysql/base.pyi b/django-stubs/db/backends/mysql/base.pyi index 63fa0f6..5f3a2a5 100644 --- a/django-stubs/db/backends/mysql/base.pyi +++ b/django-stubs/db/backends/mysql/base.pyi @@ -1,7 +1,7 @@ -import sys from typing import Any, Dict, Optional, Type from django.db.backends.base.base import BaseDatabaseWrapper as BaseDatabaseWrapper +from typing_extensions import Literal from .client import DatabaseClient from .creation import DatabaseCreation @@ -10,11 +10,6 @@ from .introspection import DatabaseIntrospection from .operations import DatabaseOperations from .validation import DatabaseValidation -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal - version: Any django_conversions: Any server_version_re: Any diff --git a/django-stubs/db/backends/mysql/compiler.pyi b/django-stubs/db/backends/mysql/compiler.pyi index f699d8d..54f03b5 100644 --- a/django-stubs/db/backends/mysql/compiler.pyi +++ b/django-stubs/db/backends/mysql/compiler.pyi @@ -9,7 +9,8 @@ class SQLCompiler(compiler.SQLCompiler): class SQLInsertCompiler(compiler.SQLInsertCompiler, 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 SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler): ... diff --git a/django-stubs/db/backends/utils.pyi b/django-stubs/db/backends/utils.pyi index be872ad..f89b64a 100644 --- a/django-stubs/db/backends/utils.pyi +++ b/django-stubs/db/backends/utils.pyi @@ -1,5 +1,4 @@ import datetime -import sys import types from contextlib import contextmanager from decimal import Decimal @@ -21,10 +20,7 @@ from typing import ( ) from uuid import UUID -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal logger: Logger diff --git a/django-stubs/db/migrations/executor.pyi b/django-stubs/db/migrations/executor.pyi index fd4f30f..6258594 100644 --- a/django-stubs/db/migrations/executor.pyi +++ b/django-stubs/db/migrations/executor.pyi @@ -1,18 +1,13 @@ -import sys from typing import List, Optional, Sequence, Set, Tuple, Union from django.db.backends.base.base import BaseDatabaseWrapper from django.db.migrations.migration import Migration +from typing_extensions import Protocol from .loader import MigrationLoader from .recorder import MigrationRecorder from .state import ProjectState -if sys.version_info < (3, 8): - from typing_extensions import Protocol -else: - from typing import Protocol - class _ProgressCallbackT(Protocol): def __call__(self, __action: str, __migration: Optional[Migration] = ..., __fake: Optional[bool] = ...) -> None: ... diff --git a/django-stubs/db/migrations/operations/special.pyi b/django-stubs/db/migrations/operations/special.pyi index af8f869..9edba8b 100644 --- a/django-stubs/db/migrations/operations/special.pyi +++ b/django-stubs/db/migrations/operations/special.pyi @@ -1,17 +1,12 @@ -import sys from typing import Any, Dict, Mapping, Optional, Sequence, Tuple, Union from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.migrations.state import StateApps from django.utils.datastructures import _ListOrTuple +from typing_extensions import Literal, Protocol 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): database_operations: Sequence[Operation] = ... state_operations: Sequence[Operation] = ... diff --git a/django-stubs/db/migrations/operations/utils.pyi b/django-stubs/db/migrations/operations/utils.pyi index 0253213..666385d 100644 --- a/django-stubs/db/migrations/operations/utils.pyi +++ b/django-stubs/db/migrations/operations/utils.pyi @@ -1,14 +1,9 @@ -import sys from collections import namedtuple from typing import Iterator, Optional, Tuple, Union from django.db.migrations.state import ModelState, ProjectState from django.db.models import Field - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal def resolve_relation(model, app_label: Optional[str] = ..., model_name: Optional[str] = ...) -> Tuple[str, str]: ... diff --git a/django-stubs/db/models/expressions.pyi b/django-stubs/db/models/expressions.pyi index eec4fa0..f7c838e 100644 --- a/django-stubs/db/models/expressions.pyi +++ b/django-stubs/db/models/expressions.pyi @@ -1,5 +1,4 @@ import datetime -import sys from decimal import Decimal 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.sql.compiler import SQLCompiler, _AsSqlType from django.db.models.sql.query import Query - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal class SQLiteNumericMixin: def as_sqlite(self, compiler: SQLCompiler, connection: BaseDatabaseWrapper, **extra_context: Any) -> _AsSqlType: ... diff --git a/django-stubs/db/models/fields/__init__.pyi b/django-stubs/db/models/fields/__init__.pyi index 299fe0c..7a06019 100644 --- a/django-stubs/db/models/fields/__init__.pyi +++ b/django-stubs/db/models/fields/__init__.pyi @@ -1,5 +1,4 @@ import decimal -import sys import uuid from datetime import date 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.utils.datastructures import DictWrapper from django.utils.functional import _Getter - -if sys.version_info < (3, 8): - from typing_extensions import Protocol -else: - from typing import Protocol +from typing_extensions import Protocol class Empty: ... class NOT_PROVIDED: ... diff --git a/django-stubs/db/models/fields/files.pyi b/django-stubs/db/models/fields/files.pyi index a6c397e..8467b5d 100644 --- a/django-stubs/db/models/fields/files.pyi +++ b/django-stubs/db/models/fields/files.pyi @@ -1,5 +1,3 @@ -import sys -from pathlib import Path from typing import Any, Callable, Iterable, Optional, Type, TypeVar, Union, overload 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.query_utils import DeferredAttribute from django.utils._os import _PathCompatible - -if sys.version_info < (3, 8): - from typing_extensions import Protocol -else: - from typing import Protocol +from typing_extensions import Protocol class FieldFile(File): instance: Model = ... diff --git a/django-stubs/db/models/fields/related.pyi b/django-stubs/db/models/fields/related.pyi index 42bb595..3b9fc61 100644 --- a/django-stubs/db/models/fields/related.pyi +++ b/django-stubs/db/models/fields/related.pyi @@ -1,12 +1,6 @@ -import sys from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type, TypeVar, Union, overload 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.db import models 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.manager import RelatedManager from django.db.models.query_utils import FilteredRelation, PathInfo, Q +from typing_extensions import Literal _T = TypeVar("_T", bound=models.Model) _F = TypeVar("_F", bound=models.Field) diff --git a/django-stubs/db/models/fields/reverse_related.pyi b/django-stubs/db/models/fields/reverse_related.pyi index 7e4162e..1503cc6 100644 --- a/django-stubs/db/models/fields/reverse_related.pyi +++ b/django-stubs/db/models/fields/reverse_related.pyi @@ -1,21 +1,15 @@ -import sys -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union +from typing import Any, Callable, List, Optional, Sequence, Tuple, Type, Union -from django.db.models import Q from django.db.models.base import Model 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.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.sql.where import WhereNode +from typing_extensions import Literal 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__`. # When parent's `contribute_to_class` is called (during startup), # strings are resolved to real model classes. diff --git a/django-stubs/db/models/lookups.pyi b/django-stubs/db/models/lookups.pyi index c97ad45..163d548 100644 --- a/django-stubs/db/models/lookups.pyi +++ b/django-stubs/db/models/lookups.pyi @@ -1,16 +1,11 @@ -import sys 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.models.expressions import Expression, Func from django.db.models.query_utils import RegisterLookupMixin from django.db.models.sql.compiler import SQLCompiler, _AsSqlType, _ParamT from django.utils.datastructures import OrderedSet +from typing_extensions import Literal _L = TypeVar("_L", bound="Lookup") _T = TypeVar("_T") diff --git a/django-stubs/db/models/options.pyi b/django-stubs/db/models/options.pyi index 54aa308..8ce2e9d 100644 --- a/django-stubs/db/models/options.pyi +++ b/django-stubs/db/models/options.pyi @@ -1,4 +1,3 @@ -import sys from typing import Any, Dict, Generic, Iterable, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, overload 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.query_utils import PathInfo from django.utils.datastructures import ImmutableList, _ListOrTuple - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal PROXY_PARENTS: object EMPTY_RELATION_TREE: Any diff --git a/django-stubs/db/models/query_utils.pyi b/django-stubs/db/models/query_utils.pyi index 24b02ac..cf17b9f 100644 --- a/django-stubs/db/models/query_utils.pyi +++ b/django-stubs/db/models/query_utils.pyi @@ -1,4 +1,3 @@ -import sys from collections import namedtuple from typing import ( Any, @@ -17,23 +16,17 @@ from typing import ( 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.models.base import Model from django.db.models.expressions import BaseExpression from django.db.models.fields import Field from django.db.models.fields.mixins import FieldCacheMixin 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.where import WhereNode from django.utils import tree +from typing_extensions import Literal PathInfo = namedtuple( "PathInfo", ["from_opts", "to_opts", "target_fields", "join_field", "m2m", "direct", "filtered_relation"] diff --git a/django-stubs/db/models/sql/compiler.pyi b/django-stubs/db/models/sql/compiler.pyi index cedb6fb..80fe68a 100644 --- a/django-stubs/db/models/sql/compiler.pyi +++ b/django-stubs/db/models/sql/compiler.pyi @@ -1,21 +1,15 @@ -import sys from datetime import date, datetime 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 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.utils import CursorWrapper from django.db.models.base import Model from django.db.models.expressions import BaseExpression, Expression from django.db.models.sql.query import Query from django.db.models.sql.subqueries import AggregateQuery, DeleteQuery, InsertQuery, UpdateQuery +from typing_extensions import Literal _ParamT = Union[str, int] _ParamsT = List[_ParamT] diff --git a/django-stubs/db/models/sql/constants.pyi b/django-stubs/db/models/sql/constants.pyi index f0e7e9b..f4dfc92 100644 --- a/django-stubs/db/models/sql/constants.pyi +++ b/django-stubs/db/models/sql/constants.pyi @@ -1,10 +1,6 @@ -import sys -from typing import Dict, Final, Pattern, Tuple +from typing import Dict, Pattern, Tuple -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Final, Literal GET_ITERATOR_CHUNK_SIZE: Final[int] = ... diff --git a/django-stubs/db/models/sql/query.pyi b/django-stubs/db/models/sql/query.pyi index 733af1a..e815c4b 100644 --- a/django-stubs/db/models/sql/query.pyi +++ b/django-stubs/db/models/sql/query.pyi @@ -1,13 +1,7 @@ import collections -import sys from collections import namedtuple 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.utils import CursorWrapper 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.datastructures import BaseTable, Join from django.db.models.sql.where import WhereNode +from typing_extensions import Literal JoinInfo = namedtuple("JoinInfo", ("final_field", "targets", "opts", "joins", "path", "transform_function")) diff --git a/django-stubs/forms/models.pyi b/django-stubs/forms/models.pyi index b0bc1db..881babe 100644 --- a/django-stubs/forms/models.pyi +++ b/django-stubs/forms/models.pyi @@ -1,4 +1,3 @@ -import sys from typing import ( Any, Callable, @@ -34,11 +33,7 @@ from django.forms.renderers import BaseRenderer from django.forms.utils import ErrorList, _DataT, _FilesT from django.forms.widgets import ChoiceWidget, Input, Widget from django.utils.datastructures import _IndexableCollection, _ListOrTuple, _PropertyDescriptor - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal ALL_FIELDS: Literal["__all__"] diff --git a/django-stubs/forms/widgets.pyi b/django-stubs/forms/widgets.pyi index 3913f1f..4c7f7ed 100644 --- a/django-stubs/forms/widgets.pyi +++ b/django-stubs/forms/widgets.pyi @@ -1,5 +1,4 @@ import datetime -import sys from typing import Any, Dict, Iterable, Iterator, List, Mapping, Optional, Sequence, Tuple, Type, Union 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.functional import _Getter from django.utils.safestring import SafeString -from typing_extensions import Literal - -if sys.version_info < (3, 8): - from typing_extensions import Protocol -else: - from typing import Protocol +from typing_extensions import Literal, Protocol _OptAttrs = Dict[str, Any] diff --git a/django-stubs/http/multipartparser.pyi b/django-stubs/http/multipartparser.pyi index ec83f5a..5181052 100644 --- a/django-stubs/http/multipartparser.pyi +++ b/django-stubs/http/multipartparser.pyi @@ -1,20 +1,15 @@ -import sys from typing import IO, Any, Dict, Iterator, List, Mapping, Optional, Tuple, Union from django.http.request import QueryDict from django.utils.datastructures import ImmutableList, MultiValueDict - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal class MultiPartParserError(Exception): ... class InputStreamExhausted(Exception): ... -RAW: Literal["raw"] = "raw" -FILE: Literal["file"] = "file" -FIELD: Literal["field"] = "field" +RAW: Literal["raw"] +FILE: Literal["file"] +FIELD: Literal["field"] class MultiPartParser: def __init__( diff --git a/django-stubs/http/request.pyi b/django-stubs/http/request.pyi index 9dd4b82..8890d3c 100644 --- a/django-stubs/http/request.pyi +++ b/django-stubs/http/request.pyi @@ -1,4 +1,3 @@ -import sys from io import BytesIO from typing import ( Any, @@ -25,11 +24,7 @@ from django.contrib.sites.models import Site from django.core.files import uploadedfile, uploadhandler from django.urls import ResolverMatch from django.utils.datastructures import CaseInsensitiveMapping, ImmutableList, MultiValueDict - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal RAISE_ERROR: object = ... host_validation_re: Pattern[str] = ... diff --git a/django-stubs/shortcuts.pyi b/django-stubs/shortcuts.pyi index 8948bd2..3e43b7d 100644 --- a/django-stubs/shortcuts.pyi +++ b/django-stubs/shortcuts.pyi @@ -1,4 +1,3 @@ -import sys from typing import Any, Callable, List, Mapping, Optional, Protocol, Sequence, Type, TypeVar, Union, overload 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 HttpResponsePermanentRedirect as HttpResponsePermanentRedirect from django.http.response import HttpResponseRedirect as HttpResponseRedirect - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal def render( request: HttpRequest, diff --git a/django-stubs/urls/base.pyi b/django-stubs/urls/base.pyi index 7cd74f0..25f4690 100644 --- a/django-stubs/urls/base.pyi +++ b/django-stubs/urls/base.pyi @@ -1,12 +1,7 @@ -import sys from typing import Any, Callable, Dict, Optional, Sequence, Union from django.urls.resolvers import ResolverMatch - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal def resolve(path: str, urlconf: Optional[str] = ...) -> ResolverMatch: ... def reverse( diff --git a/django-stubs/utils/datastructures.pyi b/django-stubs/utils/datastructures.pyi index 4087229..3fc57fd 100644 --- a/django-stubs/utils/datastructures.pyi +++ b/django-stubs/utils/datastructures.pyi @@ -1,4 +1,3 @@ -import sys from typing import ( Any, Collection, @@ -16,10 +15,7 @@ from typing import ( overload, ) -if sys.version_info < (3, 8): - from typing_extensions import Protocol -else: - from typing import Protocol +from typing_extensions import Protocol _K = TypeVar("_K") _V = TypeVar("_V") diff --git a/django-stubs/utils/dateformat.pyi b/django-stubs/utils/dateformat.pyi index b208a78..274060b 100644 --- a/django-stubs/utils/dateformat.pyi +++ b/django-stubs/utils/dateformat.pyi @@ -1,15 +1,10 @@ -import sys from datetime import date from datetime import datetime as builtin_datetime from datetime import time as builtin_time from typing import Any, Optional, Pattern, Union from django.utils.timezone import _TzInfoT - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal re_formatchars: Pattern[str] re_escaped: Pattern[str] diff --git a/django-stubs/utils/encoding.pyi b/django-stubs/utils/encoding.pyi index 71fd52d..34388e9 100644 --- a/django-stubs/utils/encoding.pyi +++ b/django-stubs/utils/encoding.pyi @@ -1,15 +1,9 @@ import datetime -import sys from decimal import Decimal 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 typing_extensions import Literal +from typing_extensions import Literal, TypeGuard class DjangoUnicodeDecodeError(UnicodeDecodeError): obj: bytes = ... diff --git a/django-stubs/utils/functional.pyi b/django-stubs/utils/functional.pyi index 9809cf1..0f1e349 100644 --- a/django-stubs/utils/functional.pyi +++ b/django-stubs/utils/functional.pyi @@ -1,13 +1,8 @@ -import sys from functools import wraps as wraps # noqa: F401 from typing import Any, Callable, Generic, List, Optional, Tuple, Type, TypeVar, Union, overload from django.db.models.base import Model - -if sys.version_info < (3, 8): - from typing_extensions import Protocol -else: - from typing import Protocol +from typing_extensions import Protocol _T = TypeVar("_T") diff --git a/django-stubs/utils/timezone.pyi b/django-stubs/utils/timezone.pyi index 77c51a4..699ca9e 100644 --- a/django-stubs/utils/timezone.pyi +++ b/django-stubs/utils/timezone.pyi @@ -1,4 +1,3 @@ -import sys import types from contextlib import ContextDecorator from datetime import date @@ -9,18 +8,9 @@ from datetime import timezone from datetime import tzinfo as tzinfo 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 from pytz import BaseTzInfo +from typing_extensions import Literal, TypeGuard _PytzTzInfoT = Union[pytz.tzinfo.BaseTzInfo, pytz._FixedOffset] _TzInfoT = Union[_PytzTzInfoT, tzinfo] diff --git a/django-stubs/utils/translation/trans_real.pyi b/django-stubs/utils/translation/trans_real.pyi index af3134c..411f257 100644 --- a/django-stubs/utils/translation/trans_real.pyi +++ b/django-stubs/utils/translation/trans_real.pyi @@ -1,14 +1,9 @@ import gettext as gettext_module -import sys 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 - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal CONTEXT_SEPARATOR: Literal["\x04"] accept_language_re: Pattern[str] diff --git a/django-stubs/views/generic/edit.pyi b/django-stubs/views/generic/edit.pyi index dcbfe21..1736670 100644 --- a/django-stubs/views/generic/edit.pyi +++ b/django-stubs/views/generic/edit.pyi @@ -1,4 +1,3 @@ -import sys from typing import Any, Dict, Generic, Optional, Type, TypeVar, Union from django.db import models @@ -8,11 +7,7 @@ from django.http import HttpRequest, HttpResponse from django.utils.datastructures import _ListOrTuple from django.views.generic.base import ContextMixin, TemplateResponseMixin, View from django.views.generic.detail import BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin - -if sys.version_info < (3, 8): - from typing_extensions import Literal -else: - from typing import Literal +from typing_extensions import Literal _FormT = TypeVar("_FormT", bound=BaseForm) _ModelFormT = TypeVar("_ModelFormT", bound=BaseModelForm) diff --git a/django_stubs_ext/django_stubs_ext/patch.py b/django_stubs_ext/django_stubs_ext/patch.py index 45767c6..6d2125a 100644 --- a/django_stubs_ext/django_stubs_ext/patch.py +++ b/django_stubs_ext/django_stubs_ext/patch.py @@ -1,3 +1,4 @@ +import builtins from typing import Any, Generic, Iterable, List, Optional, Tuple, Type, TypeVar 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.list import MultipleObjectMixin +__all__ = ["monkeypatch"] + _T = TypeVar("_T") _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.""" # Add the __class_getitem__ dunder. @@ -81,5 +84,7 @@ def monkeypatch(extra_classes: Optional[Iterable[type]] = None) -> None: for cls in extra_classes: cls.__class_getitem__ = classmethod(lambda cls, *args, **kwargs: cls) # type: ignore[attr-defined] - -__all__ = ["monkeypatch"] + # Add `reveal_type` and `reveal_locals` helpers if needed: + if include_builtins: + builtins.reveal_type = lambda _: None + builtins.reveal_locals = lambda: None diff --git a/django_stubs_ext/django_stubs_ext/types.py b/django_stubs_ext/django_stubs_ext/types.py index 0d606c4..da321a4 100644 --- a/django_stubs_ext/django_stubs_ext/types.py +++ b/django_stubs_ext/django_stubs_ext/types.py @@ -1,10 +1,7 @@ -import sys from typing import Any -if sys.version_info < (3, 8): - from typing_extensions import Protocol -else: - from typing import Protocol +from typing_extensions import Protocol + # Used internally by mypy_django_plugin. class AnyAttrAllowed(Protocol): diff --git a/django_stubs_ext/setup.py b/django_stubs_ext/setup.py index 7751e88..c24c51e 100644 --- a/django_stubs_ext/setup.py +++ b/django_stubs_ext/setup.py @@ -12,7 +12,7 @@ dependencies = [ setup( name="django-stubs-ext", - version="0.5.0", + version="0.6.0", description="Monkey-patching and extensions for django-stubs", long_description=readme, long_description_content_type="text/markdown", @@ -23,7 +23,7 @@ setup( maintainer="Nikita Sobolev", maintainer_email="mail@sobolevn.me", py_modules=[], - python_requires=">=3.6", + python_requires=">=3.7", install_requires=dependencies, packages=["django_stubs_ext", *find_packages(exclude=["scripts"])], package_data={"django_stubs_ext": ["py.typed"]}, @@ -33,6 +33,7 @@ setup( "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Typing :: Typed", "Framework :: Django", "Framework :: Django :: 2.2", diff --git a/django_stubs_ext/tests/test_monkeypatching.py b/django_stubs_ext/tests/test_monkeypatching.py index fa668fc..3eb1c59 100644 --- a/django_stubs_ext/tests/test_monkeypatching.py +++ b/django_stubs_ext/tests/test_monkeypatching.py @@ -1,13 +1,12 @@ import builtins from contextlib import suppress -from typing import Iterable, Optional +from typing import Iterable, List, Optional import pytest from _pytest.fixtures import FixtureRequest from _pytest.monkeypatch import MonkeyPatch from django.db.models import Model from django.forms.models import ModelForm -from django.views import View from typing_extensions import Protocol import django_stubs_ext @@ -19,7 +18,10 @@ class _MakeGenericClasses(Protocol): """Used to represent a type of ``make_generic_classes`` fixture.""" 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: ... @@ -29,7 +31,7 @@ def make_generic_classes( request: FixtureRequest, monkeypatch: MonkeyPatch, ) -> _MakeGenericClasses: - _extra_classes: list[type] = [] + _extra_classes: List[type] = [] def fin() -> None: for el in _need_generic: @@ -38,13 +40,22 @@ def make_generic_classes( for cls in _extra_classes: with suppress(AttributeError): 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: _extra_classes.extend(extra_classes) if django_version is not None: 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) 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: """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) for cls in extra_classes: assert cls[type] is cls # type: ignore[misc] - class TestView(View[Model]): # type: ignore[type-arg] + class _TestGeneric(_NotGeneric[Model]): # type: ignore pass @@ -106,7 +120,17 @@ def test_mypy_builtins_not_patched_globally( This should only happend during `django.setup()` (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_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") diff --git a/django_stubs_ext/tests/typecheck/test_patching.yml b/django_stubs_ext/tests/typecheck/test_patching.yml index cbab8fd..e02f1eb 100644 --- a/django_stubs_ext/tests/typecheck/test_patching.yml +++ b/django_stubs_ext/tests/typecheck/test_patching.yml @@ -3,6 +3,7 @@ # `main` is empty on purpose, because we test the `django.setup()` step. # It executes the `myapp/models.py` file and should not produce anything, # because `reveal_type` and `reveal_locals` are patched. + monkeypatch: true installed_apps: - myapp files: diff --git a/mypy.ini b/mypy.ini index e86c0eb..c76042b 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,10 +1,5 @@ # 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] allow_redefinition = True check_untyped_defs = True diff --git a/mypy.ini.dev b/mypy.ini.dev deleted file mode 100644 index 22f5e44..0000000 --- a/mypy.ini.dev +++ /dev/null @@ -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 diff --git a/mypy_django_plugin/django/context.py b/mypy_django_plugin/django/context.py index 2a425a6..56598d3 100644 --- a/mypy_django_plugin/django/context.py +++ b/mypy_django_plugin/django/context.py @@ -1,9 +1,8 @@ -import builtins import os import sys from collections import defaultdict 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.db import models @@ -39,7 +38,7 @@ if TYPE_CHECKING: @contextmanager -def temp_environ(): +def temp_environ() -> Iterator[None]: """Allow the ability to set os.environ temporarily""" environ = dict(os.environ) try: @@ -56,19 +55,6 @@ def initialize_django(settings_module: str) -> Tuple["Apps", "LazySettings"]: # add current directory to sys.path 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.conf import settings @@ -80,8 +66,8 @@ def initialize_django(settings_module: str) -> Tuple["Apps", "LazySettings"]: apps.populate(settings.INSTALLED_APPS) - assert apps.apps_ready - assert settings.configured + assert apps.apps_ready, "Apps are not ready" + assert settings.configured, "Settings are not configured" return apps, settings @@ -127,7 +113,7 @@ class DjangoContext: return model_cls 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(): if isinstance(field, Field): yield field @@ -137,7 +123,9 @@ class DjangoContext: if isinstance(field, ForeignObjectRel): 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)): related_model_cls = field.related_model primary_key_field = self.get_primary_key_field(related_model_cls) @@ -155,7 +143,7 @@ class DjangoContext: return AnyType(TypeOfAny.explicit) 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(): if isinstance(field, Field): if field.primary_key: @@ -258,11 +246,11 @@ class DjangoContext: def all_registered_model_class_fullnames(self) -> Set[str]: 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 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"): return field.null @@ -279,7 +267,9 @@ class DjangoContext: return True 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.""" target_field = field if isinstance(field, ForeignKey): @@ -297,7 +287,9 @@ class DjangoContext: field_set_type = helpers.convert_any_to_type(field_set_type, argument_field_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.""" field_info = helpers.lookup_class_typeinfo(api, field.__class__) if field_info is None: @@ -321,14 +313,16 @@ class DjangoContext: else: 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): related_model_cls = field.remote_field.model else: related_model_cls = field.field.model if isinstance(related_model_cls, str): - if related_model_cls == "self": # type: ignore[unreachable] + if related_model_cls == "self": # type: ignore # same model related_model_cls = field.model elif "." not in related_model_cls: @@ -342,9 +336,9 @@ class DjangoContext: def _resolve_field_from_parts( self, field_parts: Iterable[str], model_cls: Type[Model] - ) -> Union[Field, ForeignObjectRel]: + ) -> Union["Field[Any, Any]", ForeignObjectRel]: 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: if field_part == "pk": field = self.get_primary_key_field(currently_observed_model) @@ -364,7 +358,9 @@ class DjangoContext: assert isinstance(field, (Field, ForeignObjectRel)) 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) lookup_parts, field_parts, is_expression = query.solve_lookup_type(lookup) diff --git a/mypy_django_plugin/lib/helpers.py b/mypy_django_plugin/lib/helpers.py index abbdccb..b78fad4 100644 --- a/mypy_django_plugin/lib/helpers.py +++ b/mypy_django_plugin/lib/helpers.py @@ -95,7 +95,10 @@ def lookup_fully_qualified_typeinfo(api: Union[TypeChecker, SemanticAnalyzer], f 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) field_info = lookup_fully_qualified_typeinfo(api, fullname) return field_info @@ -183,7 +186,7 @@ def get_private_descriptor_type(type_info: TypeInfo, private_field_name: str, is 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)): lookup_type_class = field.related_model 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 if isinstance(attr_expr.expr, NameExpr) and attr_expr.expr.fullname == "django.conf.settings": 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 diff --git a/mypy_django_plugin/main.py b/mypy_django_plugin/main.py index ad44932..ca4b680 100644 --- a/mypy_django_plugin/main.py +++ b/mypy_django_plugin/main.py @@ -1,6 +1,6 @@ import sys 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 mypy.modulefinder import mypy_path @@ -72,7 +72,7 @@ class NewSemanalDjangoPlugin(Plugin): def _get_current_queryset_bases(self) -> Dict[str, int]: model_sym = self.lookup_fully_qualified(fullnames.QUERYSET_CLASS_FULLNAME) 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} ) else: @@ -81,7 +81,7 @@ class NewSemanalDjangoPlugin(Plugin): def _get_current_manager_bases(self) -> Dict[str, int]: model_sym = self.lookup_fully_qualified(fullnames.MANAGER_CLASS_FULLNAME) 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} ) else: @@ -90,7 +90,7 @@ class NewSemanalDjangoPlugin(Plugin): def _get_current_model_bases(self) -> Dict[str, int]: model_sym = self.lookup_fully_qualified(fullnames.MODEL_CLASS_FULLNAME) 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} ) else: @@ -99,7 +99,7 @@ class NewSemanalDjangoPlugin(Plugin): def _get_current_form_bases(self) -> Dict[str, int]: model_sym = self.lookup_fully_qualified(fullnames.BASEFORM_CLASS_FULLNAME) 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", { fullnames.BASEFORM_CLASS_FULLNAME: 1, @@ -305,5 +305,5 @@ class NewSemanalDjangoPlugin(Plugin): return None -def plugin(version): +def plugin(version: str) -> Type[NewSemanalDjangoPlugin]: return NewSemanalDjangoPlugin diff --git a/mypy_django_plugin/transformers/fields.py b/mypy_django_plugin/transformers/fields.py index 03ad0ec..2d01b50 100644 --- a/mypy_django_plugin/transformers/fields.py +++ b/mypy_django_plugin/transformers/fields.py @@ -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.related import RelatedField @@ -18,7 +18,7 @@ if TYPE_CHECKING: def _get_current_field_from_assignment( 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() if outer_model_info is None or not helpers.is_model_subclass_info(outer_model_info, django_context): return None @@ -42,7 +42,7 @@ def _get_current_field_from_assignment( 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 = [ helpers.convert_any_to_type(related_field_type.args[0], set_type), helpers.convert_any_to_type(related_field_type.args[1], get_type), diff --git a/mypy_django_plugin/transformers/managers.py b/mypy_django_plugin/transformers/managers.py index 7999354..721ca23 100644 --- a/mypy_django_plugin/transformers/managers.py +++ b/mypy_django_plugin/transformers/managers.py @@ -1,6 +1,6 @@ from typing import Optional, Union -from mypy.checker import TypeChecker, fill_typevars +from mypy.checker import TypeChecker from mypy.nodes import ( GDEF, CallExpr, @@ -19,6 +19,7 @@ from mypy.plugin import AttributeContext, DynamicClassDefContext, SemanticAnalyz from mypy.types import AnyType, CallableType, Instance, ProperType from mypy.types import Type as MypyType from mypy.types import TypeOfAny +from mypy.typevars import fill_typevars from typing_extensions import Final from mypy_django_plugin.lib import fullnames, helpers diff --git a/mypy_django_plugin/transformers/models.py b/mypy_django_plugin/transformers/models.py index 9c468d3..484cc87 100644 --- a/mypy_django_plugin/transformers/models.py +++ b/mypy_django_plugin/transformers/models.py @@ -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.fields import DateField, DateTimeField, Field @@ -166,7 +166,7 @@ class ModelClassInitializer: 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") @@ -202,7 +202,7 @@ class AddDefaultPrimaryKey(ModelClassInitializer): def create_autofield( self, - auto_field: Field, + auto_field: "Field[Any, Any]", dest_name: str, existing_field: bool, ) -> None: @@ -394,7 +394,7 @@ class AddManagers(ModelClassInitializer): 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 """ diff --git a/pyproject.toml b/pyproject.toml index 4dc4595..5c6ab26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ line-length = 120 include = '\.pyi?$' [tool.isort] +profile = 'black' line_length = 120 multi_line_output = 3 include_trailing_comma = true diff --git a/pytest.ini b/pytest.ini index fb2028d..53d5184 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,5 +7,5 @@ addopts = -s -v --cache-clear - --mypy-ini-file=./mypy.ini.dev + --mypy-ini-file=mypy.ini --mypy-extension-hook=scripts.tests_extension_hook.django_plugin_hook diff --git a/requirements.txt b/requirements.txt index 8daeab6..37417ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,10 +3,10 @@ requests==2.28.1 gitpython==3.1.27 pre-commit==2.20.0 pytest==7.1.2 -pytest-mypy-plugins==1.9.3 +pytest-mypy-plugins==1.10.0 psycopg2-binary -e ./django_stubs_ext -e .[compatible-mypy] # Overrides: -mypy==0.961 +mypy==0.971 diff --git a/scripts/enabled_test_modules.py b/scripts/enabled_test_modules.py index a063fa2..6c6b7f2 100644 --- a/scripts/enabled_test_modules.py +++ b/scripts/enabled_test_modules.py @@ -1,6 +1,7 @@ # Some errors occur for the test suite itself, and cannot be addressed via django-stubs. They should be ignored # using this constant. import re +from typing import Any, Dict, List IGNORED_MODULES = { "schema", @@ -56,7 +57,7 @@ EXTERNAL_MODULES = [ "argon2", "xml.dom", ] -IGNORED_ERRORS = { +IGNORED_ERRORS: Dict[str, List[Any]] = { "__common__": [ *MOCK_OBJECTS, *EXTERNAL_MODULES, diff --git a/scripts/git_helpers.py b/scripts/git_helpers.py index 48f47e4..14a63ea 100644 --- a/scripts/git_helpers.py +++ b/scripts/git_helpers.py @@ -1,7 +1,8 @@ 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 @@ -10,7 +11,9 @@ class ProgressPrinter(RemoteProgress): def line_dropped(self, line: str) -> None: 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) @@ -22,7 +25,7 @@ def checkout_django_branch(django_version: str, commit_sha: Optional[str]) -> Re repo = Repo.clone_from( "https://github.com/django/django.git", DJANGO_SOURCE_DIRECTORY, - progress=ProgressPrinter(), + progress=ProgressPrinter(), # type: ignore branch=branch, depth=100, ) diff --git a/scripts/tests_extension_hook.py b/scripts/tests_extension_hook.py index 21b1786..2f8d66a 100644 --- a/scripts/tests_extension_hook.py +++ b/scripts/tests_extension_hook.py @@ -5,6 +5,7 @@ from pytest_mypy_plugins.item import YamlTestItem def django_plugin_hook(test_item: YamlTestItem) -> None: custom_settings = test_item.parsed_test_data.get("custom_settings", "") 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: 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: 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" if not test_item.additional_mypy_config: test_item.additional_mypy_config = django_settings_section diff --git a/scripts/typecheck_tests.py b/scripts/typecheck_tests.py index 4636251..7b0313a 100644 --- a/scripts/typecheck_tests.py +++ b/scripts/typecheck_tests.py @@ -5,7 +5,7 @@ import sys from argparse import ArgumentParser from collections import defaultdict 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.git_helpers import checkout_django_branch @@ -18,8 +18,10 @@ DJANGO_COMMIT_REFS = { } 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 = [] for root_key, patterns in IGNORED_ERRORS.items(): for pattern in patterns: @@ -30,7 +32,7 @@ def get_unused_ignores(ignored_message_freq: Dict[str, Dict[Union[str, Pattern], 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 pattern.search(line): return True @@ -40,7 +42,7 @@ def does_pattern_fit(pattern: Union[Pattern, str], line: str): 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: return True @@ -86,14 +88,14 @@ if __name__ == "__main__": mypy_executable = spawn.find_executable("mypy") mypy_argv = [mypy_executable, *mypy_options] completed = subprocess.run( - mypy_argv, + mypy_argv, # type: ignore env={"PYTHONPATH": str(tests_root), "TYPECHECK_TESTS": "1"}, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) output = completed.stdout.decode() - ignored_message_freqs = defaultdict(lambda: defaultdict(int)) + ignored_message_freqs: _DictToSearch = defaultdict(lambda: defaultdict(int)) sorted_lines = sorted(output.splitlines()) for line in sorted_lines: diff --git a/setup.py b/setup.py index 5e7082f..eef4e77 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ with open("README.md") as f: dependencies = [ "mypy>=0.930", "django", - "django-stubs-ext>=0.4.0", + "django-stubs-ext>=0.6.0", "tomli", # Types: "typing-extensions", @@ -31,7 +31,7 @@ dependencies = [ ] extras_require = { - "compatible-mypy": ["mypy>=0.930,<0.970"], + "compatible-mypy": ["mypy>=0.930,<0.980"], } setup( @@ -61,6 +61,7 @@ setup( "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Typing :: Typed", "Framework :: Django", "Framework :: Django :: 2.2", diff --git a/tests/typecheck/test_annotated.yml b/tests/typecheck/test_annotated.yml index 3ff0c00..05171a9 100644 --- a/tests/typecheck/test_annotated.yml +++ b/tests/typecheck/test_annotated.yml @@ -2,11 +2,7 @@ - case: annotated_should_not_iterfere main: | from dataclasses import dataclass - import sys - if sys.version_info < (3, 9): - from typing_extensions import Annotated - else: - from typing import Annotated + from typing_extensions import Annotated class IntegerType: def __init__(self, min_value: int, max_value: int) -> None: diff --git a/tests/typecheck/test_config.yml b/tests/typecheck/test_config.yml index 9156906..f4670e6 100644 --- a/tests/typecheck/test_config.yml +++ b/tests/typecheck/test_config.yml @@ -58,8 +58,6 @@ mypy_config: | [mypy.plugins.django-stubs] django_settings_module = mysettings - env: - - MYPYPATH=./extras files: - path: extras/extra_module.py content: |