Укажите тип для поля Django в модели (для Pylint) - PullRequest
0 голосов
/ 24 января 2019

Я создал пользовательские подклассы поля модели Django на основе CharField, но использует to_python, чтобы гарантировать, что возвращаемые объекты модели имеют более сложные объекты (некоторые являются списками, некоторые являются диктовками с определенным форматом и т. Д.) - I ' Я использую MySQL, поэтому некоторые типы полей PostGreSql недоступны.

Все работает отлично, но Pylint считает, что все значения в этих полях будут строками, и поэтому я получаю много предупреждений "unsupported-members-test" и "unsubscriptable-object" в коде, который использует эти модели. Я могу отключить их по отдельности, но я бы предпочел, чтобы Pylint знал, что эти модели возвращают определенные типы объектов. Тип подсказки не помогают, например ::1003

class MealPrefs(models.Model):
    user = ...foreign key...
    prefs: dict = custom_fields.DictOfListsExtendsCharField(
            default={'breakfast': ['cereal', 'toast'], 'lunch': []},
            )

Я знаю, что некоторые встроенные поля Django возвращают правильные типы для Pylint (CharField, IntegerField), а некоторые другие расширения нашли способы указать их тип, чтобы Pylint был доволен (MultiSelectField), но копаясь в их коде, я могу ' t выяснить, где будет «магия», указывающая возвращаемый тип.

(примечание: этот вопрос не относится к INPUT: тип полей формы Django)

Спасибо!

Ответы [ 2 ]

0 голосов
/ 27 марта 2019

Сначала я думал, что вы используете плагин pylint-django , но, возможно, вы явно используете prospector , который автоматически устанавливает pylint-django, если он находит Django.

Средство проверки pylint, ни его плагин не проверяет код, используя информацию из аннотаций типа Python ( PEP 484 ). Он может анализировать код с аннотациями, не понимая их и, например, не предупреждать о «неиспользованном импорте», если имя используется только в аннотациях. Сообщение unsupported-membership-test сообщается в строке с выражением something in object_A просто, если у класса A() нет метода __contains__. Аналогично сообщение unsubscriptable-object относится к методу __getitem__.


Вы можете установить патч pylint-django для пользовательских полей следующим образом:
Добавить функцию:

def my_apply_type_shim(cls, _context=None):  # noqa
    if cls.name == 'MyListField':
        base_nodes = scoped_nodes.builtin_lookup('list')
    elif cls.name == 'MyDictField':
        base_nodes = scoped_nodes.builtin_lookup('dict')
    else:
        return apply_type_shim(cls, _context)
    base_nodes = [n for n in base_nodes[1] if not isinstance(n, nodes.ImportFrom)]
    return iter([cls] + base_nodes)

в pylint_django/transforms/fields.py

и также замените apply_type_shim на my_apply_type_shim в том же файле в этой строке:

def add_transforms(manager):
    manager.register_transform(nodes.ClassDef, inference_tip(my_apply_type_shim), is_model_or_form_field)

Это добавляет список базовых классов или dict соответственно, с их магическими методами, описанными выше, к вашим классам пользовательских полей, если они используются в Model или FormView.


Примечания:

Я также подумал о решении для заглушки плагинов, которое делает то же самое, но альтернатива «prospector» кажется настолько сложной для SO, что я предпочитаю просто исправлять источник после установки.

Классы Model или FormView являются единственными классами, созданными метаклассами, используемыми в Django. Отличная идея - эмулировать метакласс с помощью кода плагина и управлять простыми атрибутами анализа. Если я помню, MyPy , на который есть ссылка в некоторых комментариях, также имеет плагин mypy-django для Django, но только для FormView, потому что написание аннотаций для django.db сложнее, чем работа с атрибутами. - Я пытался работать над этим в течение одной недели.

0 голосов
/ 27 марта 2019

Я посмотрел на это из любопытства, и я думаю, что большая часть "магии" на самом деле приходит за pytest-django .

В исходном коде Django, например для CharField нет ничего, что могло бы дать типу подсказку, что это строка. А поскольку класс наследует только от Field, который также является родителем других нестроковых полей, знания должны быть закодированы в другом месте.

С другой стороны, копаясь в исходном коде pylint-django, я обнаружил, где это, скорее всего, происходит:

в pylint_django.transforms.fields, несколько полей жестко закодированы аналогичным образом:

_STR_FIELDS = ('CharField', 'SlugField', 'URLField', 'TextField', 'EmailField',
               'CommaSeparatedIntegerField', 'FilePathField', 'GenericIPAddressField',
               'IPAddressField', 'RegexField', 'SlugField')

Далее, функция с подозрительным именем apply_type_shim добавляет информацию в класс в зависимости от типа поля, в котором она находится (str, int, dict, list). и т. д.)

Эта дополнительная информация передается в inference_tip, который согласно астрологическим документам , используется для добавления информации о логическом выводе (выделено мной):

Astroid можно использовать как библиотеку AST, он также предлагает некоторые Основная поддержка логического вывода, это может сделать вывод, что имена могут означать в учитывая контекст, это может быть использовано для решения атрибутов в очень сложных иерархия классов и т. д. Мы называем этот механизм в целом выводом на протяжении всего проекта.

astroid - базовая библиотека, используемая Pylint для представления кода Python, поэтому я уверен, что именно так информация передается в Pylint. Если вы следите за тем, что происходит при импорте плагина, вы найдете этот интересный бит в pylint_django / .plugin , где он фактически импортирует transforms, эффективно добавляя подсказку вывода в узел AST.

Я думаю, что если вы хотите добиться того же с вашими собственными классами, вы можете либо:

  1. Непосредственно производный от другого класса модели Django, который уже имеет связанный тип, который вы ищете.
  2. Создайте и зарегистрируйте эквивалентный плагин Pylint, который также будет использовать Astroid для добавления информации в класс, чтобы Pylint знал, что с ним делать.
...