pydantic: mypy: invalid type comment or annotation

Hi,

I think this issue related more to mypy, but maybe you know about any workaround or how to solve this.

Here is my code:

class Test(BaseModel):
    k: constr(min_length=2)

And when I run mypy I got:

error: invalid type comment or annotation
note: Suggestion: use constr[...] instead of constr(...)

Any ideas?

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 61
  • Comments: 40 (15 by maintainers)

Commits related to this issue

Most upvoted comments

Why is this issue closed? From my understanding the typing behavior with mypy is still not fixed - or am I misunderstanding something?

For me I found simple solution how to use constr without mypy warnings. Look at example below:

UserName = constr(max_length=255, strip_whitespace=True)

class User(BaseModel):
    name: UserName

Also using this approach you can share your validation types, for example, between engineers in your company.

The above is not quite right as well, because the first parameter to Field is the default value. In this case the default would be the type str, which is not a str.

Another possible issue is that a default value was supplied, making this field optional.

Updated example:

class User(BaseModel):
    name: str = Field('Default name', max_length=255, strip_whitespace=True)

If you want to make the field a Required field, use an ellipsis:

class User(BaseModel):
    name: str = Field(..., max_length=255, strip_whitespace=True)

Something like required=True would feel more natural, but that’s not an option as of v1.4. Please be aware that supplying a default of None is not the same as supplying no default value.

This should be mentioned in the documentation:

https://pydantic-docs.helpmanual.io/usage/types/#constrained-types

is incompatible with mypy at the moment

@Gr1N That did it, thanks!

The final line is simply

AtLeastOneChar: constr = constr(min_length=1, strip_whitespace=True)

I believe the problem and the workaround should be explained in the docs.

sorry for the dumb question , shouldn’t the plugin handle this ?

@nandoflorestan @Gr1N that does not look correct, and in fact only works because you probably run mypy --follow-imports=skip, if you run mypy this would be the output

error: Function "pydantic.types.constr" is not valid as a type
note: Perhaps you need "Callable[...]" or a callback protocol?

the best is explained by @samuelcolvin here: https://github.com/samuelcolvin/pydantic/issues/975#issuecomment-551147305

Another approach is to inherit from ConstrainedStr:

class AtLeastOneChar(ConstrainedStr):
    min_length = 1
    strip_whitespace = True

class Item(BaseModel):
    field: AtLeastOneChar
    list_field: List[AtLeastOneChar]

For future people who find this issue, in v1 you can use Field for this:

EDIT: See comment below for an example https://github.com/samuelcolvin/pydantic/issues/156#issuecomment-614748288

Humans! Whoever thinks that Mypy does more evil than goodness by forcing developers to serve the code and not vice versa give it a thumbs up! May Mypy be eliminated from the entire history of the Universe…

Using Annotated for constraining decimals as price: Annotated[Decimal, Field(gt=Decimal("0.00"), decimal_places=2) with fastapi on 0.75.2 , Python 3.9.* and pydantic on 1.9.0 -> works! Swagger shows correct doc and validation is happening on runtime. No bypass needed now. Thanks!

Came across this from Google, in Python 3.9+ this can be solved with Annotated:

class Item(BaseModel):
    field: Annotated[str, constr(strict=True, max_length=2), Field(description='Really cool field')]

This doesn’t work – constr constraints are not applied. max_length is neither in schema of Item nor during runtime/parsing (eg. field can be longer then 2).

I’m using Python 3.9.* and pydantic 1.9.0.

Now that we have Annotated available, should this ticket be reopened? (I recognise it might not currently apply constraints correctly, but support could be implemented as part of this ticket)

This part of the Pydantic docs suggests the example should work “as is” and I would expect that to include type checking (I got this issue when trying it).

@nandoflorestan try this:

from pydantic import BaseModel, ConstrainedStr, Required, constr

AtLeastOneChar: ConstrainedStr = constr(min_length=1, strip_whitespace=True)


class Configuration(BaseModel):
    name: AtLeastOneChar = Required

Sadly since pep 472 was never implemented and keyword arguments to __getitem__ are not permitted this is not possible.

Even the works arounds suggested in pep472 foobar[{'min_length': 123}] or foobar['min_length':123] are both syntax errors in mypy so they’re not an option.

I’m closing this but happy to come back to it if anyone has a bright idea.

Even with Annotated[] now supported, defining strict fields with validation inline (while keeping mypy happy) is still not possible:

class Item(BaseModel):
    field: Annotated[StrictStr, Field(min_length=5))  # will throw as StrictStr overrides constraints from Field
    field: StrictStr = Field(min_length=5)  # is same as this, really, and throws for the same reason

The workaround of:

class Name(StrictStr):
    max_length = 2

class Item(BaseModel):
    field: Name

…is not so great, as it’s so verbose.

Name = constr(strict=True, max_length=2)

…could be better (far from perfect, though) but it just doesn’t comply with mypy.


StrictStr now inherits from ConstrainedStr which supports validators, and for that reason field: Constrained* = Field(...validators...) are not supported.

However, what if Strict* did not inherit from Constrained* classes and were simply:

class StrictStr(str):
    strict = True

In this case, you would need to define the validators via Field() (but it could be supported as there wouldn’t be possibility for clashes):

class Item(BaseModel):
    field: StrictStr = Field(min_length=5)

But I guess that would be too limited, as Constrained* classes do more than duplicate JSON schema validators (like constr(strip_whitespace=True, curtail_length=10)) and you would lose those.

I think now it should be better with Annotated that is mypy compliant So with author example

class Test(BaseModel):
    k: constr(min_length=2)

becomes

class Test(BaseModel):
    k: Annotated[str, Field(min_length=2)]

To anyone still running into this, # type: ignore now works. Its error code if you want to be pedantic (heh) is valid-type. The specific usage would be # type: ignore["valid-type"].

Quoting https://github.com/samuelcolvin/pydantic/issues/156#issuecomment-1073876877:

Came across this from Google, in Python 3.9+ this can be solved with Annotated:

class Item(BaseModel):
    field: Annotated[str, constr(strict=True, max_length=2), Field(description='Really cool field')]

This doesn’t work – constr constraints are not applied. max_length is neither in schema of Item nor during runtime/parsing (eg. field can be longer then 2).

The following isn’t pretty, but I believe it works at runtime and typecheck-time:

from pydantic import *
from typing import TYPE_CHECKING

class Item(BaseModel):
    if TYPE_CHECKING:
        field: str
    else:
        field: constr(strict=True, max_length=2)

item = Item(field="a")
if TYPE_CHECKING:
    reveal_type(item.field)   # prove that mypy can check uses of Item.field

Item(field="aaa")  # should raise pydantic.ValidationError

Running the above in an interpreter yields

>>> Item(field="aaa")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pydantic/main.py", line 331, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Item
field
  ensure this value has at most 2 characters (type=value_error.any_str.max_length; limit_value=2)

and mypy seems content:

temp.py:12: note: Revealed type is "builtins.str"
Success: no issues found in 1 source file

Ah yes. Like PrettyWood said. Waiting on a PR 😄

Is there a reason why Field(..., strict=True) is not supported? Wouldn’t that do it?

I can attempt to work it, if that’s a viable solution.

Yes, it does work. But I just wanted to know if there was a more robust or appropriate way to do it. I am working on a rather large project and I didn’t want to use # type: ignore excessively on several lines in different files.

class User(BaseModel):
    name: constr(max_length=2)

But exactly this one gives the mypy error.

Or create a custom class

class Name(StrictStr):
    max_length = 2

class User(BaseModel):
    name: Name

That’s pretty verbose. Lots of “wasted” characters, surely there’s a more compact way?

Like, if you could StrictStr(max_length=2) (but there’s no constructor, so you cannot directly do that). And, I guess it would fail with mypy the same way as constr() et al. now do.

For more info check out the doc

Yeah, I’ve read those, but I think they miss this one. For example, there’s no similar example than what you just pasted (setting max_length in sub-class).

Field(..., max_length=2) is just useful for the schema. You should do

class User(BaseModel):
    name: constr(max_length=2)

or create a custom class

class Name(StrictStr):
    max_length = 2

class User(BaseModel):
    name: Name

For more info check out the doc Hope it helps