Pydantic: pylint complain on models with validators since version 0.27

Created on 31 May 2019  路  17Comments  路  Source: samuelcolvin/pydantic



Bug

For bugs/questions:

  • OS: Linux (Debian 9)
  • Python version import sys; print(sys.version):
    3.7.3 (default, May 22 2019, 11:07:29)
    [GCC 6.3.0 20170516]
  • Pydantic version import pydantic; print(pydantic.VERSION): 0.27

Where possible please include a self contained code snippet describing your
bug, question, or where applicable feature request:

the below model causes pylint model.py to complain about
model.py:12:4: E0213: Method should have "self" as first argument (no-self-argument)
model.py:12:4: R0201: Method could be a function (no-self-use)
model.py:7:0: R0903: Too few public methods (1/2) (too-few-public-methods)

from typing import Optional

import pydantic


class DemoModel(pydantic.BaseModel):

    some: Optional[str] = None

    @pydantic.validator("some")
    def check_some(cls, val):
        return val

These complaints does not occur with pydantic 0.26.

Marking the validator as @classmethod will cause it to not execute at all, so the only option I have found is to disable these checks in pylint.

question

Most helpful comment

@samuelcolvin
Even flake8 complains about the first argument not being self. Furthermore, I don't think that changing linters is something that most people would consider.

Nevertheless, the problem is clearly with pylint which doesn't support custom decorators/descriptors.

A hackish workaround is to add a @classmethod decorator after the pydantic.validator one. For simple cases it seems to work and it does silence the linters. Not sure if there are any side-effects though and I haven't tested it in bigger codebases:

import pydantic

class Model(pydantic.BaseModel):

    value: int

    @pydantic.validator("value")
    @classmethod
    def _get_the_answer(cls, val: int) -> int:
        print(val)
        return 42

instance = Model(value=3)
print(instance)

In my case, I will probably just add an explicit # pylint: disable=no-self-argument,no-self-use though

All 17 comments

The major change in v0.27 was that pydantic was compiled with cython, I guess this makes introspection by pylint of the pydantic code harder or impossible.

There might be some way to trick pylint into think you're using the non-compiled pydantic code (should behave exactly the same), but I'm afraid that's a question for pylint.

Otherwise I don't really know how to help as I've never used pylint.

Let me know if you find a solution.

The issue seems to be in the published version of pydantic.

To replicate this I cloned the pydantic git and did the following.

Running pylint in a docker container with the source mounted I see the following.

$ docker run --rm -it -v `pwd`:/src -w /src -e https_proxy python:3.7 bash -c "pip install -q pylint && cat demo_model.py && pylint demo_model.py" 
# pylint: disable=missing-docstring
from typing import Optional

import pydantic


class DemoModel(pydantic.BaseModel):

    some: Optional[str] = None

    @pydantic.validator("some")
    def check_some(cls, val):
        return val

------------------------------------
Your code has been rated at 10.00/10

But if I use the published pydantic version I get the errors mentioned in the bug report.

$ docker run --rm -it -v `pwd`/demo_model.py:/src/demo_model.py -w /src -e https_proxy python:3.7 bash -c "pip install -q pydantic==0.27 pylint && cat demo_model.py && pylint demo_model.py"
# pylint: disable=missing-docstring
from typing import Optional

import pydantic


class DemoModel(pydantic.BaseModel):

    some: Optional[str] = None

    @pydantic.validator("some")
    def check_some(cls, val):
        return val
************* Module demo_model
demo_model.py:12:4: E0213: Method should have "self" as first argument (no-self-argument)
demo_model.py:12:4: R0201: Method could be a function (no-self-use)
demo_model.py:7:0: R0903: Too few public methods (1/2) (too-few-public-methods)

------------------------------------
Your code has been rated at -1.67/10

This might be related to the cython changes done for this release.

And on a side note. When introducing potentially breaking changes, please step up the major version to indicate this. It's hell to investigate and fix test environments due to breaking changes in minor version bumps.

confirmed with

curl -L https://files.pythonhosted.org/packages/28/06/5faf55ff0fc320c508bbdc6d3a85ca1da7537fb42617863eda1a524e4bcd/pydantic-0.27-py36.py37.py38-none-any.whl > pydantic-0.27-py36.py37.py38-none-any.whl
pip uninstall -y pydantic
pip install pydantic-0.27-py36.py37.py38-none-any.whl
pylint test.py

Doesn't give the error, while default install from pypi does.

You might be able to use something like the above for linting, but make sure you're using the compiled pydantic for tests and production.

@gangefors

And on a side note. When introducing potentially breaking changes, please step up the major version to indicate this. It's hell to investigate and fix test environments due to breaking changes in minor version bumps.

  1. I'm following semvar, we're pre version 1 so everything can be a breaking change, but here I did use a new minor version for this change - the biggest change I could make except for upreving to v1 which I think you'd agree would be more confusing.
  2. I documented it very prominently in the change log.
  3. I created a pre-release to test cython, left the pull request #548 open for a couple of days for feedback and ask people to check it worked.

Is that really not sufficient?

But we should move to version 1, I agree.

I would suggest you use something like pyup to upgrade dependencies, the changelog included in PRs will avoid "hell".

I hear you regarding following semvar. But then one could argue that it's easy to stay at 0.x.y for all future releases which would make life easy for the developer but not for the users.

This software is awesome and we have considered it really stable for many versions now, sans a couple of breaking updates. We do hope that you decide to "release" it soon. Thanks for your work and fast respones in the community.

thanks, I agree - just trying to work out if there's anything big I want to change before releasing.

This is still an issue in pydantic 1.5.1.

Seeing the same issue on pydantic 1.5.1

I'm also having the same issue :/

The code works, its just the pylint complain that annoys, but in the meantime this error could be disabled if you write this comment on top of the class
# pylint: disable=E0213

I did find this information on this post:
https://stackoverflow.com/questions/27965324/how-to-disable-pylint-no-self-use-warning

Please reopen, I also have the same issue in 1.5.1 . It is very annoying

What's the problem in pydantic? As far as I know this is an issue with pylint.

I need some indication of what pydantic is doing wrong or how we might fix it to reopen this.


If you find the open source code I work on in my free time and give away for free annoying, don't use it.

pydantic isn't doing anything wrong, it's pylint that can't introspect the code properly.

AFAICT pydantic.validator returns a classmethod type object and pylint should be able to pick this up but it doesn't.

There might be ways to rewrite the code to make pylint happy, but I do think that it would be better to make pylint handle the code as it is written since it's totally valid code.

Possibly related pylint issue. https://github.com/PyCQA/pylint/issues/1694

I'm pretty sure the code is typed correctly since it passes mypy and didn't used to, but the typing around validators is pretty complicated, I wouldn't be surprised if it was too complicated for pylint.


In general I find mypy + black + isort + flake8 enough. That more does pylint provide?

@samuelcolvin
Even flake8 complains about the first argument not being self. Furthermore, I don't think that changing linters is something that most people would consider.

Nevertheless, the problem is clearly with pylint which doesn't support custom decorators/descriptors.

A hackish workaround is to add a @classmethod decorator after the pydantic.validator one. For simple cases it seems to work and it does silence the linters. Not sure if there are any side-effects though and I haven't tested it in bigger codebases:

import pydantic

class Model(pydantic.BaseModel):

    value: int

    @pydantic.validator("value")
    @classmethod
    def _get_the_answer(cls, val: int) -> int:
        print(val)
        return 42

instance = Model(value=3)
print(instance)

In my case, I will probably just add an explicit # pylint: disable=no-self-argument,no-self-use though

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bartekbrak picture bartekbrak  路  3Comments

gangefors picture gangefors  路  3Comments

ashears picture ashears  路  3Comments

krzysieqq picture krzysieqq  路  3Comments

samuelcolvin picture samuelcolvin  路  3Comments