diff --git a/pylint_django/augmentations/__init__.py b/pylint_django/augmentations/__init__.py index d84ea858..b63c0b72 100644 --- a/pylint_django/augmentations/__init__.py +++ b/pylint_django/augmentations/__init__.py @@ -412,33 +412,27 @@ def is_model_meta_subclass(node): 'rest_framework.generics.GenericAPIView', 'rest_framework.viewsets.ReadOnlyModelViewSet', 'rest_framework.viewsets.ModelViewSet', - 'django_filters.filterset.FilterSet',) - return node_is_subclass(node.parent, *parents) - - -def is_model_factory_meta_subclass(node): - """Checks that node is derivative of DjangoModelFactory class.""" - if node.name != 'Meta' or not isinstance(node.parent, ClassDef): - return False - - parents = ('factory.django.DjangoModelFactory', - '.DjangoModelFactory',) + 'django_filters.filterset.FilterSet', + 'factory.django.DjangoModelFactory',) return node_is_subclass(node.parent, *parents) def is_model_factory(node): - """Checks that node is derivative of SubFactory class.""" + """Checks that node is derivative of DjangoModelFactory or SubFactory class.""" try: parent_classes = node.expr.inferred() except: # noqa: E722, pylint: disable=bare-except return False - parents = ('factory.declarations.SubFactory',) + parents = ('factory.declarations.SubFactory', 'factory.django.DjangoModelFactory') for parent_class in parent_classes: try: if parent_class.qname() in parents: return True + + if node_is_subclass(parent_class, *parents): + return True except AttributeError: continue @@ -714,17 +708,8 @@ def wrap_func(*args, **kwargs): def is_wsgi_application(node): frame = node.frame() - - if node.name == 'application' and isinstance(frame, Module): - path_ends_with_wsgi = False - for path in frame.path: - if path.endswith('wsgi.py'): - path_ends_with_wsgi = True - break - - return frame.name == 'wsgi' or path_ends_with_wsgi or frame.file.endswith('wsgi.py') - - return False + return node.name == 'application' and isinstance(frame, Module) and \ + (frame.name == 'wsgi' or frame.path[0].endswith('wsgi.py') or frame.file.endswith('wsgi.py')) def apply_augmentations(linter): @@ -809,10 +794,6 @@ def apply_augmentations(linter): suppress_message(linter, MisdesignChecker.leave_classdef, 'too-few-public-methods', is_model_mpttmeta_subclass) # factory_boy's DjangoModelFactory - suppress_message(linter, DocStringChecker.visit_classdef, 'missing-docstring', is_model_factory_meta_subclass) - suppress_message(linter, NewStyleConflictChecker.visit_classdef, 'old-style-class', is_model_factory_meta_subclass) - suppress_message(linter, ClassChecker.visit_classdef, 'W0232', is_model_factory_meta_subclass) - suppress_message(linter, MisdesignChecker.leave_classdef, 'too-few-public-methods', is_model_factory_meta_subclass) suppress_message(linter, TypeChecker.visit_attribute, 'no-member', is_model_factory) # ForeignKey and OneToOneField diff --git a/pylint_django/checkers/db_performance.py b/pylint_django/checkers/db_performance.py index cf052ea9..13f19efd 100644 --- a/pylint_django/checkers/db_performance.py +++ b/pylint_django/checkers/db_performance.py @@ -40,7 +40,7 @@ def _is_migrations_module(node): if not isinstance(node, astroid.Module): return False - return 'migrations' in node.path and not node.path.endswith('__init__.py') + return 'migrations' in node.path[0] and not node.path[0].endswith('__init__.py') class NewDbFieldWithDefaultChecker(checkers.BaseChecker): @@ -102,7 +102,7 @@ def _path(node): last_name_space = '' latest_migrations = [] for module in self._migration_modules: - name_space = module.path.split('migrations')[0] + name_space = module.path[0].split('migrations')[0] if name_space != last_name_space: last_name_space = name_space latest_migrations.append(module) diff --git a/pylint_django/transforms/__init__.py b/pylint_django/transforms/__init__.py index 6c7f1479..cf602b0f 100644 --- a/pylint_django/transforms/__init__.py +++ b/pylint_django/transforms/__init__.py @@ -2,53 +2,34 @@ import os import re -from astroid import MANAGER -from astroid.builder import AstroidBuilder -from astroid import nodes +import astroid from pylint_django.transforms import foreignkey, fields -foreignkey.add_transform(MANAGER) -fields.add_transforms(MANAGER) +foreignkey.add_transform(astroid.MANAGER) +fields.add_transforms(astroid.MANAGER) -def _add_transform(package_name, *class_names): - """Transform package's classes.""" - transforms_dir = os.path.join(os.path.dirname(__file__), 'transforms') - fake_module_path = os.path.join(transforms_dir, '%s.py' % re.sub(r'\.', '_', package_name)) +def _add_transform(package_name): + def fake_module_builder(): + """ + Build a fake module to use within transformations. + @package_name is a parameter from the outher scope b/c according to + the docs this can't receive any parameters. + http://pylint.pycqa.org/projects/astroid/en/latest/extending.html?highlight=MANAGER#module-extender-transforms + """ + transforms_dir = os.path.join(os.path.dirname(__file__), 'transforms') + fake_module_path = os.path.join(transforms_dir, '%s.py' % re.sub(r'\.', '_', package_name)) - with open(fake_module_path) as modulefile: - fake_module = modulefile.read() + with open(fake_module_path) as modulefile: + fake_module = modulefile.read() - fake = AstroidBuilder(MANAGER).string_build(fake_module) + return astroid.builder.AstroidBuilder(astroid.MANAGER).string_build(fake_module) - def set_fake_locals(module): - """Set fake locals for package.""" - if module.name != package_name: - return - for class_name in class_names: - # This changed from locals to _locals between astroid 1.3 and 1.4 - if hasattr(module, '_locals'): - module._locals[class_name].extend(fake._locals[class_name]) # pylint: disable=protected-access - else: - module.locals[class_name].extend(fake.locals[class_name]) + astroid.register_module_extender(astroid.MANAGER, package_name, fake_module_builder) - MANAGER.register_transform(nodes.Module, set_fake_locals) - -_add_transform('django.core.handlers.wsgi', 'WSGIRequest') -_add_transform('django.views.generic.base', 'View') -_add_transform('django.forms', 'Form') -_add_transform('django.forms', 'ModelForm') -_add_transform('django.db.models', - 'Model', - 'Manager') -_add_transform('django.utils.translation', 'ugettext_lazy') -_add_transform('mongoengine', 'Document') -_add_transform('model_utils.managers', - 'InheritanceManager', - 'QueryManager', - 'SoftDeletableManager') +_add_transform('django.utils.translation') # register transform for FileField/ImageField, see #60 -_add_transform('django.db.models.fields.files', 'FileField') +_add_transform('django.db.models.fields.files') diff --git a/pylint_django/transforms/foreignkey.py b/pylint_django/transforms/foreignkey.py index 9645d179..544bb6d7 100644 --- a/pylint_django/transforms/foreignkey.py +++ b/pylint_django/transforms/foreignkey.py @@ -37,12 +37,31 @@ def infer_key_classes(node, context=None): break elif isinstance(arg, nodes.Const): try: - model_name = arg.value.split('.')[-1] # can be 'Model' or 'app.Model' + # can be 'Model' or 'app.Model' + module_name, _, model_name = arg.value.rpartition('.') except AttributeError: break + # when ForeignKey is specified only by class name we assume that + # this class must be found in the current module + if not module_name: + current_module = node.frame() + while not isinstance(current_module, nodes.Module): + current_module = current_module.parent.frame() + + module_name = current_module.name + elif not module_name.endswith('models'): + # otherwise Django allows specifying an app name first, e.g. + # ForeignKey('auth.User') so we try to convert that to + # 'auth.models', 'User' which works nicely with the `endswith()` + # comparison below + module_name += '.models' + for module in MANAGER.astroid_cache.values(): - if model_name in module.locals: + # only load model classes from modules which match the module in + # which *we think* they are defined. This will prevent infering + # other models of the same name which are found elsewhere! + if model_name in module.locals and module.name.endswith(module_name): class_defs = [ module_node for module_node in module.lookup(model_name)[1] if isinstance(module_node, nodes.ClassDef) diff --git a/pylint_django/transforms/transforms/django_contrib_postgres_fields.py b/pylint_django/transforms/transforms/django_contrib_postgres_fields.py deleted file mode 100644 index 64c9a229..00000000 --- a/pylint_django/transforms/transforms/django_contrib_postgres_fields.py +++ /dev/null @@ -1,47 +0,0 @@ -from django.contrib.postgres import fields as django_fields -from psycopg2 import extras - - -# -------- -# lists - -class ArrayField(list, django_fields.ArrayField): - pass - - -# -------- -# dicts - -class HStoreField(dict, django_fields.HStoreField): - pass - - -class JSONField(dict, django_fields.JSONField): - pass - - -# -------- -# ranges - -class RangeField(extras.Range, django_fields.RangeField): - pass - - -class IntegerRangeField(extras.NumericRange, django_fields.IntegerRangeField): - pass - - -class BigIntegerRangeField(extras.NumericRange, django_fields.BigIntegerRangeField): - pass - - -class FloatRangeField(extras.NumericRange, django_fields.FloatRangeField): - pass - - -class DateTimeRangeField(extras.DateTimeTZRange, django_fields.DateRangeField): - pass - - -class DateRangeField(extras.DateRange, django_fields.DateRangeField): - pass diff --git a/pylint_django/transforms/transforms/django_core_handlers_wsgi.py b/pylint_django/transforms/transforms/django_core_handlers_wsgi.py deleted file mode 100644 index b9e6df53..00000000 --- a/pylint_django/transforms/transforms/django_core_handlers_wsgi.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.core.handlers.wsgi import WSGIRequest as WSGIRequestOriginal - - -class WSGIRequest(WSGIRequestOriginal): - status_code = None - content = '' - json = None diff --git a/pylint_django/transforms/transforms/django_db_models.py b/pylint_django/transforms/transforms/django_db_models.py deleted file mode 100644 index bf99630e..00000000 --- a/pylint_django/transforms/transforms/django_db_models.py +++ /dev/null @@ -1,62 +0,0 @@ -from django.core.exceptions import MultipleObjectsReturned \ - as MultipleObjectsReturnedException - - -def __noop(self, *args, **kwargs): - """Just a dumb no-op function to make code a bit more DRY""" - return None - - -def __noop_list(self, *args, **kwargs): - """Just a dumb no-op function to make code a bit more DRY""" - return [] - - -class Model(object): - _meta = None - objects = None - - id = None - pk = None - - MultipleObjectsReturned = MultipleObjectsReturnedException - - save = __noop - delete = __noop - - -class Manager(object): - """ - Eliminate E1002 for Manager object - """ - get_queryset = __noop - none = __noop - all = __noop_list - count = __noop - dates = __noop - distinct = __noop - extra = __noop - get = __noop - get_or_create = __noop - create = __noop - bulk_create = __noop - filter = __noop_list - aggregate = __noop - annotate = __noop - complex_filter = __noop - exclude = __noop - in_bulk = __noop - iterator = __noop - latest = __noop - order_by = __noop - select_for_update = __noop - select_related = __noop - prefetch_related = __noop - values = __noop - values_list = __noop - update = __noop - reverse = __noop - defer = __noop - only = __noop - using = __noop - exists = __noop diff --git a/pylint_django/transforms/transforms/django_db_models_fields.py b/pylint_django/transforms/transforms/django_db_models_fields.py deleted file mode 100644 index f8cf12e4..00000000 --- a/pylint_django/transforms/transforms/django_db_models_fields.py +++ /dev/null @@ -1,132 +0,0 @@ -from django.db.models import fields as django_fields -import datetime -from decimal import Decimal -from uuid import UUID - - -# -------- -# booleans -from utils import PY3 - - -class BooleanField(bool, django_fields.BooleanField): - pass - - -class NullBooleanField(bool, django_fields.NullBooleanField): - pass - - -# ------ -# strings - -class CharField(str, django_fields.CharField): - pass - - -class SlugField(CharField, django_fields.SlugField): - pass - - -class URLField(CharField, django_fields.URLField): - pass - - -class TextField(str, django_fields.TextField): - pass - - -class EmailField(CharField, django_fields.EmailField): - pass - - -class CommaSeparatedIntegerField(CharField, django_fields.CommaSeparatedIntegerField): - pass - - -class FilePathField(CharField, django_fields.FilePathField): - pass - - -# ------- -# numbers - -class IntegerField(int, django_fields.IntegerField): - pass - - -class BigIntegerField(IntegerField, django_fields.BigIntegerField): - pass - - -class SmallIntegerField(IntegerField, django_fields.SmallIntegerField): - pass - - -class PositiveIntegerField(IntegerField, django_fields.PositiveIntegerField): - pass - - -class PositiveSmallIntegerField(IntegerField, django_fields.PositiveSmallIntegerField): - pass - - -class FloatField(float, django_fields.FloatField): - pass - - -class DecimalField(Decimal, django_fields.DecimalField): - # DecimalField is immutable and does not use __init__, but the Django DecimalField does. To - # cheat pylint a little bit, we copy the definition of the DecimalField constructor parameters - # into the __new__ method of Decimal so that Pylint believes we are constructing a Decimal with - # the signature of DecimalField - def __new__(cls, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): - pass - - -# -------- -# date/time - -# In Python3, the date and datetime objects are immutable, so we need to do -# the same __new__ / __init__ fiddle as for Decimal - -class DateField(datetime.date, django_fields.DateField): - if PY3: - def __new__(cls, verbose_name=None, name=None, auto_now=False, - auto_now_add=False, **kwargs): - pass - - -class DateTimeField(datetime.datetime, django_fields.DateTimeField): - if PY3: - def __new__(cls, verbose_name=None, name=None, auto_now=False, - auto_now_add=False, **kwargs): - pass - - -class TimeField(datetime.time, django_fields.TimeField): - if PY3: - def __new__(cls, verbose_name=None, name=None, auto_now=False, - auto_now_add=False, **kwargs): - pass - - -class DurationField(datetime.timedelta, django_fields.DurationField): - if PY3: - def __new__(cls, verbose_name=None, name=None, **kwargs): - pass - - -# ------- -# misc - -class GenericIPAddressField(str, django_fields.GenericIPAddressField): - pass - - -class IPAddressField(str, django_fields.IPAddressField): - pass - - -class UUIDField(UUID, django_fields.UUIDField): - pass diff --git a/pylint_django/transforms/transforms/django_forms.py b/pylint_django/transforms/transforms/django_forms.py deleted file mode 100644 index 4f9029d6..00000000 --- a/pylint_django/transforms/transforms/django_forms.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.forms import BaseForm, BaseModelForm - - -class Form(BaseForm): - cleaned_data = None - fields = None - instance = None - data = None - _errors = None - base_fields = None - - -class ModelForm(BaseModelForm): - def save_m2m(self): - return None - cleaned_data = None - fields = None - instance = None - data = None - _errors = None - base_fields = None diff --git a/pylint_django/transforms/transforms/django_forms_fields.py b/pylint_django/transforms/transforms/django_forms_fields.py deleted file mode 100644 index 4ace966d..00000000 --- a/pylint_django/transforms/transforms/django_forms_fields.py +++ /dev/null @@ -1,126 +0,0 @@ -import datetime -from decimal import Decimal -from django.forms import fields as django_fields - - -# -------- -# booleans -from utils import PY3 - - -class BooleanField(bool, django_fields.BooleanField): - pass - - -class NullBooleanField(bool, django_fields.NullBooleanField): - pass - - -# ------- -# strings - -class CharField(str, django_fields.CharField): - pass - - -class EmailField(CharField, django_fields.EmailField): - pass - - -class GenericIPAddressField(CharField, django_fields.GenericIPAddressField): - pass - - -class IPAddressField(CharField, django_fields.IPAddressField): - pass - - -class SlugField(CharField, django_fields.SlugField): - pass - - -class URLField(CharField, django_fields.URLField): - pass - - -class RegexField(CharField, django_fields.RegexField): - pass - - -class FilePathField(CharField, django_fields.FilePathField): - pass - - -# ------- -# numbers - -class IntegerField(int, django_fields.IntegerField): - pass - - -class DecimalField(Decimal, django_fields.DecimalField): - # DecimalField is immutable and does not use __init__, but the Django DecimalField does. To - # cheat pylint a little bit, we copy the definition of the DecimalField constructor parameters - # into the __new__ method of Decimal so that Pylint believes we are constructing a Decimal with - # the signature of DecimalField - def __new__(cls, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): - pass - - -class FloatField(float, django_fields.FloatField): - pass - - -# ------- -# date/time - -# In Python3, the date and datetime objects are immutable, so we need to do -# the same __new__ / __init__ fiddle as for Decimal - -class DateField(datetime.date, django_fields.DateField): - if PY3: - def __new__(cls, input_formats=None, *args, **kwargs): - pass - - -class DateTimeField(datetime.datetime, django_fields.DateTimeField): - if PY3: - def __new__(cls, input_formats=None, *args, **kwargs): - pass - - -class SplitDateTimeField(datetime.datetime, django_fields.SplitDateTimeField): - if PY3: - def __new__(cls, input_date_formats=None, input_time_formats=None, *args, **kwargs): - pass - - -class TimeField(datetime.time, django_fields.TimeField): - if PY3: - def __new__(cls, input_formats=None, *args, **kwargs): - pass - - -class DurationField(datetime.timedelta, django_fields.DurationField): - if PY3: - def __new__(cls, *args, **kwargs): - pass - - -# -------- -# choice - -class ChoiceField(object, django_fields.ChoiceField): - pass - - -class MultipleChoiceField(ChoiceField, django_fields.MultipleChoiceField): - pass - - -class TypedChoiceField(ChoiceField, django_fields.TypedChoiceField): - pass - - -class TypedMultipleChoiceField(ChoiceField, django_fields.TypedMultipleChoiceField): - pass diff --git a/pylint_django/transforms/transforms/django_views_generic_base.py b/pylint_django/transforms/transforms/django_views_generic_base.py deleted file mode 100644 index 2aac139e..00000000 --- a/pylint_django/transforms/transforms/django_views_generic_base.py +++ /dev/null @@ -1,8 +0,0 @@ -class View(object): - request = None - args = () - kwargs = {} - - # as_view is marked as class-only - def as_view(*args, **kwargs): - pass diff --git a/pylint_django/transforms/transforms/model_utils_managers.py b/pylint_django/transforms/transforms/model_utils_managers.py deleted file mode 100644 index 5a060f61..00000000 --- a/pylint_django/transforms/transforms/model_utils_managers.py +++ /dev/null @@ -1,13 +0,0 @@ -from django_db_models import Manager - - -class InheritanceManager(Manager): - pass - - -class QueryManager(Manager): - pass - - -class SoftDeletableManager(Manager): - pass diff --git a/pylint_django/transforms/transforms/mongoengine.py b/pylint_django/transforms/transforms/mongoengine.py deleted file mode 100644 index 548bea97..00000000 --- a/pylint_django/transforms/transforms/mongoengine.py +++ /dev/null @@ -1,16 +0,0 @@ -from mongoengine.errors import DoesNotExist, MultipleObjectsReturned -from mongoengine.queryset.manager import QuerySetManager - - -class Document(object): - _meta = None - objects = QuerySetManager() - - id = None - pk = None - - MultipleObjectsReturned = MultipleObjectsReturned - DoesNotExist = DoesNotExist - - def save(self): - return None