Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔧 MAINTAIN: Add pylint_aiida plugin #5182

Merged
merged 5 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions aiida/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import os
import re
import sys
from typing import Any, Dict
from uuid import UUID

from .lang import classproperty
Expand Down Expand Up @@ -388,7 +389,7 @@ def _prettify_label_latex_simple(cls, label):
return re.sub(r'(\d+)', r'$_{\1}$', label)

@classproperty
def prettifiers(cls): # pylint: disable=no-self-argument
def prettifiers(cls) -> Dict[str, Any]: # pylint: disable=no-self-argument
"""
Property that returns a dictionary that for each string associates
the function to prettify a label
Expand All @@ -412,7 +413,7 @@ def get_prettifiers(cls):

:return: a list of strings
"""
return sorted(cls.prettifiers.keys()) # pylint: disable=no-member
return sorted(cls.prettifiers.keys())

def __init__(self, format): # pylint: disable=redefined-builtin
"""
Expand Down
4 changes: 2 additions & 2 deletions aiida/transports/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,10 @@ def get_valid_auth_params(cls):
if cls._valid_auth_options is None:
raise NotImplementedError
else:
return cls.auth_options.keys() # pylint: disable=no-member
return cls.auth_options.keys()

@classproperty
def auth_options(cls): # pylint: disable=no-self-argument
def auth_options(cls) -> OrderedDict: # pylint: disable=no-self-argument
"""Return the authentication options to be used for building the CLI.

:return: `OrderedDict` of tuples, with first element option name and second dictionary of kwargs
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ requires = ["setuptools>=40.8.0", "wheel", "fastentrypoints~=0.12"]
build-backend = "setuptools.build_meta"

[tool.pylint.master]
load-plugins = "pylint_django"
load-plugins = ["pylint_django", "utils.pylint_aiida"]
# this currently fails with aiida.common.exceptions.ProfileConfigurationError: no profile has been loaded
# we woud need a static settings module to use this
# django-settings-module = "aiida.backends.djsite.settings"
Expand Down
57 changes: 57 additions & 0 deletions utils/pylint_aiida.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
"""pylint plugin for ``aiida-core`` specific issues."""
import astroid


def register(linter): # pylint: disable=unused-argument
"""Register linters (unused)"""


def remove_classprop_imports(import_from: astroid.ImportFrom):
"""Remove any ``classproperty`` imports (handled in ``replace_classprops``)"""
import_from.names = [name for name in import_from.names if name[0] != 'classproperty']


def replace_classprops(func: astroid.FunctionDef):
"""Replace ``classproperty`` decorated methods.

As discussed in https://github.com/PyCQA/pylint/issues/1694, pylint does not understand the ``@classproperty``
decorator, and so mistakes the method as a function, rather than an attribute of the class.
If the method is annotated, this leads to pylint issuing ``no-member`` errors.

This transform replaces ``classproperty`` decorated methods with an annotated attribute::

from aiida.common.lang import classproperty

class MyClass:
@classproperty
def my_property(cls) -> AnnotatedType:
return cls.my_value

MyClass.my_property.attribute # <-- pylint issues: Method 'my_property' has no 'attribute' member (no-member)

class MyClass:
my_property: AnnotatedType

"""
# ignore methods without annotations or decorators
if not (func.returns and func.decorators and func.decorators.nodes):
return
# ignore methods that are specified as abstract
if any(isinstance(node, astroid.Name) and 'abstract' in node.name for node in func.decorators.nodes):
return
if any(isinstance(node, astroid.Attribute) and 'abstract' in node.attrname for node in func.decorators.nodes):
return
# convert methods with @classproperty decorator
if isinstance(func.decorators.nodes[0], astroid.Name) and func.decorators.nodes[0].name == 'classproperty':
assign = astroid.AnnAssign(lineno=func.lineno, col_offset=func.col_offset, parent=func.parent)
assign.simple = 1
assign.target = astroid.AssignName(func.name, lineno=assign.lineno, col_offset=assign.col_offset, parent=assign)
assign.annotation = func.returns
assign.annotation.parent = assign
func.parent.locals[func.name] = [assign.target]
return assign


astroid.MANAGER.register_transform(astroid.ImportFrom, remove_classprop_imports)
astroid.MANAGER.register_transform(astroid.FunctionDef, replace_classprops)