Mypy жалуется на то, что имя "Optional" не определяется без использования Optional
Я недавно начал использовать mypy и столкнулся с некоторыми странными проблемами, которые я не могу понять.
Я использую mypy 0.950, django-stubs 1.11.0, django 4.0.5 и python 3.10.2.
Запуск mypy через командную строку дает следующее:
project/suppliers/models.py:6: error: Name "Optional" is not defined
project/suppliers/models.py:6: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Optional")
project/users/models.py:6: error: Name "Optional" is not defined
project/users/models.py:6: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Optional")
project/products/models.py:6: error: Name "Optional" is not defined
project/products/models.py:6: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Optional")(Suggestion: "from typing import Optional")
Однако, строка 6 в project/suppliers/models.py
полностью пуста:
from django.contrib.sites.managers import CurrentSiteManager
from django.contrib.sites.models import Site
from django.db import models
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from django_countries.fields import CountryField
from project.core.models import BaseImageModel, BaseModel
from project.suppliers.managers import SupplierQuerySet
_SupplierManager = models.Manager.from_queryset(SupplierQuerySet)
class Supplier(BaseModel, BaseImageModel):
...
Строка 6 в project/users/models.py
является импортом django from django.contrib.contenttypes.models import ContentType
:
import random
from typing import Any
from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.managers import CurrentSiteManager
from django.contrib.sites.models import Site
from django.core import signing
from django.core.mail import send_mail
from django.db import models
from django.forms import ValidationError
from django.http import HttpRequest
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.encoding import force_bytes, force_str
from django.utils.http import (
urlsafe_base64_decode as uid_decoder,
urlsafe_base64_encode as uid_encoder,
)
from django.utils.translation import gettext_lazy as _
import phonenumbers
from project.users.enums import AvatarColors
from project.users.managers import UserQuerySet
from project.users.schemas.records import (
UserAuditLogsRecord,
UserNotesRecord,
UserProfileRecord,
)
_UserManager = models.Manager.from_queryset(UserQuerySet)
class User(AbstractBaseUser, PermissionsMixin):
А строка 6 в project/products/models.py - это еще один django import from django.utils.text import slugify
:
from decimal import Decimal
from django.contrib.sites.managers import CurrentSiteManager
from django.contrib.sites.models import Site
from django.db import models
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
from mptt.models import TreeManyToManyField
...
Моя конфигурация mypy выглядит следующим образом:
[tool.mypy]
plugins = ["mypy_django_plugin.main", "pydantic.mypy"]
follow_imports = "normal"
ignore_missing_imports = true
disallow_untyped_calls = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_return_any = true
warn_unreachable = true
no_implicit_optional = true
no_implicit_reexport = true
check_untyped_defs = true
strict_equality = true
[tool.django-stubs]
django_settings_module = "project.settings"
[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
warn_untyped_fields = true
# Admin files uses some patterns that are not easily typed
[[tool.mypy.overrides]]
module = "project.*.admin"
ignore_errors = true
[[tool.mypy.overrides]]
module = "project.*.tests.*"
ignore_errors = true
[[tool.mypy.overrides]]
module = "project.*.migrations.*"
ignore_errors = "true"
[[tool.mypy.overrides]]
module = "project.*.management.*"
disallow_untyped_defs = false
Я пробовал гуглить, но не могу найти никого, кто бы с этим столкнулся. Есть ли что-то очевидное, что я упустил, или это похоже на ошибку? По крайней мере, мне кажется, что это повлияло бы на многих, если бы это было что-то не так с mypy/django stubs.
Быстрое исправление от 1-го лица
Вы можете добавить from typing import Optional
в файлы, которые используют CurrentSiteManager
. Это решит эту проблему (да, # noqa: F401
- ваш друг).
Одноразовое исправление для пакета
В качестве быстрого обходного пути вы можете изменить django-stubs/contrib/sites/managers.pyi
на следующее содержание:
from typing import Optional, TypeVar
from django.db import models
_T = TypeVar('_T', bound=models.Model)
class CurrentSiteManager(models.Manager[_T]):
def __init__(self, field_name: Optional[str] = ...) -> None: ...
Корень проблемы
Это не настоящее решение и является ошибкой в mypy_django_plugin
. Сейчас я ищу решение. Проблема в том, что helpers.copy_method_to_another_class
(и, следовательно, transformers.models.AddManagers.create_new_model_parametrized_manager
) использует контекст определения класса модели. Я не понимаю, как правильно получить и передать другой контекст без симметричной проблемы, и слияние определенно не вариант. Я обновлю это в случае успеха и подниму PR для сопровождающего. Было бы здорово, если бы вы отправили проблему по адресу django-stubs
(и прикрепили ссылку на этот вопрос), чтобы мне меньше пришлось объяснять (или кто-то другой мог бы помочь, если у меня не получится).
Лучшее исправление #1
здесь мы можем добавить original_module_name=base_manager_info.module_name
аргумент вызова. Это позволит разрешить все, что унаследовано от первого родителя MRO. Однако, кажется, что это не сработает для более длинных цепочек наследования. Виноват, на самом деле мы итерируем здесь только над методами последнего предка, так что это кажется окончательным решением.
(Извините, что использую это в качестве журнала изменений, но раз уж начали...)