Configurações espalhadas em chamadas a os.getenv() produzem valores sem validação e erros tardios. pydantic-settings reúne as opções em um modelo tipado, converte valores de ambiente e interrompe a inicialização quando um campo obrigatório está ausente ou inválido.
Ele complementa o uso geral de Pydantic em Python, mas é distribuído como pacote separado.
Criar o modelo de configuração
python -m pip install pydantic-settings
from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
app_name: str = "Minha API"
debug: bool = False
database_url: str
api_key: SecretStr
workers: int = 2
model_config = SettingsConfigDict(
env_file=".env",
env_prefix="APP_",
extra="ignore",
)
settings = Settings()
As variáveis esperadas são APP_DATABASE_URL, APP_API_KEY e assim por diante. Strings como false e 4 são convertidas para os tipos declarados; valores inválidos geram erro de validação.
Um .env local pode conter:
APP_DEBUG=false
APP_DATABASE_URL=postgresql://localhost/app
APP_API_KEY=valor-local
APP_WORKERS=4
Adicione .env ao .gitignore e versione .env.example apenas com nomes e valores não sensíveis. SecretStr reduz exposição acidental em representações, mas não criptografa nem substitui um gerenciador de segredos.
Separar configuração por ambiente
Prefira o mesmo modelo e diferentes fontes de valores. Evite classes ProductionSettings e DevelopmentSettings cheias de regras divergentes quando apenas os valores mudam. Em produção, injete variáveis pelo serviço de deploy ou cofre de segredos.
A documentação oficial de Pydantic Settings detalha ordem das fontes, nomes aninhados, CLI e customização.
Usar sem criar globais difíceis de testar
Em uma API, carregue uma vez em um ponto explícito e injete o objeto onde necessário. No FastAPI, Depends permite substituir a configuração em testes. Para testes unitários, instancie Settings com valores controlados ou use monkeypatch no ambiente antes da criação.
Checklist de segurança
- Nunca registre o valor de tokens e senhas.
- Falhe na inicialização se uma opção obrigatória estiver ausente.
- Valide limites como quantidade de workers e timeouts.
- Não dependa do diretório atual sem documentar onde
.envé lido. - Mantenha configuração separada de dados de negócio.
- Faça rotação de segredos fora do código.
Configuração tipada melhora mensagens de erro e torna as dependências operacionais visíveis. O benefício mais importante é descobrir uma implantação incorreta antes que ela processe requisições.
Entender a prioridade das fontes
Por padrão, valores passados ao construtor têm prioridade sobre variáveis de ambiente, que prevalecem sobre o arquivo dotenv e, depois, sobre arquivos de secrets. Os valores padrão do modelo entram por último. Essa ordem permite sobrescrever uma opção local em um teste sem alterar o ambiente:
settings_teste = Settings(
database_url="sqlite://",
api_key="chave-de-teste",
workers=1,
)
Não presuma que .env substitui uma variável já exportada no processo. Em geral, a variável de ambiente vence. Isso é desejável em containers e pipelines, mas pode confundir durante depuração. Registre quais opções não sensíveis foram selecionadas e documente a origem esperada, nunca o conteúdo de credenciais.
O carregamento de dotenv não procura recursivamente diretórios pais. O caminho relativo depende do diretório de trabalho do processo. Em serviços, considere formar um caminho conhecido a partir da localização da aplicação ou deixar a plataforma fornecer todas as variáveis.
Campos aninhados e valores complexos
Listas, dicionários e submodelos podem vir de strings JSON. Para sobrescrever partes de uma estrutura por variáveis separadas, configure um delimitador:
from pydantic import BaseModel
class Banco(BaseModel):
host: str
port: int = 5432
pool_size: int = 10
class Settings(BaseSettings):
database: Banco
model_config = SettingsConfigDict(
env_prefix="APP_",
env_nested_delimiter="__",
)
Com essa configuração, APP_DATABASE__HOST=db.internal e APP_DATABASE__POOL_SIZE=20 alimentam campos internos. O nome exato e a sensibilidade a maiúsculas variam conforme configuração e sistema operacional, então padronize nomes e teste no mesmo tipo de ambiente usado em produção.
Para regras adicionais, use validadores do Pydantic. Um timeout pode exigir valor positivo; um ambiente de produção pode proibir debug=True. Mantenha validações determinísticas e sem chamadas de rede. A criação de settings deve relatar configuração inválida, não depender da disponibilidade momentânea de outro serviço.
Aliases, prefixos e nomes legados
env_prefix reduz colisões quando várias aplicações compartilham um ambiente. Aliases permitem integrar nomes existentes, mas distinguem alias de validação, serialização e nome de variável. Antes de adotar uma opção, confirme seu efeito na versão instalada de pydantic-settings e escreva um teste com o nome real.
Evite manter indefinidamente vários nomes para o mesmo campo. Durante uma migração, aceite o nome antigo de modo explícito, avise operadores e defina quando ele será removido. Configuração silenciosamente ambígua é difícil de auditar.
Para ambientes em que maiúsculas e minúsculas são distintas, trate a grafia como parte do contrato operacional. Não dependa de comportamento específico do Windows se o deploy roda em Linux.
Segredos e representação segura
SecretStr oculta o conteúdo em repr() e na serialização comum, mas o valor continua na memória e pode ser acessado com get_secret_value(). Passe-o apenas ao cliente que precisa da credencial:
cliente = ClienteExterno(
token=settings.api_key.get_secret_value()
)
Não transforme o objeto inteiro em dicionário para logs. Uma lista explícita de campos públicos é mais segura. Arquivos de secrets montados por orquestradores podem ser usados como fonte, mas permissões, rotação e persistência pertencem à plataforma. Pydantic valida e entrega o valor; não administra seu ciclo de vida.
Se um segredo é opcional apenas em desenvolvimento, modele essa decisão com cuidado. Um campo str | None pode permitir que produção suba sem proteção. Muitas equipes preferem campo obrigatório e fornecem credencial fictícia somente em testes isolados.
Carregar uma vez sem esconder dependências
Construir settings repetidamente relê fontes e refaz validação. Em uma aplicação longa, carregue uma vez ou use cache em uma função de composição:
from functools import lru_cache
@lru_cache
def get_settings() -> Settings:
return Settings()
Consumidores ainda devem receber Settings ou uma seção menor por parâmetro. Importar um singleton global em todos os módulos dificulta testes e torna dependências invisíveis. No teste, limpe o cache depois de alterar o ambiente ou, melhor, passe uma instância criada diretamente.
Bibliotecas reutilizáveis não deveriam ler variáveis de ambiente ao serem importadas. Receba opções do aplicativo hospedeiro. Isso evita efeitos colaterais e permite que dois clientes da mesma biblioteca usem configurações diferentes.
Erros de validação operacionais
Capture ValidationError no ponto de entrada para apresentar uma mensagem legível e encerrar com código diferente de zero. Não continue com configuração parcial. Ao formatar o erro, tenha cuidado: entradas inválidas podem conter dados sensíveis. Prefira nomes de campos e tipos esperados a despejar todo o valor recebido.
Teste pelo menos ausência de campos obrigatórios, conversões válidas, valores fora dos limites e precedência. Use monkeypatch.setenv() e monkeypatch.delenv() no pytest para isolar o ambiente, instanciando o modelo somente depois. O processo de testes ou a máquina do desenvolvedor pode conter variáveis inesperadas, portanto remova explicitamente as que influenciam o cenário.
Por fim, trate configuração como uma interface versionada entre código e operação. Renomear uma variável pode quebrar o deploy mesmo que todos os testes unitários passem. Documente nomes, tipos, obrigatoriedade, exemplos seguros e política de migração junto do projeto.