Python library for parsing, formatting, storing and validating international phone numbers with reduced memory footprint
—
Helper functions for normalizing, converting, and analyzing phone numbers. These utilities support data processing, string manipulation, and number comparison operations.
Normalize phone number strings by removing or converting unwanted characters to standard formats.
def normalize_digits_only(number: str) -> str:
"""
Normalize string to contain only digits.
Removes all non-digit characters from the input string.
Parameters:
- number: String that may contain phone number with formatting
Returns:
String containing only digit characters (0-9)
"""
def normalize_diallable_chars_only(number: str) -> str:
"""
Normalize string to contain only diallable characters.
Keeps digits and diallable symbols like +, *, #, but removes
formatting characters like spaces, dashes, parentheses.
Parameters:
- number: Phone number string with potential formatting
Returns:
String with only diallable characters
"""Usage Examples:
import phonenumbers
# Remove all formatting
formatted_number = "+44 (20) 8366-1177"
digits_only = phonenumbers.normalize_digits_only(formatted_number)
print(digits_only) # "442083661177"
# Keep diallable characters
number_with_extension = "+44 20 8366 1177 ext. 123"
diallable = phonenumbers.normalize_diallable_chars_only(number_with_extension)
print(diallable) # "+442083661177ext123"Convert alphabetic characters in phone numbers to their corresponding digits (vanity numbers).
def convert_alpha_characters_in_number(number: str) -> str:
"""
Convert alphabetic characters to digits using phone keypad mapping.
Maps letters to digits according to standard phone keypad:
ABC=2, DEF=3, GHI=4, JKL=5, MNO=6, PQRS=7, TUV=8, WXYZ=9
Parameters:
- number: Phone number string that may contain letters
Returns:
String with letters converted to corresponding digits
"""
def is_alpha_number(number: str) -> bool:
"""
Check if a phone number string contains alphabetic characters.
Parameters:
- number: Phone number string to check
Returns:
True if the string contains 3 or more letters, False otherwise
"""Usage Examples:
import phonenumbers
# Convert vanity number
vanity_number = "1-800-FLOWERS"
numeric = phonenumbers.convert_alpha_characters_in_number(vanity_number)
print(numeric) # "1-800-3569377"
# Check for alpha characters
has_alpha = phonenumbers.is_alpha_number("1-800-CALL-NOW")
print(has_alpha) # True
no_alpha = phonenumbers.is_alpha_number("1-800-555-1234")
print(no_alpha) # FalseCompare phone numbers to determine if they represent the same number and the quality of the match.
def is_number_match(first_number: PhoneNumber, second_number: PhoneNumber) -> MatchType:
"""
Compare two phone numbers and return the type of match.
Parameters:
- first_number: First PhoneNumber object to compare
- second_number: Second PhoneNumber object to compare
Returns:
MatchType indicating the quality of the match:
- EXACT_MATCH: Numbers are identical in all respects
- NSN_MATCH: National significant numbers match exactly
- SHORT_NSN_MATCH: One NSN is a shorter version of the other
- NO_MATCH: Numbers are different
- NOT_A_NUMBER: One or both numbers are invalid
"""Usage Examples:
import phonenumbers
from phonenumbers import MatchType
# Parse two representations of the same number
number1 = phonenumbers.parse("+442083661177")
number2 = phonenumbers.parse("020 8366 1177", "GB")
match_result = phonenumbers.is_number_match(number1, number2)
if match_result == MatchType.EXACT_MATCH:
print("Numbers are exactly the same")
elif match_result == MatchType.NSN_MATCH:
print("Numbers have the same national significant number")
elif match_result == MatchType.SHORT_NSN_MATCH:
print("One number is a shorter version of the other")
elif match_result == MatchType.NO_MATCH:
print("Numbers are different")
# Compare with extensions
number_with_ext = phonenumbers.parse("+442083661177", keep_raw_input=True)
number_with_ext.extension = "123"
number_without_ext = phonenumbers.parse("+442083661177")
match_with_extension = phonenumbers.is_number_match(number_with_ext, number_without_ext)
print(f"Match with extension: {match_with_extension}")Extract the national significant number portion from a parsed phone number.
def national_significant_number(numobj: PhoneNumber) -> str:
"""
Get the national significant number from a PhoneNumber object.
The national significant number is the portion of the phone number
that is dialed within the country, excluding the country code but
including area codes and the subscriber number.
Parameters:
- numobj: PhoneNumber object to extract NSN from
Returns:
String containing the national significant number
"""Analyze the structure of phone numbers to determine area code and destination code lengths.
def length_of_geographical_area_code(numobj: PhoneNumber) -> int:
"""
Get the length of the geographical area code for a phone number.
Parameters:
- numobj: PhoneNumber object to analyze
Returns:
Length of the area code, or 0 if not applicable or determinable
"""
def length_of_national_destination_code(numobj: PhoneNumber) -> int:
"""
Get the length of the national destination code (area code + carrier code).
Parameters:
- numobj: PhoneNumber object to analyze
Returns:
Length of the national destination code
"""Work with mobile tokens used in certain countries before the area code.
def country_mobile_token(country_calling_code: int) -> str:
"""
Get the mobile token for a country, if applicable.
Some countries use a mobile token (like '9' in Argentina) that
appears before the area code in mobile numbers.
Parameters:
- country_calling_code: Country calling code to check
Returns:
Mobile token string, or empty string if not applicable
"""Check if a region uses the North American Numbering Plan.
def is_nanpa_country(region_code: str) -> bool:
"""
Check if a region uses the North American Numbering Plan.
NANPA regions share country code +1 and similar numbering patterns.
Parameters:
- region_code: Two-letter region code to check
Returns:
True if the region is part of NANPA, False otherwise
"""Check if a region supports mobile number portability.
def is_mobile_number_portable_region(region_code: str) -> bool:
"""
Check if mobile numbers can be ported between carriers in a region.
Parameters:
- region_code: Two-letter region code to check
Returns:
True if mobile number portability is supported, False otherwise
"""import phonenumbers
def clean_phone_number_data(raw_numbers, default_region="US"):
"""Clean a list of raw phone number strings for processing."""
cleaned_numbers = []
for raw_number in raw_numbers:
try:
# Step 1: Convert alpha characters
if phonenumbers.is_alpha_number(raw_number):
numeric_number = phonenumbers.convert_alpha_characters_in_number(raw_number)
else:
numeric_number = raw_number
# Step 2: Parse the number
parsed = phonenumbers.parse(numeric_number, default_region)
# Step 3: Validate
if phonenumbers.is_valid_number(parsed):
# Step 4: Normalize to E164 format
e164 = phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)
cleaned_numbers.append({
'original': raw_number,
'cleaned': e164,
'nsn': phonenumbers.national_significant_number(parsed),
'type': phonenumbers.number_type(parsed)
})
except phonenumbers.NumberParseException:
# Log invalid numbers for review
print(f"Could not parse: {raw_number}")
return cleaned_numbers
# Example usage
raw_data = [
"1-800-FLOWERS",
"+44 (20) 8366-1177",
"555.123.4567",
"(555) 123-4567 ext 123"
]
cleaned = clean_phone_number_data(raw_data)
for item in cleaned:
print(f"{item['original']} -> {item['cleaned']}")import phonenumbers
from phonenumbers import MatchType
def deduplicate_phone_numbers(number_strings, region="US"):
"""Remove duplicate phone numbers using intelligent matching."""
parsed_numbers = []
unique_numbers = []
# Parse all valid numbers
for number_str in number_strings:
try:
parsed = phonenumbers.parse(number_str, region)
if phonenumbers.is_valid_number(parsed):
parsed_numbers.append((number_str, parsed))
except phonenumbers.NumberParseException:
continue
# Find unique numbers
for original_str, parsed_num in parsed_numbers:
is_duplicate = False
for unique_str, unique_parsed in unique_numbers:
match_type = phonenumbers.is_number_match(parsed_num, unique_parsed)
if match_type in [MatchType.EXACT_MATCH, MatchType.NSN_MATCH]:
is_duplicate = True
break
if not is_duplicate:
unique_numbers.append((original_str, parsed_num))
return [num_str for num_str, _ in unique_numbers]
# Example usage
numbers_with_duplicates = [
"+1-800-555-1234",
"1 (800) 555-1234",
"18005551234",
"+1-555-123-4567",
"555.123.4567"
]
unique = deduplicate_phone_numbers(numbers_with_duplicates)
print(f"Original: {len(numbers_with_duplicates)} numbers")
print(f"Unique: {len(unique)} numbers")import phonenumbers
def analyze_number_structure(number_str, region=None):
"""Analyze the internal structure of a phone number."""
try:
number = phonenumbers.parse(number_str, region)
if not phonenumbers.is_valid_number(number):
return {"error": "Invalid number"}
nsn = phonenumbers.national_significant_number(number)
area_code_length = phonenumbers.length_of_geographical_area_code(number)
ndc_length = phonenumbers.length_of_national_destination_code(number)
region_code = phonenumbers.region_code_for_number(number)
is_nanpa = phonenumbers.is_nanpa_country(region_code)
mobile_token = phonenumbers.country_mobile_token(number.country_code)
return {
'country_code': number.country_code,
'nsn': nsn,
'area_code_length': area_code_length,
'ndc_length': ndc_length,
'region': region_code,
'is_nanpa': is_nanpa,
'mobile_token': mobile_token,
'is_geographic': phonenumbers.is_number_geographical(number),
'can_dial_internationally': phonenumbers.can_be_internationally_dialled(number)
}
except phonenumbers.NumberParseException as e:
return {"error": str(e)}
# Example usage
analysis = analyze_number_structure("+442083661177")
print(f"Country code: {analysis['country_code']}")
print(f"NSN: {analysis['nsn']}")
print(f"Area code length: {analysis['area_code_length']}")
print(f"Region: {analysis['region']}")Install with Tessl CLI
npx tessl i tessl/pypi-phonenumberslite