CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-phonenumberslite

Python library for parsing, formatting, storing and validating international phone numbers with reduced memory footprint

Pending
Overview
Eval results
Files

as-you-type-formatting.mddocs/

As-You-Type Formatting

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.

Capabilities

AsYouTypeFormatter Class

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
        """

Usage Examples

Basic As-You-Type Formatting

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'

International Number Formatting

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 formatting

Interactive Phone Input Implementation

import 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})")

Multi-Region Support

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})")

Form Validation Integration

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()

Integration Patterns

Web Framework Integration

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

docs

as-you-type-formatting.md

core-parsing-formatting.md

index.md

number-validation.md

phone-number-matching.md

region-metadata.md

short-numbers.md

utility-functions.md

tile.json