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
?
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.
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)
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.
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
?
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.
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)
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.