How can mypy accept pydantic's constr() types?

Question:

I have this code:

from pydantic import BaseModel, constr

DeptNumber = constr(min_length=6, max_length=6)

class MyStuff(BaseModel):
    dept: DeptNumber

ms = MyStuff(dept = "123456")

deptnr.py:6: error: Variable "deptnr.DeptNumber" is not valid as a type
deptnr.py:6: note: See https://mypy.readthedocs.io/en/latest/common_issues.html#variables-vs-type-aliases

The provided link doesn’t seem to really address my problem (I’m not using Type).

This happens with or without this mypy.ini:

[mypy]
plugins = pydantic.mypy

[pydantic-mypy]
init_typed = true

Initially I also had that error in a Pydantic choice as below, but I got around that by using Python’s Literal instead.

DIR = choice(["North", "East", "South", "West"])

What do I need to change to make mypy happy with my Pydantic constr?

Asked By: Robert

||

Answers:

You can try to use Field from Pydantic :

from pydantic import BaseModel, Field

class MyStuff(BaseModel):
    dept: str = Field(..., min_length=6, max_length=6)

It seems working for me.

Answered By: Raylian

This incompatibility with mypy has been discussed in this Github issue https://github.com/samuelcolvin/pydantic/issues/156. Sadly, no concrete solution that uses constr and keeps mypy happy was found.

Instead of constr, you can subclass pydantic’s ConstrainedStr, which offers the same configurations and options as constr, but without mypy complaining about type aliases.

from pydantic import BaseModel, ConstrainedStr

class DeptNumber(ConstrainedStr):
    min_length = 6
    max_length = 6

class MyStuff(BaseModel):
    dept: DeptNumber

ms = MyStuff(dept='123456')

The Constrained* classes are briefly mentioned in the Strict Types section of the docs. It is defined in pydantic/types.py and as you can see is basically the same as constr:

class ConstrainedStr(str):
    strip_whitespace = False
    to_lower = False
    min_length: OptionalInt = None
    max_length: OptionalInt = None
    curtail_length: OptionalInt = None
    regex: Optional[Pattern[str]] = None
    strict = False

    ...

Validation works the same:

Traceback (most recent call last):
  File "test-2.py", line 13, in <module>
    ms = MyStuff(dept='123456789')
  File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for MyStuff
dept
  ensure this value has at most 6 characters (type=value_error.any_str.max_length; limit_value=6)
Answered By: Gino Mempin

Having sought a solution for this recently, and stumbled upon serveral parallel discussions and then raised (and closed) a bug report with pylance, and raised (and now closed) a feature request with pydantic, I’ve come to the conclusion that the cleanest and most lightweight way to deal with this is here.

Where currently I (Windows 10, Python 3.10.8, Pydantic 1.10.4, Pylance 2023.2.10, VSCode 1.75.0, in a venv) get a linter error with the following:

import pydantic

class MyClass(pydantic.BaseModel):
    myvar: pydantic.confloat(gt=0, lt=1)

x = MyClass(myvar=0.1)

I can make it go away like this:

from typing import Annotated
import pydantic

class MyClass(pydantic.BaseModel):
    myvar: Annotated[float, pydantic.confloat(gt=0, lt=1)]

x = MyClass(myvar=0.1)

It also sounds like Pydantic V2 will bring a more fluent version of this to the table, which is pretty exciting.

Answered By: Vin
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.