diff --git a/sqlalchemy_utils/__init__.py b/sqlalchemy_utils/__init__.py index 91be58f..8ee83a6 100644 --- a/sqlalchemy_utils/__init__.py +++ b/sqlalchemy_utils/__init__.py @@ -11,6 +11,52 @@ from sqlalchemy.sql.expression import desc, asc from sqlalchemy import types +class PhoneNumber(phonenumbers.phonenumber.PhoneNumber): + ''' + Extends a PhoneNumber class from `Python phonenumbers library`_. Adds + different phone number formats to attributes, so they can be easily used + in templates. Phone number validation method is also implemented. + + Takes the raw phone number and country code as params and parses them + into a PhoneNumber object. + + .. _Python phonenumbers library: + https://github.com/daviddrysdale/python-phonenumbers + + :param raw_number: + String representation of the phone number. + :param country_code: + Country code of the phone number. + ''' + def __init__(self, raw_number, country_code=None): + self._phone_number = phonenumbers.parse(raw_number, country_code) + super(PhoneNumber, self).__init__( + country_code=self._phone_number.country_code, + national_number=self._phone_number.national_number, + extension=self._phone_number.extension, + italian_leading_zero=self._phone_number.italian_leading_zero, + raw_input=self._phone_number.raw_input, + country_code_source=self._phone_number.country_code_source, + preferred_domestic_carrier_code= + self._phone_number.preferred_domestic_carrier_code + ) + self.national = phonenumbers.format_number( + self._phone_number, + phonenumbers.PhoneNumberFormat.NATIONAL + ) + self.international = phonenumbers.format_number( + self._phone_number, + phonenumbers.PhoneNumberFormat.INTERNATIONAL + ) + self.e164 = phonenumbers.format_number( + self._phone_number, + phonenumbers.PhoneNumberFormat.E164 + ) + + def is_valid_number(self): + return phonenumbers.is_valid_number(self._phone_number) + + class PhoneNumberType(types.TypeDecorator): """ Changes PhoneNumber objects to a string representation on the way in and @@ -18,7 +64,7 @@ class PhoneNumberType(types.TypeDecorator): as storing format, no country code is needed for parsing the database value to PhoneNumber object. """ - STORE_FORMAT = phonenumbers.PhoneNumberFormat.E164 + STORE_FORMAT = 'e164' impl = types.Unicode(20) def __init__(self, country_code='US', max_length=20, *args, **kwargs): @@ -27,18 +73,10 @@ class PhoneNumberType(types.TypeDecorator): self.impl = types.Unicode(max_length) def process_bind_param(self, value, dialect): - return phonenumbers.format_number( - value, - self.STORE_FORMAT - ) + return getattr(value, self.STORE_FORMAT) def process_result_value(self, value, dialect): - if self.STORE_FORMAT == phonenumbers.PhoneNumberFormat.E164: - return phonenumbers.parse(value) - return phonenumbers.parse( - value, - self.country_code - ) + return PhoneNumber(value, self.country_code) class InstrumentedList(_InstrumentedList): diff --git a/tests.py b/tests.py index 36d0070..dce5c7a 100644 --- a/tests.py +++ b/tests.py @@ -1,4 +1,3 @@ -import phonenumbers import sqlalchemy as sa from sqlalchemy import create_engine @@ -9,6 +8,7 @@ from sqlalchemy_utils import ( escape_like, sort_query, InstrumentedList, + PhoneNumber, PhoneNumberType, merge ) @@ -120,10 +120,48 @@ class TestSortQuery(TestCase): assert 'category.name ASC' in str(sorted_query) +class TestPhoneNumber(object): + def setup_method(self, method): + self.valid_phone_numbers = [ + '040 1234567', + '+358 401234567', + '09 2501234', + '+358 92501234', + '0800 939393', + '09 4243 0456', + '0600 900 500' + ] + self.invalid_phone_numbers = [ + 'abc', + '+040 1234567', + '0111234567', + '358' + ] + + def test_valid_phone_numbers(self): + for raw_number in self.valid_phone_numbers: + phone_number = PhoneNumber(raw_number, 'FI') + assert phone_number.is_valid_number() + + def test_invalid_phone_numbers(self): + for raw_number in self.invalid_phone_numbers: + try: + phone_number = PhoneNumber(raw_number, 'FI') + assert not phone_number.is_valid_number() + except: + pass + + def test_phone_number_attributes(self): + phone_number = PhoneNumber('+358401234567') + assert phone_number.e164 == u'+358401234567' + assert phone_number.international == u'+358 40 1234567' + assert phone_number.national == u'040 1234567' + + class TestPhoneNumberType(TestCase): def setup_method(self, method): super(TestPhoneNumberType, self).setup_method(method) - self.phone_number = phonenumbers.parse( + self.phone_number = PhoneNumber( '040 1234567', 'FI' )