指定模型中Django字段的类型(用于Pylint)

时间:2019-01-24 17:29:54

标签: django python-3.x django-models pylint python-typing

我已经基于CharField创建了自定义的Django模型域子类,但是它使用to_python来确保返回的模型对象具有更复杂的对象(有些是列表,有些是具有特定格式的字典,等等)–我是m使用MySQL,因此某些PostGreSql字段类型不可用。

一切工作正常,但是Pylint相信这些字段中的所有值都是字符串,因此在使用这些模型的代码上我收到很多“不支持成员资格测试”和“无法下标对象”警告。我可以单独禁用它们,但我想让Pylint知道这些模型返回某些对象类型。输入提示无济于事,例如:

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

我知道某些内置的Django字段会为Pylint返回正确的类型(CharField,IntegerField),某些其他扩展已经找到了指定其类型的方法,因此Pylint很高兴(MultiSelectField),但深入研究其代码,我可以t找出指定返回类型的“魔术”在哪里。

(注意:此问题与Django表单字段的INPUT类型无关)

谢谢!

2 个答案:

答案 0 :(得分:4)

出于好奇,我对此进行了查看,并且我认为大多数“魔术”实际上是为pytest-django而来的。

在Django源代码中,例如对于CharField,没有什么可以真正赋予类型提示这个字符串的概念。而且由于该类仅继承自Field,而_STR_FIELDS = ('CharField', 'SlugField', 'URLField', 'TextField', 'EmailField', 'CommaSeparatedIntegerField', 'FilePathField', 'GenericIPAddressField', 'IPAddressField', 'RegexField', 'SlugField') 也是其他非字符串字段的父级,因此知识需要在其他位置进行编码。

但是,另一方面,我在研究pylint-django的源代码时,发现了最有可能发生这种情况的地方:

pylint_django.transforms.fields中,以类似的方式对几个字段进行硬编码:

inference_tip

下面进一步是一个可疑命名的函数apply_type_shim,它根据字段的类型(“ str”,“ int”,“ dict”,“ list”等)向类添加信息。

此附加信息将传递给according to the astroid docstransforms用于添加推理信息(重点是我的):

  

astroid不仅可以用作AST库,还可以提供   推理的基本支持,它可以推断名称在   给定上下文,它可用于解决高度复杂的属性   类层次结构等。我们称这种机制为一般推断   在整个项目中。

astroid是Pylint用来表示Python代码的基础库,所以我很确定这就是信息传递给Pylint的方式。如果您遵循导入插件时发生的情况,则会在pylint_django/.plugin中发现这一有趣的地方,在该地方实际导入了@Entity @Indexed @NormalizerDef(name = "lowercase", filters = { @TokenFilterDef(factory = ASCIIFoldingFilterFactory.class), @TokenFilterDef(factory = LowerCaseFilterFactory.class) } ) public class Deal { //other fields omitted for brevity purposes @Field(store = Store.YES) @Field(name = "name_Sort", store = Store.YES, normalizer= @Normalizer(definition="lowercase")) @SortableField(forField = "name_Sort") @Column(name = "NAME") private String name = "New Deal"; //Getters/Setters omitted here } ,从而将推理技巧有效地添加到AST节点。

我认为,如果您想在自己的课程上实现相同的目标,则可以:

  1. 直接从另一个Django模型类派生,该类已经具有您要查找的关联类型。
  2. 创建并注册一个等效的pylint插件,该插件还将使用Astroid向类中添加信息,以便Pylint知道如何使用它。

答案 1 :(得分:4)

我最初以为您使用的是插件pylint-django,但是也许您明确使用了prospector,如果找到Django,它会自动安装pylint-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)

如果在ModelFormView中使用基类列表或dict,则分别将基类列表或字典以及上面介绍的魔术方法添加到您的自定义字段类中。


注意:

我还考虑了一个插件存根解决方案,该解决方案的功能相同,但是对于SO来说,“ prospector”的替代方案似乎太复杂了,我宁愿在安装后简单地修补源代码。

类Model或FormView是由元类创建的唯一类,在Django中使用。通过插件代码模拟元类并控制分析简单属性是一个好主意。如果我还记得,MyPy(在此处的一些注释中引用)也有一个适用于Django(但仅适用于FormView)的mypy-django插件,因为为django.db编写注释比使用属性要复杂得多。 -我已经尝试了一个星期。