phone-utils 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ from .normalize import normalize_phone
2
+ from .validate import validate_phone
3
+ from .variants import generate_br_variants
4
+ from .exceptions import InvalidPhoneError
5
+
6
+ __all__ = ["normalize_phone", "validate_phone", "generate_br_variants", "InvalidPhoneError"]
@@ -0,0 +1,2 @@
1
+ class InvalidPhoneError(Exception):
2
+ pass
@@ -0,0 +1,8 @@
1
+ import phonenumbers
2
+
3
+ from .parser import parse_phone
4
+
5
+
6
+ def normalize_phone(phone: str) -> str:
7
+ parsed = parse_phone(phone)
8
+ return phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)
phone_utils/parser.py ADDED
@@ -0,0 +1,50 @@
1
+ import phonenumbers
2
+
3
+ from .exceptions import InvalidPhoneError
4
+
5
+
6
+ def parse_phone(phone: str) -> phonenumbers.PhoneNumber:
7
+ parsed = _try_parse(phone)
8
+ if not phonenumbers.is_valid_number(parsed):
9
+ raise InvalidPhoneError(f"Invalid phone number: {phone!r}")
10
+ return parsed
11
+
12
+
13
+ def parse_phone_lenient(phone: str) -> phonenumbers.PhoneNumber:
14
+ """Parse accepting legacy BR formats; 12-digit BR numbers get the 9th digit auto-added."""
15
+ parsed = _try_parse(phone)
16
+
17
+ if parsed.country_code == 55:
18
+ e164 = phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)
19
+ digits = e164[1:] # strip "+"
20
+ if len(digits) == 12:
21
+ with_ninth = "+" + digits[:4] + "9" + digits[4:]
22
+ try:
23
+ candidate = phonenumbers.parse(with_ninth, None)
24
+ if phonenumbers.is_valid_number(candidate):
25
+ return candidate
26
+ except phonenumbers.NumberParseException:
27
+ pass
28
+
29
+ if not phonenumbers.is_possible_number(parsed):
30
+ raise InvalidPhoneError(f"Invalid phone number: {phone!r}")
31
+ return parsed
32
+
33
+
34
+ def _try_parse(phone: str) -> phonenumbers.PhoneNumber:
35
+ if not isinstance(phone, str) or not phone.strip():
36
+ raise InvalidPhoneError(f"Invalid phone number: {phone!r}")
37
+
38
+ cleaned = phone.strip()
39
+ candidates = [cleaned]
40
+ if not cleaned.startswith("+"):
41
+ candidates.append("+" + cleaned)
42
+
43
+ last_exc = None
44
+ for candidate in candidates:
45
+ try:
46
+ return phonenumbers.parse(candidate, None)
47
+ except phonenumbers.NumberParseException as exc:
48
+ last_exc = exc
49
+
50
+ raise InvalidPhoneError(f"Invalid phone number: {phone!r}") from last_exc
@@ -0,0 +1,9 @@
1
+ from .normalize import normalize_phone
2
+
3
+
4
+ def validate_phone(phone: str) -> bool:
5
+ try:
6
+ normalize_phone(phone)
7
+ return True
8
+ except Exception:
9
+ return False
@@ -0,0 +1,44 @@
1
+ import phonenumbers
2
+
3
+ from .exceptions import InvalidPhoneError
4
+ from .normalize import normalize_phone
5
+ from .parser import parse_phone_lenient
6
+
7
+ _BR_COUNTRY_CODE = 55
8
+ _BR_DDI = "+55"
9
+ _BR_TOTAL_WITH_NINTH = 13
10
+ _BR_TOTAL_WITHOUT_NINTH = 12
11
+
12
+
13
+ def generate_br_variants(phone: str) -> list[str]:
14
+ normalized = _normalize_lenient(phone)
15
+
16
+ if not normalized.startswith(_BR_DDI):
17
+ return [normalized]
18
+
19
+ digits = normalized[1:] # strip "+"
20
+ ddd = digits[2:4] # area code after country code "55"
21
+ local = digits[4:] # digits after DDD
22
+ total_digits = len(digits)
23
+
24
+ if total_digits == _BR_TOTAL_WITH_NINTH and local.startswith("9"):
25
+ without_ninth = f"+55{ddd}{local[1:]}"
26
+ return [normalized, without_ninth]
27
+
28
+ if total_digits == _BR_TOTAL_WITHOUT_NINTH and local.startswith("9"):
29
+ with_ninth = f"+55{ddd}9{local}"
30
+ return [with_ninth, normalized]
31
+
32
+ return [normalized]
33
+
34
+
35
+ def _normalize_lenient(phone: str) -> str:
36
+ """Normalize to E.164; falls back to lenient parsing only for legacy BR formats."""
37
+ try:
38
+ return normalize_phone(phone)
39
+ except InvalidPhoneError:
40
+ pass
41
+ parsed = parse_phone_lenient(phone)
42
+ if parsed.country_code != _BR_COUNTRY_CODE:
43
+ raise InvalidPhoneError(f"Invalid phone number: {phone!r}")
44
+ return phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: phone-utils
3
+ Version: 0.1.0
4
+ Summary: Biblioteca para normalização, validação e geração de variantes de números telefônicos.
5
+ Author-email: Alexandre Nahuz <alexandrenahuz@gmail.com>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: phonenumbers>=9.0.0
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
11
+ Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
12
+
13
+ # phone-utils
14
+
15
+ Biblioteca Python para normalização, validação e geração de variantes de números de telefone internacionais, com suporte especial à regra do 9º dígito brasileiro.
16
+
17
+ ## Instalação
18
+
19
+ ```bash
20
+ pip install phone-utils
21
+ ```
22
+
23
+ ## Uso
24
+
25
+ ```python
26
+ from phone_utils import normalize_phone, validate_phone, generate_br_variants, InvalidPhoneError
27
+ ```
28
+
29
+ ### Normalização
30
+
31
+ Converte qualquer formato de entrada para E.164 (`+CCDDNNNNNNNNN`):
32
+
33
+ ```python
34
+ normalize_phone("+55 (11) 99999-9999") # → "+5511999999999"
35
+ normalize_phone("5511999999999") # → "+5511999999999"
36
+ normalize_phone("+1 (202) 555-0123") # → "+12025550123"
37
+
38
+ normalize_phone("11999999999") # → raise InvalidPhoneError (sem DDI)
39
+ normalize_phone("abc") # → raise InvalidPhoneError
40
+ ```
41
+
42
+ ### Validação
43
+
44
+ Verifica a estrutura do número sem lançar exceção:
45
+
46
+ ```python
47
+ validate_phone("+5511999999999") # → True
48
+ validate_phone("+12025550123") # → True
49
+ validate_phone("11999999999") # → False
50
+ validate_phone("") # → False
51
+ ```
52
+
53
+ ### Variantes brasileiras (regra do 9º dígito)
54
+
55
+ Gera todas as representações válidas de um número brasileiro considerando a presença ou ausência do 9º dígito:
56
+
57
+ ```python
58
+ # Com 9º dígito → gera variante sem
59
+ generate_br_variants("+5511999999999")
60
+ # → ["+5511999999999", "+551199999999"]
61
+
62
+ # Sem 9º dígito → gera variante com
63
+ generate_br_variants("+551199999999")
64
+ # → ["+5511999999999", "+551199999999"]
65
+
66
+ # Sem 9º dígito compatível (não começa com 9) → retorna só o número
67
+ generate_br_variants("+5511799999999")
68
+ # → ["+5511799999999"]
69
+
70
+ # Número internacional → retorna só o número normalizado
71
+ generate_br_variants("+12025550123")
72
+ # → ["+12025550123"]
73
+ ```
74
+
75
+ ### Tratamento de erros
76
+
77
+ ```python
78
+ from phone_utils import InvalidPhoneError
79
+
80
+ try:
81
+ normalized = normalize_phone("numero-invalido")
82
+ except InvalidPhoneError as e:
83
+ print(f"Número inválido: {e}")
84
+ ```
85
+
86
+ ## Formatos de entrada aceitos
87
+
88
+ | Formato | Exemplo |
89
+ |---|---|
90
+ | E.164 | `+5511999999999` |
91
+ | Sem `+` (com DDI) | `5511999999999` |
92
+ | Com máscara | `+55 (11) 99999-9999` |
93
+ | Com espaços | `+55 11 99999 9999` |
94
+ | Internacional | `+1 (202) 555-0123` |
95
+
96
+ ## Escopo da biblioteca
97
+
98
+ **Faz:**
99
+ - Normalizar números para E.164
100
+ - Validar estrutura conforme regras do país
101
+ - Gerar variantes BR para compatibilidade com bases legadas
102
+
103
+ **Não faz:**
104
+ - Verificar se a linha existe ou está ativa
105
+ - Consultar operadoras
106
+ - Enriquecer dados
107
+ - Registrar logs
108
+
109
+ ## Requisitos
110
+
111
+ - Python >= 3.10
112
+ - [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) >= 9.0.0
@@ -0,0 +1,10 @@
1
+ phone_utils/__init__.py,sha256=YEEV8uoHKdxDkYVLb1IvwQNn2eCrYoXVT-uVEYZ7Gug,255
2
+ phone_utils/exceptions.py,sha256=t4d-ktkGeHKn-bi4RHetP4CthvJ1BoJbkpoq8wx52mM,45
3
+ phone_utils/normalize.py,sha256=wPZ4z1RmLVrc1jyNZ5SepySmuRGeYVe08iT5wjBttZE,210
4
+ phone_utils/parser.py,sha256=pWSIEygwzQTBA5Uxn7132uv4VtFF4pbrzTnFq429yFE,1726
5
+ phone_utils/validate.py,sha256=FSjqv47ff-7ZfOvz6kBSme_lfgklwsYwiumHZqq11Jg,184
6
+ phone_utils/variants.py,sha256=PKzxcnQkC1oAjGUGAS_ThSwAh7JF1V-Ybkz6zwmU1jw,1395
7
+ phone_utils-0.1.0.dist-info/METADATA,sha256=Q9jHDO_ovLhgjy-njuUCyXfdbA-mXAZ-aZJ4YMNUa14,3182
8
+ phone_utils-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
9
+ phone_utils-0.1.0.dist-info/top_level.txt,sha256=ID1jdHUG0gGyNoQ7QzgEdYnp7qmtOMN8PLPnTvfLRvw,12
10
+ phone_utils-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ phone_utils