-
-
Notifications
You must be signed in to change notification settings - Fork 119
RFC: Estratégia para tratamento de valores inválidos nas funções do brazilian-utils #671
-
Contexto
Hoje, várias funções do brutils retornam None quando recebem valores inválidos.
Alguns exemplos (extraídos da documentação):
format_cpf(...)→ retornastrouNonese o CPF é inválido.format_cnpj(...)→ retornastrouNonese o CNPJ é inválido.format_cep(...)→ retornastrouNonese o CEP é inválido.format_phone(...)→ retornastrouNonese o telefone é inválido.- Diversas outras funções de formatação e conversão seguem esse padrão.
Objetivo
- Definir uma estratégia padronizada para tratamento de valores inválidos nas funções do
brazilian-utils. - Preferencialmente, alinhar com a prática Python de levantar exceções quando o input é inválido.
- Fazer isso de forma:
- bem documentada,
- previsível para quem usa a lib,
- com um caminho de migração suave e incremental,
- sem quebrar código existente até uma major release.
Proposta de abordagem
Estratégia: Strict Mode Global + Parâmetro strict por função + Decorador @strictify
Esta abordagem combina três níveis de controle:
- Strict mode global — ativa/desativa o comportamento estrito para toda a lib
- Parâmetro
strictpor função — permite controle granular por chamada - Decorador
@strictify— centraliza a lógica de decisão e permite migração incremental
Prioridade de comportamento
O decorador @strictify decide se deve levantar exceção seguindo esta ordem:
- Se
strict=Truefoi passado na função → levanta exceção - Se
strict=Falsefoi passado na função → retornaNone(comportamento legado) - Se
strictnão foi passado, verifica o strict mode global:- Se
strict_mode_enabled()→ levanta exceção - Caso contrário → retorna
None(comportamento legado)
- Se
Componentes da solução
1. Hierarquia de exceções customizadas
class BrutilsError(Exception): """Exceção base para todos os erros do brutils.""" pass class InvalidCPFError(BrutilsError): """Levantada quando um CPF inválido é fornecido.""" pass class InvalidCNPJError(BrutilsError): """Levantada quando um CNPJ inválido é fornecido.""" pass class InvalidCEPError(BrutilsError): """Levantada quando um CEP inválido é fornecido.""" pass # ... outras exceções específicas
2. Strict mode global
_STRICT_MODE = False def enable_strict_mode() -> None: """Ativa o strict mode globalmente para todas as funções do brutils.""" global _STRICT_MODE _STRICT_MODE = True def disable_strict_mode() -> None: """Desativa o strict mode globalmente.""" global _STRICT_MODE _STRICT_MODE = False def strict_mode_enabled() -> bool: """Retorna True se o strict mode está ativo globalmente.""" return _STRICT_MODE
3. Decorador @strictify
def strictify(func): def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: if strict_mode_enabled() or kwargs.get("strict"): raise # novo comportamento return None # comportamento legado return wrapper
4. Adicionar warnings sobre futura mudança de default
import warnings def format_cpf(cpf: str, *, strict: bool | None = None) -> str: if strict is None and not strict_mode_enabled(): warnings.warn( "O comportamento padrão de format_cpf mudará na v3.0.0. " "CPFs inválidos levantarão InvalidCPFError ao invés de retornar None. " "Use strict=False para manter o comportamento atual.", DeprecationWarning, stacklevel=2 ) # ... resto da função
Resultado: Usuários são avisados com antecedência sobre a mudança futura.
Vantagens desta abordagem
✅ Não força usuários atuais a mudar nada — comportamento legado preservado até v3.0.0
✅ Permite evolução incremental da lib — migrar 1 função por vez, sem pressa
✅ Introduz comportamento mais seguro sem atrito — usuários podem adotar quando quiserem
✅ Granularidade total — controle global E por função
✅ Alinhado com práticas de grandes libs Python — Django, Pydantic, SQLAlchemy, Requests, Boto3 usam estratégias similares
✅ Facilita testes e code review — mudanças pequenas e incrementais
✅ Tipagem estática melhorada — funções em strict mode podem retornar str ao invés de str | None
Tarefas de implementação
Fase 1: Infraestrutura
- Criar módulo
brutils/exceptions.pycom hierarquia de exceções - Implementar strict mode global em
brutils/__init__.py - Implementar decorador
@strictify - Adicionar testes para infraestrutura
- Documentar no
README.mdeREADME_EN.md
Fase 2: Migração incremental
- Migrar
format_cpfeis_valid_cpf - Migrar
format_cnpjeis_valid_cnpj - Migrar
format_cepeis_valid_cep - Migrar demais utilitários
Alternativas Consideradas
Usar ValueError padrão ao invés de exceções customizadas
Descrição:
Ao invés de criar uma hierarquia de exceções customizadas (BRUtilsError, InvalidCPFError, etc.), usar apenas ValueError do Python padrão.
Exemplo:
@strictify def format_cpf(cpf: str, *, strict: bool | None = None) -> str: """ Formata um CPF válido. Raises: ValueError: Se o CPF é inválido e strict mode está ativo """ if not is_valid_cpf(cpf): raise ValueError(f"Invalid CPF: {cpf}") return cpf_formatado
Perguntas para discussão
-
Vocês concordam com essa abordagem de migração incremental + strict mode global + parâmetro granular?
-
A ordem de prioridade (parâmetro
strict> modo global > legado) faz sentido? -
Os nomes escolhidos estão adequados?
enable_strict_mode()/disable_strict_mode()/strict_mode_enabled()- Parâmetro
strict(ao invés deraise_exceptions) - Decorador
@strictify - Exceções como
InvalidCPFError,InvalidCNPJError, etc.
-
Existe alguma função específica onde esse modelo não se aplica bem?
Note
Comentários, contrapontos e sugestões são super bem-vindos!
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 3
Replies: 2 comments
-
@niltonpimentel02 ficou muito bom o RFC! Super claro, bem estruturado e fácil de acompanhar.
Quem sabe, vale até a gente até criar uma categoria específica só de RFC nas Discussions para centralizar esse tipo de proposta, seria ótimo pra manter histórico e abrir espaço pra mais ideias assim.
Sobre os pontos técnicos:
- O único ajuste seria na lógica do decorator, porque acredito que a implementação atual não reflete exatamente a prioridade descrita no próprio RFC. Hoje ele trata só dois cenários:
except Exception: if strict_mode_enabled() or kwargs.get("strict"): raise return None
Isso faz com que strict=False acabe ignorado caso o strict mode global esteja ativado. Para refletir certim, pensei em algo nessa linha:
def strictify(func): def wrapper(*args, **kwargs): strict_flag = kwargs.get("strict", None) try: return func(*args, **kwargs) except Exception: if strict_flag is True: raise if strict_flag is False: return None if strict_mode_enabled(): raise return None return wrapper
- Outra ideia: talvez possamos mover a lógica de warnings para dentro do próprio decorator, pra evitar repetir isso em cada função. Algo como:
def strictify(func): def wrapper(*args, **kwargs): strict_flag = kwargs.get("strict", None) try: return func(*args, **kwargs) except Exception as e: # strict=True → sempre levanta if strict_flag is True: raise # strict=False → comportamento legado if strict_flag is False: return None # strict não passado → verifica modo global if strict_mode_enabled(): raise # comportamento legado → avisar que isso mudará warnings.warn( ( f"O comportamento padrão de {func.__name__} mudará a partir da versão 3.x." f"Valores inválidos passarão a levantar {type(e).__name__} " "ao invés de retornar None. " "Use strict=False para manter o comportamento atual." ), DeprecationWarning, stacklevel=2 ) return None return wrapper
Assim a gente mantém a mensagem informativa, clara e bem parecida com a original, mas de forma genérica e concentrada dentro do decorator, sem precisar repetir isso em todas as funções, e só dando o warning para quem não migrou ainda.
Na sugestão, também mudei a parte do texto do warning que dizia mudará na v3.0.0. para mudará a partir da versão 3.x. - para deixar mais claro e mais amplo.
E queria também ouvir a opinião do @hyanmandian, como vocês estão lidando com isso na versão TS? Será que faria sentido algo nessa linha por lá também? Mesmo que ajustado ao ecossistema, poderia ser interessante manter uma coerência conceitual entre as libs.
Mas no geral, para mim tá sucesso demais, só dale!
Valeu!!
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 2
-
Primeiramente, parabéns pela RFC @niltonpimentel02, ficou muito boa!
Dando os meus 2 centavos aqui na discussão, gostei da solução proposta. Para ser sincero eu fiquei com um sentimento dúbio. Por um lado achei uma solução bastante completa, por outro achei que possui um grau de complexidade que eu não vi na biblioteca como um todo. Refletindo sobre essa questão eu tendo mais a ser a favor de adicionarmos um sistema mais robusto, mesmo que torne a biblioteca mais complexa. Acho que a tendência é esta biblioteca se tornar cada vez mais robusta e, logo, adicionar complexidade se torna inevitável. Gostei da solução final como foi colocada pela @camilamaia e, para ser sincero, acho que vocês estão muito mais por dentro dos patterns atuais do Python do que eu. Vou tentar contribuir com esta implementação, mas acho que vou mais seguir vocês.
Sobre as perguntas colocas pelo @niltonpimentel02:
1 - Sim, concordo.
2 - Sim, faz sentido.
3 - Sobre os nomes das funções, eu alteraria a função strict_mode_enabled para strict_mode_is_enabled para tentar fugir um pouco da semelhança com a função enable_strict_mode. De resto, tudo OK.
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 2