pydantic: Mypy plugin doesn't consider `allow_population_by_field_name`

Initial Checks

  • I have searched GitHub for a duplicate issue and I’m sure this is something new
  • I have searched Google & StackOverflow for a solution and couldn’t find anything
  • I have read and followed the docs and still think this is a bug
  • I am confident that the issue is with pydantic (not my code, or another library in the ecosystem like FastAPI or mypy)

Description

Probably an issue with the Pydantic mypy plugin. It happened after the mypy 1.0.0 release.

Consider this code, and run mypy --strict:

from pydantic import BaseModel, Field

class Model(BaseModel):
    field: str = Field(alias="alias")

    class Config:
        allow_population_by_field_name = True

Model(alias="")  # OK.
Model(field="")  # error: Unexpected keyword argument "field" for "Model"  [call-arg]

At runtime, this is fine, because of the allow_population_by_field_name setting, but the mypy plugin only allows instantiating the class through the alias.

Python, Pydantic & OS Version

Python 3.9.13
Pydantic 1.10.5
Ubuntu on ARM, Linux 5.15.0-58

Affected Components

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 1
  • Comments: 26 (22 by maintainers)

Most upvoted comments

from pydantic import BaseModel, Field

class Model(BaseModel):
    field: str = Field(alias="alias")

    class Config:
        allow_population_by_field_name = True

Model(alias="")
Model(field="")

@andersfylling, I think what you’re missing is that given the above, only 1 variation can ever be valid at a given time.

Config.allow_population_by_field_name just switches which one is valid for mypy when used with the plugin.

Your first error is for line 10, the second error is for line 9 instead.

For what it’s worth, if this is not resolved by #5111, I would be happy to accept a PR fixing this (in pydantic v1.10.X or v2).

If no one else gets to this first, I expect we will fix this at some point in the not-distant future in connection with the v2 release, but it will definitely happen faster if someone has a chance to figure it out what’s going wrong before then.

For what it’s worth, I would be okay with either of these approaches:

  • Generating only a single signature using no aliases when allow_population_by_field_name is True
  • Generating two overloads, one with all aliases and one with no aliases, when allow_population_by_field_name is True

I don’t think it’s a good idea to try to support every combination of keyword argument names as that will bog mypy down massively for any model with a large number of keywords, but the all-aliases and no-aliases approach seems reasonable to me.

@andersfylling the issue in the thread was that you get:

Model(alias="")  # OK.
Model(field="")  # error: Unexpected keyword argument "field" for "Model"  [call-arg]

In your example, the second case gives the error error: Missing named argument "field" for "Model" for the first line, which was addressing the issue presented. (That populate_by_name was being ignored.)

Making it produce that is what was necessary to resolve this issue, so what you are asking for is not the issue described in the thread (or at least not in the body of the issue, even if it was discussed above).

I recognize that it may be nice to have it work with either form, but I would consider that a feature request. (And I don’t believe it will be easy to implement.) The problem in this issue (at least as I interpreted it) was that it wasn’t updating the signature to use the field name. That was definitely a bug, and was not the intended behavior. I have never (yet) intended to get both the alias and the field name working in the __init__ signature, and I worry that would require a lot of effort to implement in the mypy plugin. I would be open to reviewing a PR if someone gets it working though.

If you still want the behavior changed, please create a new issue describing the current behavior and what you would like it to do instead.

(You may also be interested in https://github.com/pydantic/pydantic/pull/5433, which (in v2) will provide a way to specify the alias without it affecting the signature used by type checkers, on a per-field basis. This won’t make it so the type checker allows both with and without alias simultaneously, but it may help you get the signature you want without needing the plugin.)

You would need an overload for every single combination of possible args.

I think it would be a significant improvement to just do two overloads – one that uses all aliases, and one that uses no aliases (when both are acceptable). Usually users are consistent about using one of these two approaches. (I personally would want it to always not use aliases when the field names are acceptable, but I understand not everyone feels the same…)

That said, we’d probably want to make sure that it didn’t result in a significant increase in error message verbosity (and corresponding decrease in clarity) when using invalid keyword arguments, i.e., arguments that aren’t a part of either signature.

I haven’t worked on the mypy plugin for a while (and am not very familiar with pyright) so I don’t know how hard this would be.

Thanks @jonathanslenders for reporting this.

Yes, you are right. I can confirm the problem in 1.10.5 with both mypy 1.0.1 and mypy 1.1.0+dev. I’ve tested it on V2 and they return Unexpected keyword argument "field" for "Model" [call-arg] with both mypy 1.0.1 and mypy 1.1.0+dev

@cdce8p I’ve tried your fix on mypy with https://github.com/pydantic/pydantic/pull/5077, but it doesn’t fix the problem on 1.10.x and V2