Added coercion_listener

This commit is contained in:
Konsta Vesterinen
2013-05-08 15:48:58 +03:00
parent c9c4979452
commit f0f894f205
9 changed files with 125 additions and 6 deletions

View File

@@ -4,6 +4,12 @@ Changelog
Here you can see the full list of changes between each SQLAlchemy-Utils release.
0.11.0 (2013-05-08)
^^^^^^^^^^^^^^^^^^^
- Added coercion_listener
0.10.0 (2013-04-29)
^^^^^^^^^^^^^^^^^^^

View File

@@ -6,7 +6,45 @@
SQLAlchemy-Utils
================
SQLAlchemy-Utils provides various utility classes and functions for SQLAlchemy.
SQLAlchemy-Utils provides custom data types and various utility functions for SQLAlchemy.
Using automatic data coercion
-----------------------------
SQLAlchemy-Utils provides various new data types for SQLAlchemy and in order to gain full
advantage of these datatypes you should use coercion_listener. Setting up the listener is easy:
::
import sqlalchemy as sa
from sqlalchemy_utils import coercion_listener
sa.event.listen(sa.orm.mapper, 'mapper_configured', coercion_listener)
The listener automatically detects SQLAlchemy-Utils compatible data types and coerces all attributes
using these types to appropriate objects.
Example
::
from colour import Color
from sqlalchemy_utils import ColorType
class Document(Base):
__tablename__ = 'player'
id = db.Column(db.Integer, autoincrement=True)
name = db.Column(db.Unicode(50))
background_color = db.Column(ColorType)
document = Document()
document.background_color = 'F5F5F5'
document.background_color # Color object
session.commit()
ScalarListType
--------------

View File

@@ -3,4 +3,3 @@ pytest==2.2.3
Pygments==1.2
Jinja2==2.3
docutils>=0.10
phonenumbers>=5.4b1

View File

@@ -1,4 +1,3 @@
SQLAlchemy>=0.7.8
psycopg2>=2.4.6
phonenumbers>=5.4b1
colour==0.0.2

View File

@@ -2,7 +2,7 @@
SQLAlchemy-Utils
----------------
Various utility functions for SQLAlchemy.
Various utility functions and custom data types for SQLAlchemy.
"""
from setuptools import setup, Command
@@ -24,7 +24,7 @@ class PyTest(Command):
setup(
name='SQLAlchemy-Utils',
version='0.10.0',
version='0.11.0',
url='https://github.com/kvesteri/sqlalchemy-utils',
license='BSD',
author='Konsta Vesterinen',
@@ -39,7 +39,6 @@ setup(
platforms='any',
install_requires=[
'SQLAlchemy>=0.7.8',
'psycopg2>=2.4.6',
'phonenumbers>=5.4b1',
'colour==0.0.2'
],

View File

@@ -1,4 +1,5 @@
from .functions import sort_query, defer_except, escape_like
from .listeners import coercion_listener
from .merge import merge, Merger
from .types import (
ColorType,
@@ -17,6 +18,7 @@ from .types import (
__all__ = (
coercion_listener,
sort_query,
defer_except,
escape_like,

View File

@@ -0,0 +1,19 @@
import sqlalchemy as sa
def coercion_listener(mapper, class_):
"""
Auto assigns coercing listener for all class properties which are of coerce
capable type.
"""
for prop in mapper.iterate_properties:
try:
listener = prop.columns[0].type.coercion_listener
except AttributeError:
continue
sa.event.listen(
getattr(class_, prop.key),
'set',
listener,
retval=True
)

View File

@@ -84,6 +84,11 @@ class PhoneNumberType(types.TypeDecorator):
return PhoneNumber(value, self.country_code)
return value
def coercion_listener(self, target, value, oldvalue, initiator):
if value is not None and not isinstance(value, PhoneNumber):
value = PhoneNumber(value, country_code=self.country_code)
return value
class ColorType(types.TypeDecorator):
"""
@@ -107,6 +112,11 @@ class ColorType(types.TypeDecorator):
return Color(value)
return value
def coercion_listener(self, target, value, oldvalue, initiator):
if value is not None and not isinstance(value, Color):
value = Color(value)
return value
class ScalarListException(Exception):
pass
@@ -174,6 +184,14 @@ class NumberRangeType(types.TypeDecorator):
return NumberRange.from_normalized_str(value)
return value
def coercion_listener(self, target, value, oldvalue, initiator):
if value is not None and not isinstance(value, NumberRange):
if isinstance(value, basestring):
value = NumberRange.from_normalized_str(value)
else:
raise TypeError
return value
class NumberRangeException(Exception):
pass

View File

@@ -0,0 +1,39 @@
from colour import Color
import sqlalchemy as sa
from sqlalchemy_utils import (
ColorType,
NumberRangeType,
NumberRange,
PhoneNumberType,
PhoneNumber,
coercion_listener
)
from tests import DatabaseTestCase
class TestCoercionListener(DatabaseTestCase):
def create_models(self):
class User(self.Base):
__tablename__ = 'user'
id = sa.Column(sa.Integer, primary_key=True)
fav_color = sa.Column(ColorType)
phone_number = sa.Column(PhoneNumberType(country_code='FI'))
number_of_friends = sa.Column(NumberRangeType)
def __repr__(self):
return 'User(%r)' % self.id
self.User = User
sa.event.listen(
sa.orm.mapper, 'mapper_configured', coercion_listener
)
def test_scalar_attributes_get_coerced_to_objects(self):
user = self.User(
fav_color='white',
phone_number='050111222',
number_of_friends='[12, 18]'
)
assert isinstance(user.fav_color, Color)
assert isinstance(user.phone_number, PhoneNumber)
assert isinstance(user.number_of_friends, NumberRange)