Strictness with BaseSettings
I fail to be able to use strict parsing with BaseSettings. I haven't found this behavior documented in the docs whatsoever. No mention of strict/strictness among the issues here either as far as I could see.
Given these definitions,
import json as json
from pydantic import BaseModel, StrictBool, StrictInt, ValidationError
from pydantic_settings import BaseSettings
json_doc = """
{
"mybool": 0
}
"""
class Model(BaseModel):
mybool: bool
class Settings(BaseSettings):
mybool: bool
class StrictSettings(BaseSettings):
mybool: StrictBool
we will get ValidationErrors with Model but not with Settings:
try:
data = Model.model_validate_json(json_doc, strict=True)
except ValidationError as e:
assert e.error_count() == 1
data = Settings.model_validate_json(json_doc, strict=True)
Same with model_validate.
Even enabling strict on model_config doesn't have an effect
Settings.model_config["strict"] = True
obj = json.loads(json_doc)
try:
data = Model.model_validate(obj, strict=True)
except ValidationError as e:
assert e.error_count() == 1
data = Settings.model_validate(obj, strict=True)
Using Strict* types changes things but that's not what I am looking for:
try:
data = StrictSettings.model_validate(obj)
except ValidationError as e:
assert e.error_count() == 1
My use case is that I want CLI arguments to be parsed normally and config files to be parsed strictly.
And I use yaml config files so that will be another problem with SettingsSource configuration but let's not get ahead of ourselves.
The more I think about the more it seems that model_validate* should not be used with BaseSettings. All examples construct BaseSettings instances by relying on the various sources. It is weird however why model_validate* methods sit there with options like strict: bool waiting to be misused. I only resorted to this way of doing it so that I can produce simplified examples. As I mentioned my use case revolves around CLI argument and config file parsing. I had gotten here from this test of mine where I tried to hack in strict parsing just for config files:
from pydantic_settings import BaseSettings, JsonConfigSettingsSource, PydanticBaseSettingsSource
class Settings(BaseSettings):
@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
strict = True
json = True
if strict:
# Doesn't quite help
StrictBase = type("StrictBase", (settings_cls,), {})
StrictBase.model_config["strict"] = True
# or this, since I noticed altering model_config members late don't work
StrictBase = type(
"StrictBase", (settings_cls,), {"model_config": SettingsConfigDict(strict=True)}
)
base = StrictBase
else:
base = settings_cls
if json:
return (JsonConfigSettingsSource(base, json_file="myconfig.json"),)
else:
return (YamlConfigSettingsSource(base, yaml_file="myconfig.yaml"),)
Thanks @pycaw for this issue.
pydantic-settings overrides the BaseModel.__init__ and for some reason, in model_validate and model_validate_json function call the init function of BaseSettings will be called with the values, and it will be passed to the BaseModel.__init__. So, when you call Settings.model_validate(obj, strict=True) it actually will be call BaseModel.__init__(**obj). That's why pydantic-settings ignores the strict flag.
@Viicos Do you have more to add here? is there a way to fix the problem without a refactor/breaking change?
My recently created issue about problem X: When using CLI parsing, how to apply strict parsing for other sources?
Unfortunately the custom __init__() from BaseSettings isn't playing well (Pydantic issue tracking this: https://github.com/pydantic/pydantic/issues/10062).
Even enabling
strictonmodel_configdoesn't have an effect
Configuration isn't meant to be mutated directly (https://github.com/pydantic/pydantic/issues/12372), it will work if you set strict=True at model definition.