Skip to content

Commit

Permalink
feat: Check incorrect PluginConfig class
Browse files Browse the repository at this point in the history
  • Loading branch information
snaselj committed Jan 29, 2024
1 parent e774bef commit 90fee69
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 24 deletions.
76 changes: 57 additions & 19 deletions pylint_nautobot/incorrect_base_class.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,79 @@
"""Check for imports whose paths have changed in 2.0."""
"""Check for incorrect base classes."""
from typing import NamedTuple

from astroid import Assign
from astroid import ClassDef
from astroid import Const
from pylint.checkers import BaseChecker

from pylint_nautobot.utils import is_nautobot_v2_installed
from .utils import is_version_compatible


def is_abstract(node):
def is_abstract(node: ClassDef) -> bool:
"""Given a node, returns whether it is an abstract base model."""
for child_node in node.get_children():
if not (isinstance(child_node, ClassDef) and child_node.name == "Meta"):
continue

for meta_child in child_node.get_children():
if (
not isinstance(meta_child, Assign)
or not meta_child.targets[0].name == "abstract"
or not meta_child.targets[0].name == "abstract" # type: ignore
or not isinstance(meta_child.value, Const)
):
continue
# At this point we know we are dealing with an assignment to a constant for the 'abstract' field on the
# 'Meta' class. Therefore, we can assume the value of that to be whether the node is an abstract base model
# or not.
return meta_child.value.value

return False


_CLASS_MAPPING = (
{
"incorrect": "django_filters.filters.FilterSet",
"correct": "django_filters.filters.BaseFilterSet",
},
{
"incorrect": "django.db.models.base.Model",
"correct": "nautobot.core.models.BaseModel",
},
{
"versions": "<2.0",
"incorrect": "django.forms.forms.Form",
"correct": "nautobot.utilities.forms.forms.BootstrapMixin",
},
{
"versions": ">=2.0",
"incorrect": "django.forms.forms.Form",
"correct": "nautobot.core.forms.forms.BootstrapMixin",
},
{
"versions": ">=2.0",
"incorrect": "django.forms.ModelForm",
"correct": "nautobot.apps.forms.NautobotModelForm",
},
{
"versions": ">=2.0",
"incorrect": "nautobot.extras.plugins.PluginConfig",
"correct": "nautobot.apps.NautobotAppConfig",
},
)


class _Mapping(NamedTuple):
incorrect: str
correct: str


_COMPATIBLE_MAPPING = (
_Mapping(item["incorrect"], item["correct"])
for item in _CLASS_MAPPING
if is_version_compatible(item.get("versions", ""))
)


class NautobotIncorrectBaseClassChecker(BaseChecker):
"""Check that all X inherits from Y.
Expand All @@ -36,17 +84,6 @@ class NautobotIncorrectBaseClassChecker(BaseChecker):

# Maps a non-Nautobot-specific base class to a Nautobot-specific base class which has to be in the class hierarchy
# for every class that has the base class in its hierarchy.
external_to_nautobot_class_mapping = [
("django_filters.filters.FilterSet", "django_filters.filters.BaseFilterSet"),
("django.db.models.base.Model", "nautobot.core.models.BaseModel"),
(
"django.forms.forms.Form",
"nautobot.core.forms.forms.BootstrapMixin"
if is_nautobot_v2_installed()
else "nautobot.utilities.forms.forms.BootstrapMixin",
),
]

name = "nautobot-incorrect-base-class"
msgs = {
"E4242": (
Expand All @@ -56,7 +93,7 @@ class NautobotIncorrectBaseClassChecker(BaseChecker):
)
}

def visit_classdef(self, node):
def visit_classdef(self, node: ClassDef):
"""Visit class definitions."""
if is_abstract(node):
return
Expand All @@ -66,6 +103,7 @@ def visit_classdef(self, node):
return

ancestor_class_types = [ancestor.qname() for ancestor in node.ancestors()]
for base_class, nautobot_base_class in self.external_to_nautobot_class_mapping:
if base_class in ancestor_class_types and nautobot_base_class not in ancestor_class_types:
self.add_message(msgid="nb-incorrect-base-class", node=node, args=(base_class, nautobot_base_class))
for mapping in _COMPATIBLE_MAPPING:
if mapping.incorrect in ancestor_class_types and mapping.correct not in ancestor_class_types:
self.add_message(msgid="nb-incorrect-base-class", node=node, args=(mapping.incorrect, mapping.correct))
return
5 changes: 0 additions & 5 deletions pylint_nautobot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@
from yaml import safe_load


def is_nautobot_v2_installed() -> bool:
"""Return True if Nautobot v2.x is installed."""
return MINIMUM_NAUTOBOT_VERSION.major == 2


def is_version_compatible(specifier_set: Union[str, SpecifierSet]) -> bool:
"""Return True if the Nautobot version is compatible with the given version specifier_set."""
if isinstance(specifier_set, str):
Expand Down

0 comments on commit 90fee69

Please sign in to comment.