Files
deb-python-sqlalchemy-utils/sqlalchemy_utils/types.py
2013-03-26 13:03:43 +02:00

180 lines
5.5 KiB
Python

import phonenumbers
from functools import wraps
from sqlalchemy.orm.collections import InstrumentedList as _InstrumentedList
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
changes them back to PhoneNumber objects on the way out. If E164 is used
as storing format, no country code is needed for parsing the database
value to PhoneNumber object.
"""
STORE_FORMAT = 'e164'
impl = types.Unicode(20)
def __init__(self, country_code='US', max_length=20, *args, **kwargs):
super(PhoneNumberType, self).__init__(*args, **kwargs)
self.country_code = country_code
self.impl = types.Unicode(max_length)
def process_bind_param(self, value, dialect):
return getattr(value, self.STORE_FORMAT)
def process_result_value(self, value, dialect):
return PhoneNumber(value, self.country_code)
class NumberRangeRawType(types.UserDefinedType):
"""
Raw number range type, only supports PostgreSQL for now.
"""
def get_col_spec(self):
return 'int4range'
class NumberRangeType(types.TypeDecorator):
impl = NumberRangeRawType
def process_bind_param(self, value, dialect):
return value
def process_result_value(self, value, dialect):
return NumberRange.from_normalized_str(value)
class NumberRange(object):
def __init__(self, min_value, max_value):
self.min_value = min_value
self.max_value = max_value
@classmethod
def from_normalized_str(cls, value):
if value is not None:
values = value[1:-1].split(',')
min_value, max_value = map(
lambda a: int(a.strip()), values
)
if value[0] == '(':
min_value += 1
if value[1] == ')':
max_value -= 1
return cls(min_value, max_value)
@classmethod
def from_str(cls, value):
if value is not None:
values = value.split('-')
min_value, max_value = map(
lambda a: int(a.strip()), values
)
return cls(min_value, max_value)
def __repr__(self):
return 'NumberRange(%r, %r)' % (self.min_value, self.max_value)
def __str__(self):
return '[%s, %s]' % (self.min_value, self.max_value)
def __add__(self, other):
try:
return NumberRange(
self.min_value + other.min_value,
self.max_value + other.max_value
)
except AttributeError:
return NotImplemented
def __iadd__(self, other):
try:
self.min_value += other.min_value
self.max_value += other.max_value
return self
except AttributeError:
return NotImplemented
def __sub__(self, other):
try:
return NumberRange(
self.min_value - other.min_value,
self.max_value - other.max_value
)
except AttributeError:
return NotImplemented
def __isub__(self, other):
try:
self.min_value -= other.min_value
self.max_value -= other.max_value
return self
except AttributeError:
return NotImplemented
class InstrumentedList(_InstrumentedList):
"""Enhanced version of SQLAlchemy InstrumentedList. Provides some
additional functionality."""
def any(self, attr):
return any(getattr(item, attr) for item in self)
def all(self, attr):
return all(getattr(item, attr) for item in self)
def instrumented_list(f):
@wraps(f)
def wrapper(*args, **kwargs):
return InstrumentedList([item for item in f(*args, **kwargs)])
return wrapper