Add support for PhoneNumber composites

This commit is contained in:
Konsta Vesterinen
2016-04-21 17:24:58 +03:00
parent c5b20535fd
commit c3a931b549
5 changed files with 87 additions and 10 deletions

View File

@@ -4,6 +4,12 @@ Changelog
Here you can see the full list of changes between each SQLAlchemy-Utils release.
0.32.3 (2016-04-20)
^^^^^^^^^^^^^^^^^^^
- Added support for PhoneNumber objects as composites
0.32.2 (2016-04-20)
^^^^^^^^^^^^^^^^^^^

View File

@@ -108,6 +108,8 @@ PhoneNumberType
.. module:: sqlalchemy_utils.types.phone_number
.. autoclass:: PhoneNumber
.. autoclass:: PhoneNumberType

View File

@@ -93,4 +93,4 @@ from .types import ( # noqa
WeekDaysType
)
__version__ = '0.32.2'
__version__ = '0.32.3'

View File

@@ -25,18 +25,46 @@ class PhoneNumber(BasePhoneNumber):
.. _Python phonenumbers library:
https://github.com/daviddrysdale/python-phonenumbers
::
from sqlalchemy_utils import PhoneNumber
class User(self.Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
name = sa.Column(sa.Unicode(255))
_phone_number = sa.Column(sa.Unicode(20))
country_code = sa.Column(sa.Unicode(8))
phonenumber = sa.orm.composite(
PhoneNumber,
_phone_number,
country_code
)
user = User(phone_number=PhoneNumber('0401234567', 'FI'))
user.phone_number.e164 # u'+358401234567'
user.phone_number.international # u'+358 40 1234567'
user.phone_number.national # u'040 1234567'
user.country_code # 'FI'
:param raw_number:
String representation of the phone number.
:param country_code:
Country code of the phone number.
:param region:
Region of the phone number.
'''
def __init__(self, raw_number, country_code=None):
def __init__(self, raw_number, region=None):
# Bail if phonenumbers is not found.
if phonenumbers is None:
raise ImproperlyConfigured(
"'phonenumbers' is required to use 'PhoneNumber'")
self._phone_number = phonenumbers.parse(raw_number, country_code)
self._phone_number = phonenumbers.parse(raw_number, region)
super(PhoneNumber, self).__init__(
country_code=self._phone_number.country_code,
national_number=self._phone_number.national_number,
@@ -48,6 +76,7 @@ class PhoneNumber(BasePhoneNumber):
self._phone_number.preferred_domestic_carrier_code
)
)
self.region = region
self.national = phonenumbers.format_number(
self._phone_number,
phonenumbers.PhoneNumberFormat.NATIONAL
@@ -61,6 +90,9 @@ class PhoneNumber(BasePhoneNumber):
phonenumbers.PhoneNumberFormat.E164
)
def __composite_values__(self):
return self.national, self.region
def is_valid_number(self):
return phonenumbers.is_valid_number(self._phone_number)
@@ -96,20 +128,20 @@ class PhoneNumberType(types.TypeDecorator, ScalarCoercible):
def python_type(self, text):
return self._coerce(text)
def __init__(self, country_code='US', max_length=20, *args, **kwargs):
def __init__(self, region='US', max_length=20, *args, **kwargs):
# Bail if phonenumbers is not found.
if phonenumbers is None:
raise ImproperlyConfigured(
"'phonenumbers' is required to use 'PhoneNumberType'")
super(PhoneNumberType, self).__init__(*args, **kwargs)
self.country_code = country_code
self.region = region
self.impl = types.Unicode(max_length)
def process_bind_param(self, value, dialect):
if value:
if not isinstance(value, PhoneNumber):
value = PhoneNumber(value, country_code=self.country_code)
value = PhoneNumber(value, region=self.region)
if self.STORE_FORMAT == 'e164' and value.extension:
return '%s;ext=%s' % (value.e164, value.extension)
@@ -120,11 +152,11 @@ class PhoneNumberType(types.TypeDecorator, ScalarCoercible):
def process_result_value(self, value, dialect):
if value:
return PhoneNumber(value, self.country_code)
return PhoneNumber(value, self.region)
return value
def _coerce(self, value):
if value and not isinstance(value, PhoneNumber):
value = PhoneNumber(value, country_code=self.country_code)
value = PhoneNumber(value, region=self.region)
return value or None

View File

@@ -148,3 +148,40 @@ class TestPhoneNumberType(object):
user = User(phone_number='050111222')
assert isinstance(user.phone_number, PhoneNumber)
@pytest.mark.skipif('types.phone_number.phonenumbers is None')
class TestPhoneNumberComposite(object):
@pytest.fixture
def User(self, Base):
class User(Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
name = sa.Column(sa.String(255))
_phone_number = sa.Column(sa.String(255))
country = sa.Column(sa.String(255))
phone_number = sa.orm.composite(
PhoneNumber,
_phone_number,
country
)
return User
@pytest.fixture
def user(self, session, User):
user = User()
user.name = u'Someone'
user.phone_number = PhoneNumber('+35840111222', 'FI')
session.add(user)
session.commit()
return user
def test_query_returns_phone_number_object(
self,
session,
User,
user
):
queried_user = session.query(User).first()
assert queried_user.phone_number.national == '040 111222'
assert queried_user.phone_number.region == 'FI'