pylint-dev / pylint

It's not just a linter that annoys you!
https://pylint.readthedocs.io/en/latest/
GNU General Public License v2.0
5.33k stars 1.14k forks source link

pylint crashed with a ``AstroidError`` (astroid.exceptions.ParentMissingError) #7680

Open sharky98 opened 2 years ago

sharky98 commented 2 years ago

Bug description

When trying to lint a file that contain the DeclarativeBase of the recent SQLAlchemy 2.0 (beta), it fails with an astroid.exceptions.ParentMissingError: Parent not found on <Const.str l.None at 0x189844d3730>. error. I was able to pin point the issue with the new sqlalchemy.orm.DeclarativeBase.

I understand this is most probably due to the new SQLAlchemy 2.0, but since the traceback point to pylint, I created to issue here. Maybe some guys from SQLAlchemy could help out here too? @CaselIT @zzzeek (hopefully that will ping them!)

Code that makes pylint crash

The code below is based on the migration guide and the declarative mixins. The only difference I made is to put the mixin directly in the base.

# pylint: disable=missing-docstring
from sqlalchemy.orm import DeclarativeBase, declared_attr

class Base(DeclarativeBase):
    @classmethod
    @declared_attr.directive
    def __tablename__(cls):
        return cls.__name__.lower()

Stacktrace

Traceback (most recent call last):
  File "[project_root].venv\lib\site-packages\pylint\lint\pylinter.py", line 790, in _lint_file
    check_astroid_module(module)
  File "[project_root].venv\lib\site-packages\pylint\lint\pylinter.py", line 1060, in check_astroid_module
    retval = self._check_astroid_module(
  File "[project_root].venv\lib\site-packages\pylint\lint\pylinter.py", line 1110, in _check_astroid_module
    walker.walk(node)
  File "[project_root].venv\lib\site-packages\pylint\utils\ast_walker.py", line 93, in walk
    self.walk(child)
  File "[project_root].venv\lib\site-packages\pylint\utils\ast_walker.py", line 93, in walk
    self.walk(child)
  File "[project_root].venv\lib\site-packages\pylint\utils\ast_walker.py", line 90, in walk
    callback(astroid)
  File "[project_root].venv\lib\site-packages\pylint\checkers\classes\special_methods_checker.py", line 183, in visit_functiondef
    inferred = _safe_infer_call_result(node, node)
  File "[project_root].venv\lib\site-packages\pylint\checkers\classes\special_methods_checker.py", line 42, in _safe_infer_call_result
    value = next(inferit)
  File "[project_root].venv\lib\site-packages\astroid\nodes\scoped_nodes\scoped_nodes.py", line 1749, in infer_call_result
    yield from returnnode.value.infer(context)
  File "[project_root].venv\lib\site-packages\astroid\nodes\node_ng.py", line 169, in infer
    yield from self._infer(context=context, **kwargs)
  File "[project_root].venv\lib\site-packages\astroid\decorators.py", line 140, in raise_if_nothing_inferred
    yield next(generator)
  File "[project_root].venv\lib\site-packages\astroid\decorators.py", line 109, in wrapped
    for res in _func(node, context, **kwargs):
  File "[project_root].venv\lib\site-packages\astroid\inference.py", line 255, in infer_call
    for callee in self.func.infer(context):
  File "[project_root].venv\lib\site-packages\astroid\nodes\node_ng.py", line 182, in infer
    for i, result in enumerate(self._infer(context=context, **kwargs)):
  File "[project_root].venv\lib\site-packages\astroid\decorators.py", line 140, in raise_if_nothing_inferred
    yield next(generator)
  File "[project_root].venv\lib\site-packages\astroid\decorators.py", line 109, in wrapped
    for res in _func(node, context, **kwargs):
  File "[project_root].venv\lib\site-packages\astroid\inference.py", line 343, in infer_attribute
    for owner in self.expr.infer(context):
  File "[project_root].venv\lib\site-packages\astroid\nodes\node_ng.py", line 182, in infer
    for i, result in enumerate(self._infer(context=context, **kwargs)):
  File "[project_root].venv\lib\site-packages\astroid\decorators.py", line 140, in raise_if_nothing_inferred
    yield next(generator)
  File "[project_root].venv\lib\site-packages\astroid\decorators.py", line 109, in wrapped
    for res in _func(node, context, **kwargs):
  File "[project_root].venv\lib\site-packages\astroid\inference.py", line 352, in infer_attribute
    yield from owner.igetattr(self.attrname, context)
  File "[project_root].venv\lib\site-packages\astroid\nodes\scoped_nodes\scoped_nodes.py", line 2646, in igetattr
    first_scope = first_attr.scope()
  File "[project_root].venv\lib\site-packages\astroid\nodes\node_ng.py", line 359, in scope
    raise ParentMissingError(target=self)
astroid.exceptions.ParentMissingError: Parent not found on <Const.str l.None at 0x189844d3730>.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "[project_root].venv\lib\site-packages\pylint\lint\pylinter.py", line 755, in _lint_files
    self._lint_file(fileitem, module, check_astroid_module)
  File "[project_root].venv\lib\site-packages\pylint\lint\pylinter.py", line 792, in _lint_file
    raise astroid.AstroidError from e
astroid.exceptions.AstroidError

Code that works

Having the mixin code not in the Base seems to resolve the issue.

# pylint: disable=missing-docstring
from sqlalchemy.orm import DeclarativeBase, Mapped, declared_attr, mapped_column

class Base(DeclarativeBase):
    pass

class CommonMixin:
    @classmethod
    @declared_attr.directive
    def __tablename__(cls) -> str:
        return cls.__name__.lower()

    id: Mapped[int] = mapped_column(primary_key=True)

So does getting rid of DeclarativeBase

# pylint: disable=missing-docstring
class Base:
    @classmethod
    def __tablename__(cls):
        """My Docstring"""
        return cls.__name__.lower()

Configuration

No response

Command used

pylint test.py

Pylint output

************* Module test
test.py:1:0: F0002: test.py: Fatal error while checking 'test.py'. Please open an issue in our bug tracker so we address this. There is a pre-filled template that you can use in '[...]'. (astroid-error)

Expected behavior

No issues found.

Pylint version

pylint 2.15.5
astroid 2.12.12
Python 3.10.5 (tags/v3.10.5:f377153, Jun  6 2022, 16:14:13) [MSC v.1929 64 bit (AMD64)]

OS / Environment

Windows 10 Poetry

Additional dependencies

sqlalchemy = {version = "^2.0.0b2", allow-prereleases = true}

zzzeek commented 2 years ago

definitely on pylint side, we run many code quality tools on sqlalchemy including flake8 with lots of extensions, mypy, pyright, pylance, black, and others.

pylint 2.15.5 reproduces the issue, while an older version I had installed, 2.11.1, does not:

$ pylint test3.py 
************* Module test3
test3.py:9:0: C0304: Final newline missing (missing-final-newline)
test3.py:5:0: R0903: Too few public methods (1/2) (too-few-public-methods)

------------------------------------------------------------------
Your code has been rated at 5.00/10 (previous run: 5.00/10, +0.00)
widal001 commented 9 months ago

I had a very similar issue in #9446 and simply removing the @classmethod worked for me:

class UUIDAuditBase(DeclarativeBase):
    """Base db model that includes id, created_at, and update_at."""

    id: Mapped[UUID] = mapped_column(primary_key=True)
    created_at: Mapped[DateTime] = mapped_column(
        DateTime(timezone=True),
        default=functions.now(),
    )
    updated_at: Mapped[DateTime] = mapped_column(
        DateTime(timezone=True),
        default=functions.now(),
        onupdate=functions.now(),
    )

    @declared_attr.directive
    def __tablename__(cls) -> str:  # noqa: N805
        """Set default table name as the lowercase version of the class name."""
        return cls.__name__.lower()