Python Admin

Guia Completo de Variáveis de Ambiente em Python: python-dotenv e pydantic-settings Já leu

Por que Variáveis de Ambiente Importam Variáveis de ambiente são valores configuráveis que seu programa lê do sistema operacional, sem que precisem estar hardcoded no código-fonte. Isso é fundamental para segurança, portabilidade e manutenção. Imagine que sua aplicação precisa conectar a um banco de dados: você não quer que a senha apareça no repositório Git. Da mesma forma, URLs de APIs, chaves de autenticação e configurações específicas de cada ambiente (desenvolvimento, teste, produção) mudam constantemente. A maioria das linguagens modernas oferece acesso direto a variáveis de ambiente através de bibliotecas padrão. Em Python, você pode acessá-las via . No entanto, gerenciar dezenas de variáveis manualmente é tedioso e propenso a erros. É aí que entram as ferramentas: python-dotenv simplifica o carregamento a partir de arquivos locais, enquanto pydantic-settings adiciona validação tipada e estruturada. Entendendo python-dotenv Como Funciona O python-dotenv lê um arquivo chamado (por convenção) e carrega cada linha como uma variável de ambiente. O formato é simples: . Quando

Por que Variáveis de Ambiente Importam

Variáveis de ambiente são valores configuráveis que seu programa lê do sistema operacional, sem que precisem estar hardcoded no código-fonte. Isso é fundamental para segurança, portabilidade e manutenção. Imagine que sua aplicação precisa conectar a um banco de dados: você não quer que a senha apareça no repositório Git. Da mesma forma, URLs de APIs, chaves de autenticação e configurações específicas de cada ambiente (desenvolvimento, teste, produção) mudam constantemente.

A maioria das linguagens modernas oferece acesso direto a variáveis de ambiente através de bibliotecas padrão. Em Python, você pode acessá-las via os.environ. No entanto, gerenciar dezenas de variáveis manualmente é tedioso e propenso a erros. É aí que entram as ferramentas: python-dotenv simplifica o carregamento a partir de arquivos locais, enquanto pydantic-settings adiciona validação tipada e estruturada.

Entendendo python-dotenv

Como Funciona

O python-dotenv lê um arquivo chamado .env (por convenção) e carrega cada linha como uma variável de ambiente. O formato é simples: CHAVE=valor. Quando você chama load_dotenv(), a biblioteca injeta essas variáveis no dicionário os.environ, tornando-as acessíveis em qualquer parte do código.

A ideia é clara: você cria um arquivo .env localmente (nunca commitado no Git), o desenvolvimento funciona naturalmente, e em produção as variáveis vêm do sistema operacional ou de um gerenciador de secrets.

Instalação e Exemplo Básico

pip install python-dotenv

Crie um arquivo .env no raiz do projeto:

DATABASE_URL=postgresql://user:password@localhost/mydb
API_KEY=seu_token_secreto_aqui
DEBUG=True
LOG_LEVEL=INFO

Agora, no seu código Python:

import os
from dotenv import load_dotenv

# Carrega as variáveis do arquivo .env
load_dotenv()

# Acessa as variáveis como strings
db_url = os.getenv('DATABASE_URL')
api_key = os.getenv('API_KEY')
debug = os.getenv('DEBUG')  # Nota: isso é uma string 'True', não booleano

print(f"Database: {db_url}")
print(f"API Key: {api_key}")
print(f"Debug mode: {debug}")

Melhorando com Valores Padrão e Caminho Customizado

O método os.getenv() aceita um segundo argumento como valor padrão, útil quando uma variável é opcional:

from dotenv import load_dotenv
import os

# Carregar de um arquivo específico (não obrigatório ser .env)
load_dotenv(dotenv_path='config/.env.local')

# Com valor padrão
timeout = os.getenv('REQUEST_TIMEOUT', '30')
redis_host = os.getenv('REDIS_HOST', 'localhost')

print(f"Timeout: {timeout}")
print(f"Redis Host: {redis_host}")

A limitação do python-dotenv é que tudo vem como string. Se você precisa de um inteiro ou booleano, deve fazer casting manual. Aqui entra o pydantic-settings.

Estrutura Profissional com pydantic-settings

Por Que Usar Pydantic-Settings

pydantic-settings combina o carregamento de variáveis de ambiente com validação de tipos rigorosa. Você define uma classe que descreve a forma de suas configurações — quais campos são obrigatórios, seus tipos exatos, valores padrão — e a biblioteca se encarrega do resto: lê as variáveis, converte para os tipos corretos, valida e lança exceções se algo estiver errado.

Isso evita bugs silenciosos como tentar fazer operações matemáticas em uma string quando esperava um inteiro, ou deixar passar uma URL inválida.

Instalação e Primeiro Exemplo

pip install pydantic-settings pydantic

Crie um arquivo .env:

DATABASE_URL=postgresql://user:senha@localhost/banco
DATABASE_POOL_SIZE=10
API_KEY=chave_super_secreta
DEBUG=false
REDIS_ENABLED=true
LOG_LEVEL=INFO
MAX_WORKERS=4

Agora, defina suas configurações em config.py:

from pydantic_settings import BaseSettings
from pydantic import Field

class Settings(BaseSettings):
    # Campo obrigatório
    database_url: str

    # Com valores padrão
    api_key: str = "chave_padrao"
    debug: bool = False

    # Campo com alias (lê DATABASE_POOL_SIZE do .env)
    database_pool_size: int = Field(default=5, alias='DATABASE_POOL_SIZE')

    redis_enabled: bool = True
    log_level: str = "WARNING"
    max_workers: int = 2

    class Config:
        # Arquivo .env a ser lido
        env_file = '.env'
        env_file_encoding = 'utf-8'
        # Ignora variáveis de ambiente não declaradas
        extra = 'ignore'

# Instancia uma única vez (padrão singleton é comum)
settings = Settings()

Use-o em qualquer lugar do código:

from config import settings

print(f"Database: {settings.database_url}")
print(f"Pool size: {settings.database_pool_size}")  # Já é int
print(f"Debug: {settings.debug}")  # Já é bool
print(f"Workers: {settings.max_workers}")  # Já é int

# Operações seguras
total_connections = settings.database_pool_size * 2

Validação e Tratamento de Erros

A força do pydantic aparece quando há erros. Se uma variável obrigatória estiver faltando ou tem tipo errado:

# .env com erro
# DEBUG=not_a_boolean
# DATABASE_POOL_SIZE=abc

from config import Settings

try:
    settings = Settings()
except Exception as e:
    print(f"Erro ao carregar configurações: {e}")

O pydantic lança um erro descritivo listando exatamente qual campo falhou e por quê. Isso economiza horas de debug.

Separar Ambientes (Desenvolvimento, Teste, Produção)

Uma abordagem profissional é ter múltiplos arquivos .env:

.env.example          # Versionado no Git (template)
.env.development      # Local, não versionado
.env.test            # Para testes, pode ser versionado
.env.production       # Em produção, variáveis vêm do sistema

Carregue dinamicamente:

import os
from pydantic_settings import BaseSettings
from dotenv import load_dotenv

# Define qual ambiente está ativo
ENVIRONMENT = os.getenv('ENVIRONMENT', 'development')

# Carrega o arquivo apropriado
load_dotenv(f'.env.{ENVIRONMENT}')

class Settings(BaseSettings):
    database_url: str
    api_key: str
    debug: bool = False

    class Config:
        env_file = f'.env.{ENVIRONMENT}'
        env_file_encoding = 'utf-8'

settings = Settings()

Execute com: ENVIRONMENT=production python seu_app.py

Padrões Avançados e Boas Práticas

Variáveis Aninhadas e Tipos Complexos

Pydantic permite estruturas mais sofisticadas. Por exemplo, se você tem várias instâncias de banco de dados:

from pydantic_settings import BaseSettings
from pydantic import BaseModel

class DatabaseConfig(BaseModel):
    url: str
    pool_size: int = 5
    timeout: int = 30

class Settings(BaseSettings):
    primary_db: DatabaseConfig
    replica_db: DatabaseConfig
    log_level: str = "INFO"

    class Config:
        env_file = '.env'
        # Permite parsing de variáveis com prefixo
        env_nested_delimiter = '__'

settings = Settings()

Com arquivo .env:

PRIMARY_DB__URL=postgresql://localhost/main
PRIMARY_DB__POOL_SIZE=20
REPLICA_DB__URL=postgresql://replica-host/main
REPLICA_DB__POOL_SIZE=10

Validação Customizada

Você pode adicionar validadores que rodam automaticamente:

from pydantic import field_validator
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    api_key: str
    max_workers: int

    @field_validator('api_key')
    @classmethod
    def validate_api_key(cls, v):
        if len(v) < 10:
            raise ValueError('API key deve ter no mínimo 10 caracteres')
        return v

    @field_validator('max_workers')
    @classmethod
    def validate_workers(cls, v):
        if v < 1 or v > 100:
            raise ValueError('max_workers deve estar entre 1 e 100')
        return v

    class Config:
        env_file = '.env'

settings = Settings()

Logging de Configurações (com Segurança)

Ao debugar, você quer ver quais valores foram carregados, mas nunca deve logar senhas:

import logging
from pydantic_settings import BaseSettings

logger = logging.getLogger(__name__)

class Settings(BaseSettings):
    database_url: str
    api_key: str
    debug: bool = False

    class Config:
        env_file = '.env'

    def log_loaded_config(self):
        """Log seguro das configurações carregadas"""
        logger.info(f"Debug mode: {self.debug}")
        logger.info(f"Database host: {self.database_url.split('@')[1] if '@' in self.database_url else 'unknown'}")
        # Nunca loga api_key inteira
        logger.info(f"API key loaded: {'*' * len(self.api_key) if self.api_key else 'None'}")

settings = Settings()
settings.log_loaded_config()

Comparação: python-dotenv vs pydantic-settings

Aspecto python-dotenv pydantic-settings
Complexidade Simples Moderada
Validação de Tipos Nenhuma Rigorosa
Casting Automático Não Sim
Estruturas Complexas Não suporta Suporta
Curva de Aprendizado Muito baixa Média
Projeto Pequeno Perfeito Overkill
Projeto Corporativo Insuficiente Ideal

Use python-dotenv se você tem poucos parâmetros e não precisa de validação. Use pydantic-settings se sua aplicação cresce ou trabalha em equipe.

Conclusão

Aprendemos que variáveis de ambiente são essenciais para separar configuração de código. python-dotenv oferece uma forma simples e direta de carregar pares chave-valor de um arquivo .env, ideal para prototipagem. pydantic-settings eleva isso a um nível profissional, adicionando validação tipada, tratamento de erros robusto e suporte a estruturas complexas — é o padrão em aplicações sérias e equipes.

A escolha não é binária: você pode começar com python-dotenv e evoluir para pydantic-settings conforme o projeto cresce. O importante é nunca hardcodear secrets ou configurações dependentes de ambiente diretamente no código.

Referências


Artigos relacionados