Variáveis de ambiente são valores dinâmicos que afetam o comportamento de processos em execução em um sistema operacional. No desenvolvimento de software, elas são essenciais para configurar aplicações sem expor informações sensíveis no código-fonte, como chaves de API, senhas de banco de dados ou URLs de serviços. Em Python, o módulo os.environ fornece acesso direto a essas variáveis, enquanto bibliotecas como python-dotenv facilitam o gerenciamento delas em ambientes de desenvolvimento.

Nesta aula, você aprenderá a ler, definir e manipular variáveis de ambiente em Python, além de boas práticas para lidar com segredos e configurações em diferentes ambientes (desenvolvimento, teste, produção). Ao final, você será capaz de criar aplicações mais seguras e portáveis.

os.environ

O módulo os em Python fornece uma interface para interagir com o sistema operacional. A variável os.environ é um dicionário (na verdade, um objeto os._Environ) que contém as variáveis de ambiente do processo atual. Você pode acessá-las como em um dicionário comum, mas com algumas peculiaridades: as chaves e valores são sempre strings, e a leitura de uma chave inexistente levanta KeyError.

Para evitar erros, é comum usar o método get() que retorna um valor padrão se a chave não existir. Também é possível definir ou modificar variáveis de ambiente atribuindo valores a chaves, mas é importante lembrar que essas alterações afetam apenas o processo atual e seus filhos, não persistindo após o término do script.

import os

# Acessando uma variável de ambiente existente
home = os.environ.get('HOME')
print(f'Home: {home}')

# Acessando com valor padrão
api_key = os.environ.get('API_KEY', 'default-key')
print(f'API Key: {api_key}')

# Definindo uma nova variável de ambiente
os.environ['MY_VAR'] = 'hello'
print(os.environ['MY_VAR'])

# Verificando se uma variável existe
if 'PATH' in os.environ:
    print('PATH está definida')

Uma prática comum é usar os.getenv(), que é um atalho para os.environ.get(). Ambos são equivalentes, mas os.getenv() é mais conciso.

# Usando os.getenv()
port = os.getenv('PORT', '8000')
print(f'Porta: {port}')

É importante notar que as variáveis de ambiente são herdadas de processos pai para processos filho. Por exemplo, se você definir uma variável em um terminal e executar um script Python, o script verá essa variável. No entanto, se você definir uma variável dentro do script, ela não afetará o terminal após a execução.

python-dotenv (visão geral)

O python-dotenv é uma biblioteca que carrega variáveis de ambiente de um arquivo .env para o ambiente do processo. Isso é extremamente útil em desenvolvimento, pois permite manter configurações locais sem poluir o ambiente do sistema ou o código-fonte. O arquivo .env normalmente contém pares CHAVE=valor em texto simples, e é adicionado ao .gitignore para não ser versionado.

Para instalar, use pip install python-dotenv. O uso básico é chamar load_dotenv() no início do script. A biblioteca procura por um arquivo chamado .env no diretório atual ou em um caminho especificado.

# Exemplo de .env
# API_KEY=abc123
# DEBUG=true

from dotenv import load_dotenv
import os

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

api_key = os.getenv('API_KEY')
debug = os.getenv('DEBUG', 'false')
print(f'API Key: {api_key}')
print(f'Debug: {debug}')

Além do load_dotenv(), você pode especificar o caminho do arquivo com load_dotenv('/caminho/para/.env'). A biblioteca também permite sobrescrever variáveis existentes com override=True, mas o padrão é não sobrescrever.

Em produção, muitas vezes as variáveis de ambiente são definidas diretamente no sistema (por exemplo, em um serviço de nuvem ou no Docker), e o python-dotenv não é necessário. Portanto, é comum carregar o .env apenas em desenvolvimento, por exemplo, condicionando ao valor de DEBUG ou usando um arquivo de configuração separado.

Segredos

Segredos são informações sensíveis como senhas, chaves de API, tokens de autenticação e strings de conexão. Nunca devem ser commitados no controle de versão. O uso de variáveis de ambiente é uma prática recomendada para gerenciar segredos, pois mantém o código limpo e permite que cada ambiente tenha seus próprios valores.

Ao trabalhar com segredos, é crucial seguir algumas regras: não logar segredos, não incluí-los em mensagens de erro e não expô-los em interfaces de usuário. Além disso, é importante usar bibliotecas de gerenciamento de segredos (como o AWS Secrets Manager ou o HashiCorp Vault) em ambientes de produção, mas para aplicações simples, variáveis de ambiente são suficientes.

Um padrão comum é ter um arquivo .env.example versionado, que lista todas as variáveis necessárias com valores fictícios ou vazios, para que outros desenvolvedores saibam o que configurar. O arquivo real .env é ignorado pelo Git.

# Exemplo de uso de segredos
import os
from dotenv import load_dotenv

load_dotenv()

# Suponha que DATABASE_URL esteja definido no .env
DATABASE_URL = os.getenv('DATABASE_URL')
if not DATABASE_URL:
    raise ValueError('DATABASE_URL não está definida')

# Conectar ao banco (exemplo fictício)
# db.connect(DATABASE_URL)

Lembre-se de que, ao definir variáveis de ambiente em sistemas operacionais, é possível usar o comando export no Linux/macOS ou set no Windows, mas isso é temporário. Para persistir, você pode adicionar ao arquivo de perfil do shell ou usar ferramentas de orquestração.

Boas práticas

1. **Use variáveis de ambiente para configuração**: Evite hard-coded de valores que variam entre ambientes (URLs, portas, chaves). Isso torna a aplicação portátil e fácil de configurar.

2. **Nunca commite segredos**: Adicione .env ao .gitignore e forneça um .env.example para documentar as variáveis necessárias.

3. **Forneça valores padrão seguros**: Ao usar os.getenv(), forneça um valor padrão que seja seguro para desenvolvimento, mas que falhe em produção se não for definido (por exemplo, lançando uma exceção).

4. **Valide a presença de variáveis críticas**: No início da aplicação, verifique se as variáveis essenciais estão definidas e lance um erro claro se não estiverem.

5. **Use tipos corretos**: Variáveis de ambiente são strings. Converta para o tipo necessário (int, bool, etc.) com validação adequada.

# Exemplo de validação e conversão
import os

def get_bool_env(name, default=False):
    value = os.getenv(name)
    if value is None:
        return default
    return value.lower() in ('true', '1', 'yes')

DEBUG = get_bool_env('DEBUG')
PORT = int(os.getenv('PORT', '8000'))

6. **Documente as variáveis**: Mantenha uma lista atualizada de variáveis de ambiente no README ou em um arquivo de documentação.

7. **Use ferramentas de gerenciamento de segredos em produção**: Para aplicações maiores, considere serviços como AWS Secrets Manager, Azure Key Vault ou HashiCorp Vault.

Observações finais

Dominar o uso de variáveis de ambiente é fundamental para qualquer desenvolvedor Python. Elas permitem separar configuração de código, aumentando a segurança e a flexibilidade da aplicação. Ao combinar os.environ com python-dotenv, você pode criar um fluxo de trabalho eficiente tanto em desenvolvimento quanto em produção. Lembre-se sempre de aplicar as boas práticas discutidas para evitar vazamentos de informações sensíveis.

Referências

Exercícios

  1. Escreva um script Python que leia as variáveis de ambiente USER e HOME e imprima uma mensagem como "Usuário: fulano, Home: /home/fulano". Use os.environ.get() com valores padrão.

✓ Resposta:
import os

user = os.environ.get('USER', 'desconhecido')
home = os.environ.get('HOME', '/tmp')
print(f'Usuário: {user}, Home: {home}')
  • Crie um arquivo .env com as variáveis DB_HOST, DB_PORT e DB_NAME. Escreva um script usando python-dotenv que carregue essas variáveis e imprima uma string de conexão fictícia.
  • ✓ Resposta:
    # .env
    # DB_HOST=localhost
    # DB_PORT=5432
    # DB_NAME=meubanco
    
    from dotenv import load_dotenv
    import os
    
    load_dotenv()
    host = os.getenv('DB_HOST')
    port = os.getenv('DB_PORT')
    name = os.getenv('DB_NAME')
    print(f'postgresql://{host}:{port}/{name}')
    
  • Implemente uma função get_required_env(name) que retorna o valor da variável de ambiente ou lança uma exceção ValueError com uma mensagem clara se não estiver definida.
  • ✓ Resposta:
    import os
    
    def get_required_env(name):
        value = os.environ.get(name)
        if value is None:
            raise ValueError(f'A variável de ambiente {name} não está definida.')
        return value
    
    # Exemplo de uso
    try:
        api_key = get_required_env('API_KEY')
        print(api_key)
    except ValueError as e:
        print(e)
    
  • Dado o seguinte trecho de código, identifique e corrija o erro: print(os.environ['PORT']) caso a variável PORT não exista. Reescreva usando get() com valor padrão '8080'.
  • ✓ Resposta: O erro é que os.environ['PORT'] levanta KeyError se a variável não existir. A correção é usar os.environ.get('PORT', '8080').
    import os
    port = os.environ.get('PORT', '8080')
    print(port)
    
  • Escreva um script que defina uma variável de ambiente chamada APP_MODE com o valor 'development' se ela não existir, e depois imprima o valor. Use os.environ.setdefault() (ou uma lógica equivalente).
  • ✓ Resposta:
    import os
    
    os.environ.setdefault('APP_MODE', 'development')
    print(os.environ['APP_MODE'])