Added CaseInsensitiveComparator + Email type
This commit is contained in:
@@ -4,6 +4,13 @@ Changelog
|
||||
Here you can see the full list of changes between each SQLAlchemy-Utils release.
|
||||
|
||||
|
||||
0.9.0 (2013-04-11)
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- Added CaseInsensitiveComparator
|
||||
- Added Email type
|
||||
|
||||
|
||||
0.8.4 (2013-04-08)
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
2
setup.py
2
setup.py
@@ -24,7 +24,7 @@ class PyTest(Command):
|
||||
|
||||
setup(
|
||||
name='SQLAlchemy-Utils',
|
||||
version='0.8.4',
|
||||
version='0.9',
|
||||
url='https://github.com/kvesteri/sqlalchemy-utils',
|
||||
license='BSD',
|
||||
author='Konsta Vesterinen',
|
||||
|
@@ -1,6 +1,7 @@
|
||||
from .functions import sort_query, defer_except, escape_like
|
||||
from .merge import merge, Merger
|
||||
from .types import (
|
||||
Email,
|
||||
instrumented_list,
|
||||
InstrumentedList,
|
||||
PhoneNumber,
|
||||
@@ -20,6 +21,7 @@ __all__ = (
|
||||
escape_like,
|
||||
instrumented_list,
|
||||
merge,
|
||||
Email,
|
||||
InstrumentedList,
|
||||
Merger,
|
||||
NumberRange,
|
||||
|
50
sqlalchemy_utils/operators.py
Normal file
50
sqlalchemy_utils/operators.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
class CaseInsensitiveComparator(sa.Unicode.Comparator):
|
||||
@classmethod
|
||||
def lowercase_arg(cls, func):
|
||||
def operation(self, other, **kwargs):
|
||||
if other is None:
|
||||
return getattr(sa.Unicode.Comparator, func)(
|
||||
self, other, **kwargs
|
||||
)
|
||||
return getattr(sa.Unicode.Comparator, func)(
|
||||
self, sa.func.lower(other), **kwargs
|
||||
)
|
||||
return operation
|
||||
|
||||
def in_(self, other):
|
||||
if isinstance(other, list) or isinstance(other, tuple):
|
||||
other = map(sa.func.lower, other)
|
||||
return sa.Unicode.Comparator.in_(self, other)
|
||||
|
||||
def notin_(self, other):
|
||||
if isinstance(other, list) or isinstance(other, tuple):
|
||||
other = map(sa.func.lower, other)
|
||||
return sa.Unicode.Comparator.notin_(self, other)
|
||||
|
||||
|
||||
string_operator_funcs = [
|
||||
'__eq__',
|
||||
'__ne__',
|
||||
'__lt__',
|
||||
'__le__',
|
||||
'__gt__',
|
||||
'__ge__',
|
||||
'concat',
|
||||
'contains',
|
||||
'ilike',
|
||||
'like',
|
||||
'notlike',
|
||||
'notilike',
|
||||
'startswith',
|
||||
'endswith',
|
||||
]
|
||||
|
||||
for func in string_operator_funcs:
|
||||
setattr(
|
||||
CaseInsensitiveComparator,
|
||||
func,
|
||||
CaseInsensitiveComparator.lowercase_arg(func)
|
||||
)
|
@@ -3,6 +3,7 @@ from functools import wraps
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm.collections import InstrumentedList as _InstrumentedList
|
||||
from sqlalchemy import types
|
||||
from .operators import CaseInsensitiveComparator
|
||||
|
||||
|
||||
class PhoneNumber(phonenumbers.phonenumber.PhoneNumber):
|
||||
@@ -118,6 +119,16 @@ class ScalarList(types.TypeDecorator):
|
||||
)
|
||||
|
||||
|
||||
class Email(sa.types.TypeDecorator):
|
||||
impl = sa.Unicode(255)
|
||||
comparator_factory = CaseInsensitiveComparator
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
if value is not None:
|
||||
return value.lower()
|
||||
return value
|
||||
|
||||
|
||||
class NumberRangeRawType(types.UserDefinedType):
|
||||
"""
|
||||
Raw number range type, only supports PostgreSQL for now.
|
||||
|
44
tests/test_case_insensitive_comparator.py
Normal file
44
tests/test_case_insensitive_comparator.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy_utils import Email
|
||||
from tests import DatabaseTestCase
|
||||
|
||||
|
||||
class TestCaseInsensitiveComparator(DatabaseTestCase):
|
||||
def create_models(self):
|
||||
class User(self.Base):
|
||||
__tablename__ = 'user'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
email = sa.Column(Email)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Building(%r)' % self.id
|
||||
|
||||
self.User = User
|
||||
|
||||
def test_supports_equals(self):
|
||||
query = (
|
||||
self.session.query(self.User)
|
||||
.filter(self.User.email == u'email@example.com')
|
||||
)
|
||||
|
||||
assert '"user".email = lower(:lower_1)' in str(query)
|
||||
|
||||
def test_supports_in_(self):
|
||||
query = (
|
||||
self.session.query(self.User)
|
||||
.filter(self.User.email.in_([u'email@example.com', u'a']))
|
||||
)
|
||||
assert (
|
||||
'"user".email IN (lower(:lower_1), lower(:lower_2))'
|
||||
in str(query)
|
||||
)
|
||||
|
||||
def test_supports_notin_(self):
|
||||
query = (
|
||||
self.session.query(self.User)
|
||||
.filter(self.User.email.notin_([u'email@example.com', u'a']))
|
||||
)
|
||||
assert (
|
||||
'"user".email NOT IN (lower(:lower_1), lower(:lower_2))'
|
||||
in str(query)
|
||||
)
|
27
tests/test_email.py
Normal file
27
tests/test_email.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy_utils import Email
|
||||
from tests import DatabaseTestCase
|
||||
|
||||
|
||||
class TestEmailType(DatabaseTestCase):
|
||||
def create_models(self):
|
||||
class User(self.Base):
|
||||
__tablename__ = 'user'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
email = sa.Column(Email)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Building(%r)' % self.id
|
||||
|
||||
self.User = User
|
||||
|
||||
def test_saves_email_as_lowercased(self):
|
||||
user = self.User(
|
||||
email=u'Someone@example.com'
|
||||
)
|
||||
|
||||
self.session.add(user)
|
||||
self.session.commit()
|
||||
|
||||
user = self.session.query(self.User).first()
|
||||
assert user.email == u'someone@example.com'
|
@@ -28,6 +28,21 @@ class TestNumberRangeType(DatabaseTestCase):
|
||||
assert building.persons_at_night.min_value == 1
|
||||
assert building.persons_at_night.max_value == 3
|
||||
|
||||
def test_nullify_number_range(self):
|
||||
building = self.Building(
|
||||
persons_at_night=NumberRange(1, 3)
|
||||
)
|
||||
|
||||
self.session.add(building)
|
||||
self.session.commit()
|
||||
|
||||
building = self.session.query(self.Building).first()
|
||||
building.persons_at_night = None
|
||||
self.session.commit()
|
||||
|
||||
building = self.session.query(self.Building).first()
|
||||
assert building.persons_at_night is None
|
||||
|
||||
|
||||
class TestNumberRange(object):
|
||||
def test_equality_operator(self):
|
||||
|
Reference in New Issue
Block a user