-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feature: Add phone number validator * comment and import cleanup * comment and import cleanup * fix: Add typing extension fallback for python 3.8 * fix: Lint * fix: Make mypy happy * chore: Move json schema tests to json schema file
- Loading branch information
Showing
4 changed files
with
282 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
from typing import Any, Optional, Union | ||
|
||
try: | ||
from typing import Annotated | ||
except ImportError: | ||
# Python 3.8 | ||
from typing_extensions import Annotated | ||
|
||
|
||
import phonenumbers | ||
import pytest | ||
from phonenumbers import PhoneNumber | ||
from pydantic import BaseModel, TypeAdapter, ValidationError | ||
|
||
from pydantic_extra_types.phone_numbers import PhoneNumberValidator | ||
|
||
Number = Annotated[Union[str, PhoneNumber], PhoneNumberValidator()] | ||
NANumber = Annotated[ | ||
Union[str, PhoneNumber], | ||
PhoneNumberValidator( | ||
supported_regions=['US', 'CA'], | ||
default_region='US', | ||
), | ||
] | ||
UKNumber = Annotated[ | ||
Union[str, PhoneNumber], | ||
PhoneNumberValidator( | ||
supported_regions=['GB'], | ||
default_region='GB', | ||
number_format='E164', | ||
), | ||
] | ||
|
||
number_adapter = TypeAdapter(Number) | ||
|
||
|
||
class Numbers(BaseModel): | ||
phone_number: Optional[Number] = None | ||
na_number: Optional[NANumber] = None | ||
uk_number: Optional[UKNumber] = None | ||
|
||
|
||
def test_validator_constructor() -> None: | ||
PhoneNumberValidator() | ||
PhoneNumberValidator(supported_regions=['US', 'CA'], default_region='US') | ||
PhoneNumberValidator(supported_regions=['GB'], default_region='GB', number_format='E164') | ||
with pytest.raises(ValueError, match='Invalid default region code: XX'): | ||
PhoneNumberValidator(default_region='XX') | ||
with pytest.raises(ValueError, match='Invalid number format: XX'): | ||
PhoneNumberValidator(number_format='XX') | ||
with pytest.raises(ValueError, match='Invalid supported region code: XX'): | ||
PhoneNumberValidator(supported_regions=['XX']) | ||
|
||
|
||
# Note: the 555 area code will result in an invalid phone number | ||
def test_valid_phone_number() -> None: | ||
Numbers(phone_number='+1 901 555 1212') | ||
|
||
|
||
def test_when_extension_provided() -> None: | ||
Numbers(phone_number='+1 901 555 1212 ext 12533') | ||
|
||
|
||
def test_when_phonenumber_instance() -> None: | ||
phone_number = phonenumbers.parse('+1 901 555 1212', region='US') | ||
numbers = Numbers(phone_number=phone_number) | ||
assert numbers.phone_number == 'tel:+1-901-555-1212' | ||
# Additional validation is still performed on the instance | ||
with pytest.raises(ValidationError, match='value is not from a supported region'): | ||
Numbers(uk_number=phone_number) | ||
|
||
|
||
@pytest.mark.parametrize('invalid_number', ['', '123', 12, object(), '55 121']) | ||
def test_invalid_phone_number(invalid_number: Any) -> None: | ||
# Use a TypeAdapter to test the validation logic for None otherwise | ||
# optional fields will not attempt to validate | ||
with pytest.raises(ValidationError, match='value is not a valid phone number'): | ||
number_adapter.validate_python(invalid_number) | ||
|
||
|
||
def test_formats_phone_number() -> None: | ||
result = Numbers(phone_number='+1 901 555 1212 ext 12533', uk_number='+44 20 7946 0958') | ||
assert result.phone_number == 'tel:+1-901-555-1212;ext=12533' | ||
assert result.uk_number == '+442079460958' | ||
|
||
|
||
def test_default_region() -> None: | ||
result = Numbers(na_number='901 555 1212') | ||
assert result.na_number == 'tel:+1-901-555-1212' | ||
with pytest.raises(ValidationError, match='value is not a valid phone number'): | ||
Numbers(phone_number='901 555 1212') | ||
|
||
|
||
def test_supported_regions() -> None: | ||
assert Numbers(na_number='+1 901 555 1212') | ||
assert Numbers(uk_number='+44 20 7946 0958') | ||
with pytest.raises(ValidationError, match='value is not from a supported region'): | ||
Numbers(na_number='+44 20 7946 0958') | ||
|
||
|
||
def test_parse_error() -> None: | ||
with pytest.raises(ValidationError, match='value is not a valid phone number'): | ||
Numbers(phone_number='555 1212') | ||
|
||
|
||
def test_parsed_but_not_a_valid_number() -> None: | ||
with pytest.raises(ValidationError, match='value is not a valid phone number'): | ||
Numbers(phone_number='+1 555-1212') |