pylint icon indicating copy to clipboard operation
pylint copied to clipboard

Pylint incorrectly resolving type vs instances

Open matejsp opened this issue 1 year ago • 1 comments

Bug description

When trying to have a method that read from class variable that can be type or instance of that type pylint incorrectly.

First issue is in line 16 saying that data_schema_class is not callable (but it is). Second issue is that in line 35 it says that obj is not defined (because first paramer is self)

import typing

import marshmallow


class Base:
    data_schema_class: typing.Optional[
        typing.Union[typing.Type[marshmallow.Schema], marshmallow.Schema]
    ] = None  # Marshmallow schema class for responses

    def get_schema_instance(self) -> marshmallow.Schema:
        if self.data_schema_class is None:
            raise NotImplementedError()

        if isinstance(self.data_schema_class, marshmallow.Schema):
            return self.data_schema_class

        schema = self.data_schema_class()
        return schema


class DerivedOne(Base):

    data_schema_class = marshmallow.Schema()

    def test_me(self):
        schema = self.get_schema_instance()
        schema.dump({})


class DerivedTwo(Base):

    data_schema_class = marshmallow.Schema

    def tesst_me(self):
        schema = self.get_schema_instance()
        schema.dump({})

Configuration

No response

Command used

pylint  --disable=C0114,C0115,C0116,R0903 pylint_error.py

Pylint output

(.venv) ➜ pylint pylint_error.py
************* Module pylint_error
pylint_error.py:16:17: E1102: self.data_schema_class is not callable (not-callable)
pylint_error.py:35:8: E1120: No value for argument 'obj' in unbound method call (no-value-for-parameter)

Expected behavior

It should pass the validation.

Pylint version

pylint 3.0.3
astroid 3.0.2
Python 3.11.2 (main, Jan  5 2024, 12:15:46) [Clang 15.0.0 (clang-1500.1.0.2.5)]

OS / Environment

MacOSX

Additional dependencies

marshmallow==3.20.1

matejsp avatar Feb 02 '24 12:02 matejsp

I can reproduce that on Ubuntu 22.04, Python 3.10.12, pylint 3.1.0. I've stripped marshmallow from the example code, which leaves this:

# pylint: disable=missing-module-docstring
# pylint: disable=missing-class-docstring
# pylint: disable=missing-function-docstring
# pylint: disable=too-few-public-methods

import typing


class Schema:
    def dump(self, obj):
        pass


class Base:
    data_schema_class: typing.Optional[
        typing.Union[typing.Type[Schema], Schema]
    ] = None  # Marshmallow schema class for responses

    def get_schema_instance(self) -> Schema:
        if self.data_schema_class is None:
            raise NotImplementedError()

        if isinstance(self.data_schema_class, Schema):
            return self.data_schema_class

        schema = self.data_schema_class()  # line 26
        return schema


class DerivedOne(Base):

    data_schema_class = Schema()

    def test_me(self):
        schema = self.get_schema_instance()
        schema.dump({})


class DerivedTwo(Base):

    data_schema_class = Schema

    def tesst_me(self):
        schema = self.get_schema_instance()
        schema.dump({})  # line 45

This produces the following output:

> pylint demo.py
************* Module demo
demo.py:26:17: E1102: self.data_schema_class is not callable (not-callable)
demo.py:45:8: E1120: No value for argument 'obj' in unbound method call (no-value-for-parameter)

------------------------------------------------------------------
Your code has been rated at 5.65/10 (previous run: 5.65/10, +0.00)

I've also tried latest commit (82ef6475) from the main branch:

> PYTHONPATH=. python -m pylint issue9541/demo.py 

************* Module demo
issue9541/demo.py:15:23: R6003: Consider using alternative Union syntax instead of 'Optional'. Add 'from __future__ import annotations' as well (consider-alternative-union-syntax)
issue9541/demo.py:16:8: R6003: Consider using alternative Union syntax instead of 'Union'. Add 'from __future__ import annotations' as well (consider-alternative-union-syntax)
issue9541/demo.py:26:17: E1102: self.data_schema_class is not callable (not-callable)
issue9541/demo.py:45:8: E1120: No value for argument 'obj' in unbound method call (no-value-for-parameter)
issue9541/demo.py:16:21: R6002: 'typing.Type' will be deprecated with PY39, consider using 'type' instead. Add 'from __future__ import annotations' as well (consider-using-alias)

------------------------------------------------------------------
Your code has been rated at 4.35/10 (previous run: 0.00/10, +4.35)

This emits additional R6003 and R6002 messages, maybe that can be used to create a workaround, @matejsp, while at the same time improving the future-compatibility of your code.

BTW: The documentation for typing of class objects (as opposed to instances) is https://docs.python.org/3/library/typing.html#the-type-of-class-objects. The example code there doesn't raise warnings about two calls that are made with faulty parameters (but a few others). Maybe fixing this less complex issue first will cause the more complex one to go away automagically. For the record, the code I was running pylint on is this:

from __future__ import annotations

class User: ...
class ProUser(User): ...
class TeamUser(User): ...

def make_new_user(user_class: type[User]) -> User:
    return user_class()

make_new_user(User)      # OK
make_new_user(ProUser)   # Also OK: ``type[ProUser]`` is a subtype of ``type[User]``
make_new_user(TeamUser)  # Still fine
make_new_user(User())    # Error: expected ``type[User]`` but got ``User``
make_new_user(int)       # Error: ``type[int]`` is not a subtype of ``type[User]``

UlrichEckhardt avatar May 01 '24 22:05 UlrichEckhardt