17

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?

Robert
  • 7,394
  • 40
  • 45
  • 64

3 Answers3

27

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.

On Pydantic v1 note 1, 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)

note 1:
I haven't gotten a chance to work with Pydantic v2 yet, so I don't know if it still works on that version.
I can only guarantee that the answer works on v1.

Gino Mempin
  • 25,369
  • 29
  • 96
  • 135
  • 1
    Sadly this still seems to result in a general type issue with pylance. :/ – Monkeyphant Jul 18 '22 at 14:35
  • 2
    @Monkeyphant That's unfortunate. I don't use Pylance, and mypy != pylance, and the original question was for mypy, no mention of pylance. You might want to ask a separate question for pylance. – Gino Mempin Jul 18 '22 at 21:26
  • 1
    mypy is still complaining ;`Argument "dept" to "MyStuff" has incompatible type "str"; expected "DeptNumber" [arg-type]`. Do you a specific mypy configuration? – guhur Nov 08 '22 at 14:14
  • @guhur I don't have a copy of the env I used for this answer, so I don't remember. But I reproduced the issue from the same code from the question, and applied my answer, and it still works. Using Python 3.8/3.9/3.10, pydantic 1.10.2, mypy 0.990 (with all default configs). I simply ran `mypy test.py` where `test.py` contains the code from this answer. Tried with `mypy --strict` and still no errors. Also doesn't show any error when enabling mypy in VS Code. – Gino Mempin Nov 10 '22 at 09:24
  • 1
    On `pyproject.toml`, I had to use `init_typed=False` to make it work in the section `[too.pydantic-mypy]`. – guhur Nov 10 '22 at 10:20
  • @guhur Huh, thanks for the info. I'm using [setup.cfg to enable the pydantic.mypy plugin](https://pydantic-docs.helpmanual.io/mypy_plugin/#enabling-the-plugin), and all I have is `[mypy] > plugins = pydantic.mypy` – Gino Mempin Nov 10 '22 at 10:36
  • 1
    As of 5 Feb 2023, this solution [works for me](https://github.com/microsoft/pylance-release/issues/3912) with Pylance. – Vin Feb 04 '23 at 20:06
  • 2
    This is the nicest workaround I've found for this problem yet, it's quite clean: https://github.com/koxudaxi/datamodel-code-generator/issues/492#issue-965007077 – Vin Feb 04 '23 at 22:49
6

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.

Raylian
  • 163
  • 4
  • The `Field` function also allows a number of constraints to be added to the field https://pydantic-docs.helpmanual.io/usage/schema/#field-customisation – Matti Lyra Dec 06 '21 at 09:36
  • 5
    This does *not* work with `strip_whitespace=True`. – Paul P Sep 29 '22 at 10:50
  • The "constr" is the correct way. Field don't works as expected. – Eduardo Lucio Dec 28 '22 at 17:14
  • Specifically for `constr`, [this](https://stackoverflow.com/a/67871116/7955271) other answer appears to offer a more complete (if less "idiomatic") solution. Have tried with Pylance (but not mypy) and it works. – Vin Feb 04 '23 at 20:02
3

In Pydantic V2, you can use the StringConstraints type along with Annotated:

from pydantic import stringConstraints
from typing import Annotated

DeptNumber = Annotated[
    str,
    StringConstraints(
        min_length=6,
        max_length=6,
    )
] 

Annotated makes sure that DeptNumber is a str type, while adding some functionality on top of it.

Nico Ekkart
  • 33
  • 1
  • 5