Validação e Formatação de Números de Telefone e Documentos em Python: Guia Prático com Regex e Pydantic

Validação e Formatação de Números de Telefone e Documentos em Python: Guia Prático com Regex e Pydantic

A entrada inconsistente de dados é um dos desafios mais frequentes no desenvolvimento de sistemas backend, APIs e pipelines de engenharia de dados. Telefones inseridos em múltiplos formatos — com parênteses, traços, espaços, códigos de país ausentes ou prefixos de protocolo — e documentos cadastrais sem padronização comprometem a integridade do banco de dados e provocam falhas silenciosas em integrações com APIs de SMS, WhatsApp ou serviços de autenticação.

Para solucionar esse problema de forma profissional, a abordagem recomendada combina duas etapas fundamentais: a sanitização prévia por expressões regulares (re) para padronizar caracteres e a validação estruturada de modelos com Pydantic para garantir tipos, obrigatoriedade e integridade dos campos.

1. Sanitização Prévia: Limpeza de Caracteres Especiais

Antes de verificar se um número de telefone ou documento atende aos critérios formais de validação, é essencial aplicar um processo de limpeza (normalization). Usuários frequentemente colam dados contendo formatações visuais como +55 (11) 98765-4321 ou prefixos vindos de links como tel:+1-415-555-2671.

Em Python, a biblioteca nativa re permite remover todos os caracteres não numéricos e prefixos desnecessários em uma única passagem:

import re

def clean_phone_input(raw_phone: str) -> str:
    """
    Remove espaços, traços, pontos, parênteses e prefixos comuns (tel:, call:)
    de uma string de telefone mantendo o sinal de '+' se presente.
    """
    # Remove prefixos de protocolo como 'tel:', 'phone:' ou 'call:'
    cleaned = re.sub(r'^(tel:|phone:|call:)', '', raw_phone, flags=re.IGNORECASE)
    
    # Remove todos os caracteres de formatação: espaços, traços, pontos e parênteses
    cleaned = re.sub(r'[\s\-\.\(\)]', '', cleaned)
    
    return cleaned

# Exemplos de uso:
print(clean_phone_input("tel: +1 (415) 555-2671"))  # Resultado: +14155552671
print(clean_phone_input("(11) 99999-8888"))          # Resultado: 11999998888

Ao isolar a etapa de sanitização, você evita falhas em padrões regex estritos que rejeitariam números válidos apenas pela presença de um parêntese ou espaço em branco.

2. Validação de Telefones com Regex e Padrão E.164

O padrão E.164 (recomendação da ITU-T) é o formato universal aceito por gateways de telecomunicação e APIs de mensagens. Suas regras estabelecem:

  • Início obrigatório com o símbolo +.
  • Código do país de 1 a 3 dígitos (onde o primeiro dígito não pode ser zero).
  • Comprimento total variando entre 7 e 15 dígitos (excluindo o caractere +).
  • Ausência de espaços, traços ou caracteres especiais.

Expressões Regulares por Região e Formato

A tabela abaixo resume os padrões de expressões regulares para validação de telefone testados para Python:

Formato / RegiãoExpressão Regular (Regex)Exemplo Válido
E.164 Internacional^\+[1-9]\d{6,14}$+14155552671
EUA/Canadá (NANP)^\+?1?[\s.\-]?\(?\d{3}\)?[\s.\-]?\d{3}[\s.\-]?\d{4}$(415) 555-2671
Reino Unido (Móvel)^\+?44[\s]?7\d{9}$+44 7911 123456
Índia (Móvel)^\+?91[\s]?[6-9]\d{9}$+91 9876543210
Dígitos Genéricos (Frouxo)^\+?\d{7,15}$+5511987654321

Implementação de Validação em Cascata em Python

Quando sua aplicação recebe contatos de múltiplos países, a melhor prática é aplicar uma validação em cascata que testa formatos específicos e recai sobre um formato genérico:

import re

def validate_phone(phone_number: str) -> dict:
    cleaned = clean_phone_input(phone_number)
    
    patterns = {
        'e164':    r'^\+[1-9]\d{6,14}$',
        'us_nanp': r'^\+?1?\d{10}$',
        'uk':      r'^\+?44\d{10}$',
        'india':   r'^\+?91[6-9]\d{9}$',
        'generic': r'^\+?\d{7,15}$',
    }
    
    for fmt, pattern in patterns.items():
        if re.match(pattern, cleaned):
            return {
                'valid': True,
                'format_detected': fmt,
                'cleaned_number': cleaned
            }
            
    return {
        'valid': False,
        'format_detected': None,
        'cleaned_number': cleaned
    }

# Testes de validação:
print(validate_phone("+1 415-555-2671")) 
# {'valid': True, 'format_detected': 'e164', 'cleaned_number': '+14155552671'}

print(validate_phone("+123")) 
# {'valid': False, 'format_detected': None, 'cleaned_number': '+123'}

3. Validação Estruturada e de Schemas de Documentos com Pydantic

Enquanto o Regex cuida de padrões textuais de strings individuais, a biblioteca Pydantic oferece uma camada robusta de validação para schemas completos de dados. Desenvolvida sobre dicas de tipo (type hints) do Python com núcleo de validação construído em Rust, o Pydantic realiza validação de tipos, coerção automática e emissão de erros detalhados.

Criando Modelos de Dados com BaseModel

Ao estender a classe BaseModel, cada atributo tipado define o contrato do documento recebido. Caso os dados de entrada não estejam em conformidade, o Pydantic lança a exceção ValidationError.

from pydantic import BaseModel, Field, field_validator, ValidationError
import re

class UserDocumentModel(BaseModel):
    user_id: int
    full_name: str = 'Usuário Não Identificado'
    phone_number: str
    document_code: str = Field(..., description="Código do documento do cliente")

    @field_validator('phone_number')
    @classmethod
    def validate_and_clean_phone(cls, value: str) -> str:
        cleaned = re.sub(r'[\s\-\.\(\)]', '', value)
        cleaned = re.sub(r'^(tel:|phone:|call:)', '', cleaned, flags=re.IGNORECASE)
        
        # Garante que atenda ao formato internacional genérico ou E.164
        if not re.match(r'^\+?[1-9]\d{6,14}$', cleaned):
            raise ValueError(f"O número de telefone '{value}' é inválido para o formato E.164.")
        return cleaned

    @field_validator('document_code')
    @classmethod
    def validate_document(cls, value: str) -> str:
        # Sanitização simples de documento
        cleaned_doc = re.sub(r'[^\w]', '', value)
        if len(cleaned_doc) < 5:
            raise ValueError("O documento informado é muito curto ou inválido.")
        return cleaned_doc

Tratamento de Sucesso e Erros de Validação

O Pydantic suporta tanto o modo flexível (lax), onde tenta converter tipos compatíveis (ex: converter a string '123' para número inteiro 123), quanto o modo estrito (strict).

Exemplo de Sucesso com Coerção Automática:

payload_valido = {
    'user_id': '1050',  # String será convertida para int
    'full_name': 'Ana Silva',
    'phone_number': '+1 (415) 555-2671',
    'document_code': 'DOC-987.654'
}

try:
    user = UserDocumentModel(**payload_valido)
    print("Objeto validado com sucesso:")
    print(user.model_dump())
    # Resultado:
    # {'user_id': 1050, 'full_name': 'Ana Silva', 'phone_number': '+14155552671', 'document_code': 'DOC987654'}
except ValidationError as e:
    print(e.errors())

Exemplo de Captura de Erros (ValidationError):

payload_invalido = {
    'user_id': 'não_e_um_numero',
    'phone_number': '123',  # Telefone muito curto
    'document_code': '12'
}

try:
    user = UserDocumentModel(**payload_invalido)
except ValidationError as e:
    print("Erros capturados na validação:")
    for err in e.errors():
        campo = err['loc'][0]
        mensagem = err['msg']
        print(f" - Campo '{campo}': {mensagem}")

4. Boas Práticas para Validação de Dados em Produção

  1. Sempre Normalize Antes de Validar: Não aplique expressões regulares estritas sobre entradas brutas dos usuários. Remova espaços, traços e prefixos de protocolo antes do teste.
  2. Utilize re.compile() para Padrões Frequentes: Ao processar grandes volumes de dados em loops ou pipelines, pré-compile as expressões regulares para otimizar o tempo de execução.
  3. Cuidado com Escapes em Classes de Caracteres: Em expressões como [\s\-\.\(\)], o hífen deve ser devidamente escapado com barra invertida \- ou posicionado no início/fim do grupo para evitar interpretação acidental como intervalo de caracteres.
  4. Combine Pydantic e Regex: Utilize o Pydantic como a estrutura principal de validação de schemas da sua API ou pipeline e incorpore validadores customizados (@field_validator) alimentados por expressões regulares.
  5. Retorne Mensagens Amigáveis: Ao capturar ValidationError, extraia o local do erro (loc) e a mensagem (msg) para fornecer retornos claros aos clientes da API ou gerar logs de auditoria estruturados.

Thales de Oliveira Gomes

Thales de Oliveira Gomes é gestor de Tecnologia da Informação, especialista em infraestrutura de redes, computação em nuvem e operações de TI hospitalar, com mais de 18 anos de experiência na área. Graduado em Gestão de TI e pós-graduado em Engenharia de Redes e Telecomunicações, Cloud Computing, Redes de Computadores e Gestão Hospitalar, construiu uma trajetória sólida liderando projetos de alta complexidade, como a implementação e migração para nuvem de sistemas ERP de grande porte. Sua atuação no setor o levou a ser indicado ao prêmio IT Leaders Brasil na categoria Rising Leaders. Além da carreira corporativa e da paixão por tecnologia, Thales é autor independente com mais de 70 obras publicadas sobre redes, segurança da informação, Linux e física, além de criador de conteúdo no blog Vetor Tech e na plataforma CiberDicas. No universo das artes e do entretenimento, atua como produtor de música eletrônica sob o projeto Yknex, cria ilustrações digitais e compartilha walkthroughs de games no YouTube.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *