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ão | Expressã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
- 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.
- 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. - 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. - 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. - 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.
