EnvlessSettingsSource is a pydantic-settings source that loads from Envless. Your BaseSettings class then validates every value against its own field types, so a port is an int and a URL is checked, whatever Envless stored.
from pydantic import PostgresDsn
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
from envless_env.pydantic import EnvlessSettingsSource
class Settings(BaseSettings):
database_url: PostgresDsn
port: int = 8000
debug: bool = False
@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, ...]:
return (init_settings, env_settings, EnvlessSettingsSource(settings_cls))
settings = Settings() # type: ignore[call-arg]Type checkers cannot see that a source fills the required fields, so constructing the class carries the ignore pydantic-settings projects usually need. Building the source loads, exactly as load() would, and raises LoadError if it cannot. Variable names match fields the way they do for environment variables, so DATABASE_URL fills database_url, and env_prefix, case_sensitive, nested delimiters, aliases, env_ignore_empty and env_parse_none_str from your model config all apply.
Which source wins
pydantic-settings takes each field from the first source in the tuple that has it. Placed after env_settings, as above, a variable already set in the process environment wins, which matches how the loader treats os.environ. Place it before env_settings and Envless wins instead.
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
from envless_env.pydantic import EnvlessSettingsSource
class Settings(BaseSettings):
port: int = 8000
@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, ...]:
return (init_settings, EnvlessSettingsSource(settings_cls), env_settings)Leave dotenv_settings out once your variables live in Envless, so a stale .env file on someone's laptop cannot quietly override them.
The source reads every variable, including those marked server, which is what settings are for. Keep the settings object on the server, and use the client view for anything a browser receives.