Python port of Google's libphonenumber library for parsing, formatting, storing and validating international phone numbers
—
Helper functions for string processing, normalization, example number generation, and metadata access. These utilities provide advanced functionality for specialized use cases and integration with other systems.
Functions to clean, normalize, and process phone number strings before parsing or after formatting.
def normalize_digits_only(number: str, keep_non_digits: bool = False) -> str:
"""
Extract and normalize only digits from string.
Parameters:
- number: Input string to normalize
- keep_non_digits: Whether to preserve non-digit characters
Returns:
String containing only digits (0-9) or digits plus non-digits if keep_non_digits=True
"""
def normalize_diallable_chars_only(number: str) -> str:
"""
Normalize string to contain only diallable characters.
Parameters:
- number: Input string to normalize
Returns:
String containing only characters that can be dialed (digits, +, *, #, etc.)
"""
def convert_alpha_characters_in_number(number: str) -> str:
"""
Convert alphabetic characters to digits (e.g., ABC → 222).
Parameters:
- number: Phone number string potentially containing letters
Returns:
String with letters converted to corresponding digits
"""
def is_alpha_number(number: str) -> bool:
"""
Check if string could be an alphanumeric phone number.
Parameters:
- number: String to check for alphanumeric phone number pattern
Returns:
True if the string appears to be an alphanumeric phone number
"""Usage Examples:
# Normalize digits from messy input
messy_input = "Call (555) 123-HELP or +1.555.987.6543 ext.123"
digits_only = phonenumbers.normalize_digits_only(messy_input)
print(digits_only) # "555123555987654312"
# Keep some non-digits for context
partial_normalize = phonenumbers.normalize_digits_only("555-123-HELP", keep_non_digits=True)
print(partial_normalize) # "555-123-"
# Normalize to diallable characters
diallable = phonenumbers.normalize_diallable_chars_only("Call +1 (555) 123-4567!")
print(diallable) # "+15551234567"
# Convert letters to numbers (phone keypad mapping)
alpha_number = "1-800-FLOWERS"
numeric = phonenumbers.convert_alpha_characters_in_number(alpha_number)
print(numeric) # "1-800-3569377"
# Letter conversion examples
conversions = [
"CALL-HOME", # 2255-4663
"555-HELP", # 555-4357
"1-800-GOT-JUNK", # 1-800-468-5865
"TEXT-TAXI" # 8398-8294
]
for alpha in conversions:
numeric = phonenumbers.convert_alpha_characters_in_number(alpha)
print(f"{alpha} -> {numeric}")
# Check if string is alphanumeric phone number
test_strings = [
"1-800-FLOWERS", # True
"555-HELP", # True
"CALL-ME", # True
"Hello World", # False
"123-456-7890", # False (no letters)
"" # False
]
for test_str in test_strings:
is_alpha = phonenumbers.is_alpha_number(test_str)
print(f"'{test_str}' is alphanumeric: {is_alpha}")Functions to generate example phone numbers for testing, documentation, and user interface mockups.
def example_number(region_code: str) -> PhoneNumber | None:
"""
Get example phone number for region.
Parameters:
- region_code: Region identifier (e.g., "US", "GB")
Returns:
Example PhoneNumber for the region, None if no example available
"""
def example_number_for_type(region_code: str | None, num_type: int) -> PhoneNumber | None:
"""
Get example number of specified type for region.
Parameters:
- region_code: Region identifier, None for non-geographical numbers
- num_type: PhoneNumberType value (MOBILE, FIXED_LINE, etc.)
Returns:
Example PhoneNumber of the specified type, None if unavailable
"""
def example_number_for_non_geo_entity(country_calling_code: int) -> PhoneNumber | None:
"""
Get example number for non-geographical country code.
Parameters:
- country_calling_code: Non-geographical country calling code
Returns:
Example PhoneNumber for the non-geographical entity
"""
def invalid_example_number(region_code: str) -> PhoneNumber | None:
"""
Get example of invalid number for testing purposes.
Parameters:
- region_code: Region identifier
Returns:
Example invalid PhoneNumber for testing validation logic
"""Usage Examples:
# Generate example numbers for different regions
regions = ["US", "GB", "DE", "FR", "JP", "AU"]
print("Example numbers by region:")
for region in regions:
example = phonenumbers.example_number(region)
if example:
formatted = phonenumbers.format_number(example, phonenumbers.PhoneNumberFormat.INTERNATIONAL)
print(f"{region}: {formatted}")
# Generate examples by number type
types_to_test = [
(phonenumbers.PhoneNumberType.FIXED_LINE, "Fixed Line"),
(phonenumbers.PhoneNumberType.MOBILE, "Mobile"),
(phonenumbers.PhoneNumberType.TOLL_FREE, "Toll Free"),
(phonenumbers.PhoneNumberType.PREMIUM_RATE, "Premium Rate")
]
print("\nUS examples by type:")
for num_type, type_name in types_to_test:
example = phonenumbers.example_number_for_type("US", num_type)
if example:
formatted = phonenumbers.format_number(example, phonenumbers.PhoneNumberFormat.NATIONAL)
print(f"{type_name}: {formatted}")
# Non-geographical examples
non_geo_codes = [800, 808, 870, 878, 881, 882, 883, 888]
print("\nNon-geographical examples:")
for code in non_geo_codes:
example = phonenumbers.example_number_for_non_geo_entity(code)
if example:
formatted = phonenumbers.format_number(example, phonenumbers.PhoneNumberFormat.INTERNATIONAL)
print(f"Country code {code}: {formatted}")
# Invalid examples for testing
print("\nInvalid examples for testing:")
test_regions = ["US", "GB", "DE"]
for region in test_regions:
invalid = phonenumbers.invalid_example_number(region)
if invalid:
# These should fail validation
is_valid = phonenumbers.is_valid_number(invalid)
formatted = phonenumbers.format_number(invalid, phonenumbers.PhoneNumberFormat.E164)
print(f"{region} invalid: {formatted} (valid: {is_valid})")
# Generate test data for unit tests
def generate_test_data(region, include_invalid=True):
"""Generate comprehensive test data for a region."""
test_data = {
"region": region,
"general_example": None,
"by_type": {},
"invalid_example": None
}
# General example
general = phonenumbers.example_number(region)
if general:
test_data["general_example"] = {
"number": general,
"e164": phonenumbers.format_number(general, phonenumbers.PhoneNumberFormat.E164),
"national": phonenumbers.format_number(general, phonenumbers.PhoneNumberFormat.NATIONAL),
"international": phonenumbers.format_number(general, phonenumbers.PhoneNumberFormat.INTERNATIONAL)
}
# Examples by type
all_types = [
phonenumbers.PhoneNumberType.FIXED_LINE,
phonenumbers.PhoneNumberType.MOBILE,
phonenumbers.PhoneNumberType.TOLL_FREE,
phonenumbers.PhoneNumberType.PREMIUM_RATE,
phonenumbers.PhoneNumberType.SHARED_COST,
phonenumbers.PhoneNumberType.VOIP,
phonenumbers.PhoneNumberType.PERSONAL_NUMBER,
phonenumbers.PhoneNumberType.PAGER,
phonenumbers.PhoneNumberType.UAN,
phonenumbers.PhoneNumberType.VOICEMAIL
]
for num_type in all_types:
example = phonenumbers.example_number_for_type(region, num_type)
if example:
test_data["by_type"][num_type] = {
"number": example,
"formatted": phonenumbers.format_number(example, phonenumbers.PhoneNumberFormat.E164)
}
# Invalid example
if include_invalid:
invalid = phonenumbers.invalid_example_number(region)
if invalid:
test_data["invalid_example"] = {
"number": invalid,
"formatted": phonenumbers.format_number(invalid, phonenumbers.PhoneNumberFormat.E164)
}
return test_data
# Generate test data for US
us_test_data = generate_test_data("US")
print(f"\nGenerated test data for US:")
print(f"- General example: {us_test_data['general_example']['e164'] if us_test_data['general_example'] else 'None'}")
print(f"- Number types with examples: {len(us_test_data['by_type'])}")
print(f"- Has invalid example: {us_test_data['invalid_example'] is not None}")Access to global constants and metadata about supported regions and capabilities.
SUPPORTED_REGIONS: set[str] # Set of all supported region codes
SUPPORTED_SHORT_REGIONS: list[str] # List of regions with short number support
COUNTRY_CODE_TO_REGION_CODE: dict[int, tuple[str, ...]] # Map country codes to regions
COUNTRY_CODES_FOR_NON_GEO_REGIONS: set[int] # Non-geographical country codes
UNKNOWN_REGION: str # Constant for unknown region ("ZZ")
REGION_CODE_FOR_NON_GEO_ENTITY: str # Region code for non-geographical numbers ("001")
NON_DIGITS_PATTERN: Pattern[str] # Regex pattern matching non-digit characters
__version__: str # Package version stringUsage Examples:
# Access global constants
print(f"Library version: {phonenumbers.__version__}")
print(f"Unknown region code: {phonenumbers.UNKNOWN_REGION}")
print(f"Non-geo region code: {phonenumbers.REGION_CODE_FOR_NON_GEO_ENTITY}")
# Supported regions information
all_regions = phonenumbers.SUPPORTED_REGIONS
print(f"Total supported regions: {len(all_regions)}")
print(f"Sample regions: {sorted(list(all_regions))[:10]}")
# Check if specific regions are supported
regions_to_check = ["US", "GB", "CA", "AU", "XX", "ZZ"]
for region in regions_to_check:
is_supported = region in phonenumbers.SUPPORTED_REGIONS
print(f"Region {region} supported: {is_supported}")
# Short number support
short_regions = phonenumbers.SUPPORTED_SHORT_REGIONS
print(f"\nShort numbers supported in {len(short_regions)} regions")
print(f"Short number regions: {short_regions[:10]}") # First 10
# Country code mappings
print(f"\nCountry code mappings (sample):")
sample_codes = [1, 44, 33, 49, 81]
for code in sample_codes:
regions = phonenumbers.COUNTRY_CODE_TO_REGION_CODE.get(code, ())
print(f"Country code {code}: {regions}")
# Non-geographical country codes
non_geo_codes = phonenumbers.COUNTRY_CODES_FOR_NON_GEO_REGIONS
print(f"\nNon-geographical country codes: {sorted(non_geo_codes)}")
# Pattern matching with NON_DIGITS_PATTERN
import re
test_strings = ["123-456-7890", "abc123def", "+1 (555) 123-4567", ""]
for test_str in test_strings:
non_digits = phonenumbers.NON_DIGITS_PATTERN.findall(test_str)
print(f"'{test_str}' non-digits: {non_digits}")Specialized utility functions for advanced use cases and system integration.
def supported_calling_codes() -> set[int]:
"""Get set of all supported country calling codes."""
def supported_types_for_region(region_code: str) -> set[int]:
"""Get supported phone number types for region."""
def supported_types_for_non_geo_entity(country_code: int) -> set[int]:
"""Get supported types for non-geographical entities."""Usage Examples:
# Get all supported calling codes
all_codes = phonenumbers.supported_calling_codes()
print(f"All supported country codes ({len(all_codes)}):")
print(f"Range: {min(all_codes)} to {max(all_codes)}")
print(f"Sample codes: {sorted(list(all_codes))[:20]}")
# Analyze regional capabilities
def analyze_region_capabilities(region_code):
"""Analyze what number types are supported in a region."""
if region_code not in phonenumbers.SUPPORTED_REGIONS:
return f"Region {region_code} is not supported"
supported_types = phonenumbers.supported_types_for_region(region_code)
type_names = {
phonenumbers.PhoneNumberType.FIXED_LINE: "Fixed Line",
phonenumbers.PhoneNumberType.MOBILE: "Mobile",
phonenumbers.PhoneNumberType.FIXED_LINE_OR_MOBILE: "Fixed Line or Mobile",
phonenumbers.PhoneNumberType.TOLL_FREE: "Toll Free",
phonenumbers.PhoneNumberType.PREMIUM_RATE: "Premium Rate",
phonenumbers.PhoneNumberType.SHARED_COST: "Shared Cost",
phonenumbers.PhoneNumberType.VOIP: "VoIP",
phonenumbers.PhoneNumberType.PERSONAL_NUMBER: "Personal Number",
phonenumbers.PhoneNumberType.PAGER: "Pager",
phonenumbers.PhoneNumberType.UAN: "Universal Access Number",
phonenumbers.PhoneNumberType.VOICEMAIL: "Voicemail"
}
capabilities = []
for type_code in supported_types:
type_name = type_names.get(type_code, f"Type {type_code}")
capabilities.append(type_name)
return {
"region": region_code,
"supported_types": sorted(capabilities),
"count": len(capabilities)
}
# Analyze several regions
regions_to_analyze = ["US", "GB", "DE", "JP", "IN", "BR"]
for region in regions_to_analyze:
analysis = analyze_region_capabilities(region)
if isinstance(analysis, dict):
print(f"\n{region} capabilities ({analysis['count']} types):")
for capability in analysis['supported_types']:
print(f" - {capability}")
# Non-geographical entity analysis
non_geo_codes = [800, 808, 870, 878, 881, 882, 883, 888]
print(f"\nNon-geographical entity capabilities:")
for code in non_geo_codes:
supported_types = phonenumbers.supported_types_for_non_geo_entity(code)
if supported_types:
print(f"Country code {code}: {len(supported_types)} supported types")
# Create comprehensive capability matrix
def create_capability_matrix(regions=None):
"""Create a matrix showing which capabilities are available in which regions."""
if regions is None:
regions = ["US", "GB", "DE", "FR", "JP", "AU", "CA", "IN"]
all_types = [
phonenumbers.PhoneNumberType.FIXED_LINE,
phonenumbers.PhoneNumberType.MOBILE,
phonenumbers.PhoneNumberType.TOLL_FREE,
phonenumbers.PhoneNumberType.PREMIUM_RATE,
phonenumbers.PhoneNumberType.SHARED_COST,
phonenumbers.PhoneNumberType.VOIP
]
type_names = ["Fixed", "Mobile", "Toll Free", "Premium", "Shared", "VoIP"]
matrix = {}
for region in regions:
if region in phonenumbers.SUPPORTED_REGIONS:
supported = phonenumbers.supported_types_for_region(region)
matrix[region] = [num_type in supported for num_type in all_types]
return matrix, type_names
capability_matrix, type_headers = create_capability_matrix()
print(f"\nCapability Matrix:")
print(f"{'Region':<8}", end="")
for header in type_headers:
print(f"{header:<10}", end="")
print()
print("-" * (8 + len(type_headers) * 10))
for region, capabilities in capability_matrix.items():
print(f"{region:<8}", end="")
for has_capability in capabilities:
mark = "✓" if has_capability else "✗"
print(f"{mark:<10}", end="")
print()Utility functions to help integrate phonenumbers with other systems and frameworks.
Usage Examples:
# Create validation helpers for web forms
def create_phone_validator(region_code):
"""Create a phone number validator function for a specific region."""
def validate_phone(phone_string):
try:
parsed = phonenumbers.parse(phone_string, region_code)
return {
"valid": phonenumbers.is_valid_number(parsed),
"possible": phonenumbers.is_possible_number(parsed),
"formatted": phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164),
"type": phonenumbers.number_type(parsed),
"region": phonenumbers.region_code_for_number(parsed)
}
except phonenumbers.NumberParseException as e:
return {
"valid": False,
"error": str(e),
"error_type": e.error_type
}
return validate_phone
# Create validators for different regions
us_validator = create_phone_validator("US")
uk_validator = create_phone_validator("GB")
# Test validation
test_numbers = [
"(555) 123-4567",
"+1 555 123 4567",
"020 8366 1177",
"+44 20 8366 1177",
"invalid"
]
for number in test_numbers:
us_result = us_validator(number)
uk_result = uk_validator(number)
print(f"\n'{number}':")
print(f" US context: {'Valid' if us_result.get('valid') else 'Invalid'}")
print(f" UK context: {'Valid' if uk_result.get('valid') else 'Invalid'}")
# Database storage helper
def prepare_for_storage(phone_string, region_code=None):
"""Prepare phone number for database storage."""
try:
parsed = phonenumbers.parse(phone_string, region_code)
if not phonenumbers.is_valid_number(parsed):
return None # Don't store invalid numbers
return {
"e164": phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164),
"national": phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.NATIONAL),
"international": phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.INTERNATIONAL),
"country_code": parsed.country_code,
"national_number": parsed.national_number,
"region": phonenumbers.region_code_for_number(parsed),
"type": phonenumbers.number_type(parsed),
"extension": parsed.extension
}
except phonenumbers.NumberParseException:
return None
# Test storage preparation
storage_tests = [
("(555) 123-4567", "US"),
("+44 20 8366 1177", None),
("invalid", "US")
]
for phone, region in storage_tests:
stored_data = prepare_for_storage(phone, region)
if stored_data:
print(f"\n'{phone}' -> Storage data:")
print(f" E164: {stored_data['e164']}")
print(f" Region: {stored_data['region']}")
print(f" Type: {stored_data['type']}")
else:
print(f"\n'{phone}' -> Not suitable for storage")
# Bulk processing helper
def bulk_process_numbers(phone_list, region_code=None, format_type=None):
"""Process a list of phone numbers efficiently."""
if format_type is None:
format_type = phonenumbers.PhoneNumberFormat.E164
results = []
for phone_string in phone_list:
try:
parsed = phonenumbers.parse(phone_string, region_code)
result = {
"input": phone_string,
"valid": phonenumbers.is_valid_number(parsed),
"formatted": phonenumbers.format_number(parsed, format_type),
"region": phonenumbers.region_code_for_number(parsed),
"type": phonenumbers.number_type(parsed)
}
results.append(result)
except phonenumbers.NumberParseException as e:
results.append({
"input": phone_string,
"valid": False,
"error": str(e),
"error_type": e.error_type
})
return results
# Test bulk processing
phone_batch = [
"(555) 123-4567",
"+44 20 8366 1177",
"1-800-555-0199",
"invalid-number",
"+33 1 42 68 53 00"
]
batch_results = bulk_process_numbers(phone_batch, "US", phonenumbers.PhoneNumberFormat.INTERNATIONAL)
print(f"\nBulk processing results:")
for result in batch_results:
status = "✓" if result.get("valid") else "✗"
formatted = result.get("formatted", "N/A")
print(f"{status} {result['input']} -> {formatted}")Install with Tessl CLI
npx tessl i tessl/pypi-phonenumbers