Python library for parsing, formatting, storing and validating international phone numbers with reduced memory footprint
—
Interactive phone number formatting for user interfaces, providing real-time formatting as users type phone numbers. This capability enables better user experience in forms and input fields.
The main class for providing real-time phone number formatting as users enter digits.
class AsYouTypeFormatter:
"""
A formatter that formats phone numbers as they are being typed.
Provides real-time formatting feedback for user interfaces,
automatically applying appropriate formatting patterns based
on the digits entered so far.
"""
def __init__(self, region_code: str):
"""
Initialize formatter for a specific region.
Parameters:
- region_code: Two-letter region code for formatting context
"""
def input_digit(self, next_char: str, remember_position: bool = False) -> str:
"""
Input a single digit and get the formatted result.
Parameters:
- next_char: Single character ('0'-'9', '+', etc.) to add
- remember_position: Whether to remember position for cursor tracking
Returns:
Formatted phone number string with the new character incorporated
"""
def clear(self) -> None:
"""
Clear the formatter state and prepare for new input.
Clears internal state so the formatter can be reused.
"""
def get_remembered_position(self) -> int:
"""
Get the position in formatted output of a remembered character.
Returns position of character that was input with remember_position=True.
Returns:
Position index in the formatted string
"""import phonenumbers
# Create formatter for US numbers
formatter = phonenumbers.AsYouTypeFormatter("US")
# Simulate user typing a phone number
digits = "6502532222"
print("As user types:")
for digit in digits:
result = formatter.input_digit(digit)
print(f" Input '{digit}' -> '{result}'")
# Output will show progressive formatting:
# Input '6' -> '6'
# Input '5' -> '65'
# Input '0' -> '650'
# Input '2' -> '650-2'
# Input '5' -> '650-25'
# Input '3' -> '650-253'
# Input '2' -> '650-2532'
# Input '2' -> '(650) 253-22'
# Input '2' -> '(650) 253-222'
# Input '2' -> '(650) 253-2222'import phonenumbers
# Format international number as user types
formatter = phonenumbers.AsYouTypeFormatter("US")
# User starts with international prefix
international_digits = "+442083661177"
print("International number formatting:")
for digit in international_digits:
result = formatter.input_digit(digit)
print(f" '{digit}' -> '{result}'")
# Shows progression from local to international formattingimport phonenumbers
class PhoneInputHandler:
"""Example implementation of interactive phone number input."""
def __init__(self, default_region="US"):
self.formatter = phonenumbers.AsYouTypeFormatter(default_region)
self.current_value = ""
def handle_digit_input(self, digit):
"""Handle single digit input from user."""
if digit.isdigit() or digit in "+":
formatted = self.formatter.input_digit(digit)
self.current_value = formatted
return formatted
return self.current_value
def handle_backspace(self):
"""Handle backspace/delete key."""
if self.current_value:
# Clear formatter and re-input all but last character
self.formatter.clear()
input_chars = [c for c in self.current_value if c.isdigit() or c == '+']
if input_chars:
input_chars.pop() # Remove last character
self.current_value = ""
for char in input_chars:
self.current_value = self.formatter.input_digit(char)
return self.current_value
return ""
def clear_input(self):
"""Clear all input."""
self.formatter.clear()
self.current_value = ""
return ""
def get_parsed_number(self):
"""Get parsed PhoneNumber object if input is valid."""
try:
# Remove formatting for parsing
clean_number = ''.join(filter(str.isdigit, self.current_value))
if self.current_value.startswith('+'):
clean_number = '+' + clean_number
return phonenumbers.parse(clean_number)
except phonenumbers.NumberParseException:
return None
def is_valid_number(self):
"""Check if current input represents a valid number."""
parsed = self.get_parsed_number()
if parsed:
return phonenumbers.is_valid_number(parsed)
return False
# Example usage
phone_input = PhoneInputHandler("US")
# Simulate user input
test_input = "6502532222"
print("Simulating phone input:")
for digit in test_input:
formatted = phone_input.handle_digit_input(digit)
is_valid = phone_input.is_valid_number()
print(f"Input: '{digit}' -> Display: '{formatted}' (Valid: {is_valid})")
# Test backspace
print("\nTesting backspace:")
for _ in range(3):
formatted = phone_input.handle_backspace()
is_valid = phone_input.is_valid_number()
print(f"After backspace: '{formatted}' (Valid: {is_valid})")import phonenumbers
class MultiRegionPhoneFormatter:
"""Phone formatter that can switch regions based on input."""
def __init__(self, default_region="US"):
self.default_region = default_region
self.current_region = default_region
self.formatter = phonenumbers.AsYouTypeFormatter(default_region)
self.input_buffer = ""
def detect_region_from_input(self, input_so_far):
"""Attempt to detect region from country code in input."""
if input_so_far.startswith('+'):
# Try to parse and determine region
try:
# Extract potential country code
digits_only = ''.join(filter(str.isdigit, input_so_far))
if len(digits_only) >= 1:
# Try different country code lengths
for cc_length in [1, 2, 3]:
if len(digits_only) >= cc_length:
potential_cc = int(digits_only[:cc_length])
region = phonenumbers.region_code_for_country_code(potential_cc)
if region and region != "ZZ":
return region
except (ValueError, TypeError):
pass
return self.default_region
def input_digit(self, digit):
"""Input digit with automatic region detection."""
self.input_buffer += digit
# Check if we need to switch regions
detected_region = self.detect_region_from_input(self.input_buffer)
if detected_region != self.current_region:
# Switch to new region and restart formatting
self.current_region = detected_region
self.formatter = phonenumbers.AsYouTypeFormatter(detected_region)
# Re-input all digits with new formatter
result = ""
for d in self.input_buffer:
result = self.formatter.input_digit(d)
return result
else:
return self.formatter.input_digit(digit)
def clear(self):
"""Clear formatter and reset to default region."""
self.input_buffer = ""
self.current_region = self.default_region
self.formatter = phonenumbers.AsYouTypeFormatter(self.default_region)
self.formatter.clear()
return ""
# Example usage
multi_formatter = MultiRegionPhoneFormatter("US")
# Test with US number
us_number = "6502532222"
print("US number formatting:")
for digit in us_number:
result = multi_formatter.input_digit(digit)
print(f" '{digit}' -> '{result}' (Region: {multi_formatter.current_region})")
print()
multi_formatter.clear()
# Test with UK number (starts with +44)
uk_number = "+442083661177"
print("UK number formatting:")
for digit in uk_number:
result = multi_formatter.input_digit(digit)
print(f" '{digit}' -> '{result}' (Region: {multi_formatter.current_region})")import phonenumbers
class PhoneNumberField:
"""Phone number field with real-time validation and formatting."""
def __init__(self, region="US", required=True):
self.region = region
self.required = required
self.formatter = phonenumbers.AsYouTypeFormatter(region)
self.raw_value = ""
self.formatted_value = ""
self.is_valid = False
self.validation_message = ""
def set_value(self, value):
"""Set the field value and update formatting."""
self.raw_value = value
self.formatter.clear()
# Apply formatting character by character
self.formatted_value = ""
for char in value:
if char.isdigit() or char == '+':
self.formatted_value = self.formatter.input_digit(char)
# Validate the result
self._validate()
return {
'formatted': self.formatted_value,
'is_valid': self.is_valid,
'message': self.validation_message
}
def _validate(self):
"""Internal validation logic."""
if not self.raw_value and self.required:
self.is_valid = False
self.validation_message = "Phone number is required"
return
if not self.raw_value:
self.is_valid = True
self.validation_message = ""
return
try:
parsed = phonenumbers.parse(self.raw_value, self.region)
if phonenumbers.is_valid_number(parsed):
self.is_valid = True
self.validation_message = ""
elif phonenumbers.is_possible_number(parsed):
self.is_valid = False
self.validation_message = "Phone number format is not valid"
else:
reason = phonenumbers.is_possible_number_with_reason(parsed)
if reason == phonenumbers.ValidationResult.TOO_SHORT:
self.validation_message = "Phone number is too short"
elif reason == phonenumbers.ValidationResult.TOO_LONG:
self.validation_message = "Phone number is too long"
else:
self.validation_message = "Invalid phone number"
self.is_valid = False
except phonenumbers.NumberParseException as e:
self.is_valid = False
if e.error_type == phonenumbers.NumberParseException.INVALID_COUNTRY_CODE:
self.validation_message = "Invalid country code"
elif e.error_type == phonenumbers.NumberParseException.NOT_A_NUMBER:
self.validation_message = "Not a valid phone number"
else:
self.validation_message = "Invalid phone number format"
# Example usage
phone_field = PhoneNumberField("US", required=True)
# Test various inputs
test_inputs = [
"", # Empty (required field)
"650", # Too short
"6502532222", # Valid US number
"+442083661177", # Valid UK number
"invalid", # Not a number
"65025322221234567", # Too long
]
for test_input in test_inputs:
result = phone_field.set_value(test_input)
print(f"Input: '{test_input}'")
print(f" Formatted: '{result['formatted']}'")
print(f" Valid: {result['is_valid']}")
print(f" Message: '{result['message']}'")
print()import phonenumbers
def create_phone_input_widget(name, region="US", placeholder=None):
"""Create HTML for phone input with JavaScript formatting."""
placeholder_text = placeholder or f"Enter {region} phone number"
html = f'''
<div class="phone-input-container">
<input type="tel"
name="{name}"
id="{name}"
placeholder="{placeholder_text}"
data-region="{region}"
class="phone-input">
<div class="validation-message" id="{name}-validation"></div>
</div>
<script>
// JavaScript integration would use phonenumbers library
// or server-side formatting via AJAX calls
document.getElementById('{name}').addEventListener('input', function(e) {{
// Format input in real-time
// Validate and show feedback
}});
</script>
'''
return html
# Server-side formatting endpoint
def format_phone_api(request):
"""API endpoint for real-time phone formatting."""
digit = request.GET.get('digit', '')
region = request.GET.get('region', 'US')
current_state = request.GET.get('state', '')
# In a real implementation, you'd maintain formatter state
# in session or pass it back and forth
formatter = phonenumbers.AsYouTypeFormatter(region)
# Restore previous state
for d in current_state:
if d.isdigit() or d == '+':
formatter.input_digit(d)
# Add new digit
if digit:
result = formatter.input_digit(digit)
return {'formatted': result, 'state': current_state + digit}
return {'formatted': current_state, 'state': current_state}Install with Tessl CLI
npx tessl i tessl/pypi-phonenumberslite