django-extra-checks icon indicating copy to clipboard operation
django-extra-checks copied to clipboard

Getting "OSError: could not find class definition" for most checks

Open JackAtOmenApps opened this issue 2 years ago • 1 comments

I tried installing django-extra-checks, but only one of the check I've tried worked without errors.

My EXTRA_CHECKS dict looks like this:

EXTRA_CHECKS = {
    "checks": [
        # FileField/ImageField must have non empty `upload_to` argument
        "field-file-upload-to",
        #
        # Use UniqueConstraint with the constraints option instead
        # "no-unique-together",
        #
        # Use the indexes option instead
        # "no-index-together",
        #
        # All field's `verbose_name` must use gettext
        # "field-verbose-name-gettext",
        #
        # All field's `help_text` must use gettext
        # "field-help-text-gettext",
        #
        # TextField() shouldn't use null=True
        # "field-text-null",
    ]
}

The field-file-upload-to check works fine if it's the only check. If I uncomment any of the other checks in EXTRA_CHECKS above (whether field-file-upload-to is commented out or not), I get the following:

Traceback (most recent call last):
  File "/app/manage.py", line 31, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.9/site-packages/django/core/management/__init__.py", line 413, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/base.py", line 354, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/base.py", line 398, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.9/site-packages/django/core/management/commands/check.py", line 63, in handle
    self.check(
  File "/usr/local/lib/python3.9/site-packages/django/core/management/base.py", line 419, in check
    all_issues = checks.run_checks(
  File "/usr/local/lib/python3.9/site-packages/django/core/checks/registry.py", line 80, in run_checks
    errors.extend(new_errors)
  File "/usr/local/lib/python3.9/site-packages/extra_checks/checks/model_checks.py", line 63, in check_models
    yield from check(field, ast=field_ast, model=model)
  File "/usr/local/lib/python3.9/site-packages/extra_checks/checks/model_field_checks.py", line 31, in __call__
    yield from super().__call__(obj, ast=ast, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/extra_checks/checks/base_checks.py", line 40, in __call__
    if not ast or not ast.is_disabled_by_comment(error.id):
  File "/usr/local/lib/python3.9/site-packages/django/utils/functional.py", line 246, in inner
    self._setup()
  File "/usr/local/lib/python3.9/site-packages/django/utils/functional.py", line 382, in _setup
    self._wrapped = self._setupfunc()
  File "/usr/local/lib/python3.9/site-packages/extra_checks/ast/ast.py", line 96, in <lambda>
    FieldAST, SimpleLazyObject(lambda: get_field_ast(self, field))  # type: ignore
  File "/usr/local/lib/python3.9/site-packages/extra_checks/ast/ast.py", line 117, in get_field_ast
    model_ast._assignments[field.name], field, model_ast._source_provider
  File "/usr/local/lib/python3.9/site-packages/django/utils/functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/usr/local/lib/python3.9/site-packages/extra_checks/ast/ast.py", line 84, in _assignments
    self._parse()
  File "/usr/local/lib/python3.9/site-packages/extra_checks/ast/ast.py", line 54, in _parse
    for node in self._nodes:
  File "/usr/local/lib/python3.9/site-packages/django/utils/functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/usr/local/lib/python3.9/site-packages/extra_checks/ast/ast.py", line 48, in _nodes
    if self._source_provider.source is None:
  File "/usr/local/lib/python3.9/site-packages/django/utils/functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/usr/local/lib/python3.9/site-packages/extra_checks/ast/source_provider.py", line 45, in source
    return textwrap.dedent(inspect.getsource(self._obj))
  File "/usr/local/lib/python3.9/inspect.py", line 1024, in getsource
    lines, lnum = getsourcelines(object)
  File "/usr/local/lib/python3.9/inspect.py", line 1006, in getsourcelines
    lines, lnum = findsource(object)
  File "/usr/local/lib/python3.9/inspect.py", line 851, in findsource
    raise OSError('could not find class definition')
OSError: could not find class definition
ERROR: 1

I'm not exactly sure where to go from here to troubleshoot this. Any ideas? Am I missing something obvious?

JackAtOmenApps avatar Nov 20 '21 16:11 JackAtOmenApps

@OmenApps inspect.getsource can't find source code of one of your models:

  File "/usr/local/lib/python3.9/site-packages/extra_checks/ast/source_provider.py", line 45, in source
    return textwrap.dedent(inspect.getsource(self._obj))

check what is inside of self._obj and if its source code file is reachable from the environment where you run check command. Try to run inspect.getsource on that object from manage.py shell.

Judging by the paths of the stack trace you're most probably running the app inside docker, so my best guess is that something broken in environment setup that leads to the problem when inspect can't find source file.

kalekseev avatar Nov 21 '21 19:11 kalekseev

Hello, I am using django-extra-checks in a project and I have this same problem with the django-simple-history library. I tried to ignore the line that generates the error according to the documentation, but it still does not work. This is the case:

# Standard Libraries
import textwrap
from typing import Final, final

# Django Libraries
from django.db import models
from django.utils.translation import gettext_lazy as _

# Thirdparty Libraries
from main.models.main import MAX_LENGTH_CHAR_FIELD, BaseAbstractModel
from simple_history.models import HistoricalRecords

#: That's how constants should be defined.
_POST_TITLE_MAX_LENGTH: Final = 80


@final
class BlogPost(BaseAbstractModel):
    title = models.CharField(max_length=_POST_TITLE_MAX_LENGTH)
    body = models.TextField()
    # extra-checks-disable-next-line
    history = HistoricalRecords()

    class Meta(object):
        verbose_name = _("BlogPost")
        verbose_name_plural = _("BlogPosts")

    def __str__(self) -> str:
        return textwrap.wrap(self.title, _POST_TITLE_MAX_LENGTH // 4)[0]

This is the output:

Performing system checks...

Exception in thread django-main-thread:
Traceback (most recent call last):
  File "/project/.pyenv/versions/3.9.13/lib/python3.9/threading.py", line 980, in _bootstrap_inner
    self.run()
  File "/project/.pyenv/versions/3.9.13/lib/python3.9/threading.py", line 917, in run
    self._target(*self._args, **self._kwargs)
  File "/project/.venv/lib/python3.9/site-packages/django/utils/autoreload.py", line 64, in wrapper
    fn(*args, **kwargs)
  File "/project/.venv/lib/python3.9/site-packages/django/core/management/commands/runserver.py", line 118, in inner_run
    self.check(display_num_errors=True)
  File "/project/.venv/lib/python3.9/site-packages/django/core/management/base.py", line 419, in check
    all_issues = checks.run_checks(
  File "/project/.venv/lib/python3.9/site-packages/django/core/checks/registry.py", line 80, in run_checks
    errors.extend(new_errors)
  File "/project/.venv/lib/python3.9/site-packages/extra_checks/checks/model_checks.py", line 59, in check_models
    yield from check(model, ast=model_ast)
  File "/project/.venv/lib/python3.9/site-packages/extra_checks/checks/base_checks.py", line 40, in __call__
    if not ast or not ast.is_disabled_by_comment(error.id):
  File "/project/.venv/lib/python3.9/site-packages/extra_checks/ast/ast.py", line 112, in is_disabled_by_comment
    return check in self._source_provider.get_disabled_checks_for_line(1)
  File "/project/.venv/lib/python3.9/site-packages/extra_checks/ast/source_provider.py", line 69, in get_disabled_checks_for_line
    self._comments_cache[line_no] = _find_disabled_checks(comments)
  File "/project/.venv/lib/python3.9/site-packages/extra_checks/ast/source_provider.py", line 30, in _find_disabled_checks
    for line in comments:
  File "/project/.venv/lib/python3.9/site-packages/extra_checks/ast/source_provider.py", line 59, in _get_line_comments
    for line in (self.source or "").splitlines()[line_no::-1]:
  File "/project/.venv/lib/python3.9/site-packages/django/utils/functional.py", line 48, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/project/.venv/lib/python3.9/site-packages/extra_checks/ast/source_provider.py", line 45, in source
    return textwrap.dedent(inspect.getsource(self._obj))
  File "/project/.pyenv/versions/3.9.13/lib/python3.9/inspect.py", line 1024, in getsource
    lines, lnum = getsourcelines(object)
  File "/project/.pyenv/versions/3.9.13/lib/python3.9/inspect.py", line 1006, in getsourcelines
    lines, lnum = findsource(object)
  File "/project/.pyenv/versions/3.9.13/lib/python3.9/inspect.py", line 851, in findsource
    raise OSError('could not find class definition')
OSError: could not find class definition

Is there any possible solution that you can recommend me?

Thank you

gvizquel avatar Oct 22 '22 00:10 gvizquel

@gvizquel Hi, you can disable HistoricalRecords field check with skipif, see example skipif_streamfield https://github.com/kalekseev/django-extra-checks#ignoring-check-problems

If you have time to debug please provide information what you have in self._obj here:

  File "/project/.venv/lib/python3.9/site-packages/extra_checks/ast/source_provider.py", line 45, in source
    return textwrap.dedent(inspect.getsource(self._obj))

kalekseev avatar Oct 22 '22 17:10 kalekseev

I just installed/configured django-extra-checks and I have exactly the same problem.

Maybe adding a new extra-check on the app itself for catching unexpected errors is needed here :)

fabiocaccamo avatar Nov 08 '22 10:11 fabiocaccamo

@gvizquel thank you for the source code, I was able to reproduce the error with simple history field. The root cause of the error is that simple history generate a companion model in the runtime so it's impossible to get its source code.

If django-extra-checks emits errors for that model you can disable them using skipif:

def skipif_simple_history(model_cls, *args, **kwargs):
    from simple_history.models import HistoricalChanges

    return issubclass(model_cls, HistoricalChanges)


EXTRA_CHECKS = {
    "checks": [
        {
            "id": "model-attribute",
            "attrs": ["tenant_link"],
            "level": "ERROR",
            "skipif": skipif_simple_history,
        },
        {
            "id": "model-meta-attribute",
            "attrs": ["db_table"],
            "level": "ERROR",
            "skipif": skipif_simple_history,
        },
}

kalekseev avatar Nov 13 '22 15:11 kalekseev

@kalekseev thank you very much for the quick fix and release!

fabiocaccamo avatar Nov 15 '22 09:11 fabiocaccamo