Merge branch 'master' into topics/password

Conflicts:
	setup.py
This commit is contained in:
Konsta Vesterinen
2013-07-23 10:35:43 +03:00
9 changed files with 169 additions and 9 deletions

View File

@@ -4,6 +4,24 @@ Changelog
Here you can see the full list of changes between each SQLAlchemy-Utils release. Here you can see the full list of changes between each SQLAlchemy-Utils release.
0.14.7 (2013-07-22)
^^^^^^^^^^^^^^^^^^^
- Lazy import for ipaddress package
0.14.6 (2013-07-22)
^^^^^^^^^^^^^^^^^^^
- Fixed UUID import issues
0.14.5 (2013-07-22)
^^^^^^^^^^^^^^^^^^^
- Added UUID type
0.14.4 (2013-07-03) 0.14.4 (2013-07-03)
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^

View File

@@ -173,6 +173,24 @@ NumberRange supports some arithmetic operators:
# '30-140' # '30-140'
UUIDType
--------
UUIDType will store a UUID in the database in a native format, if available,
or a 16-byte BINARY column or a 32-character CHAR column if not.
::
from sqlalchemy_utils import UUIDType
import uuid
class User(Base):
__tablename__ = 'user'
# Pass `binary=False` to fallback to CHAR instead of BINARY
id = sa.Column(UUIDType(binary=False), primary_key=True)
API Documentation API Documentation
----------------- -----------------

View File

@@ -24,7 +24,7 @@ class PyTest(Command):
setup( setup(
name='SQLAlchemy-Utils', name='SQLAlchemy-Utils',
version='0.14.4', version='0.14.7',
url='https://github.com/kvesteri/sqlalchemy-utils', url='https://github.com/kvesteri/sqlalchemy-utils',
license='BSD', license='BSD',
author='Konsta Vesterinen', author='Konsta Vesterinen',
@@ -33,7 +33,7 @@ setup(
'Various utility functions for SQLAlchemy.' 'Various utility functions for SQLAlchemy.'
), ),
long_description=__doc__, long_description=__doc__,
packages=['sqlalchemy_utils'], packages=['sqlalchemy_utils', 'sqlalchemy_utils.types'],
zip_safe=False, zip_safe=False,
include_package_data=True, include_package_data=True,
platforms='any', platforms='any',
@@ -55,8 +55,8 @@ setup(
'flexmock>=0.9.7', 'flexmock>=0.9.7',
], ],
'phone': ['phonenumbers3k==5.6b1'], 'phone': ['phonenumbers3k==5.6b1'],
'color': ['colour>=0.0.3'], 'password': ['passlib >= 1.6, < 2.0'],
'password': ['passlib >= 1.6, < 2.0'] 'color': ['colour>=0.0.4']
}, },
cmdclass={'test': PyTest}, cmdclass={'test': PyTest},
classifiers=[ classifiers=[

View File

@@ -21,11 +21,12 @@ from .types import (
NumberRangeType, NumberRangeType,
ScalarListType, ScalarListType,
ScalarListException, ScalarListException,
TSVectorType TSVectorType,
UUIDType,
) )
__version__ = '0.14.4' __version__ = '0.14.7'
__all__ = ( __all__ = (
@@ -48,11 +49,13 @@ __all__ = (
NumberRangeException, NumberRangeException,
NumberRangeRawType, NumberRangeRawType,
NumberRangeType, NumberRangeType,
Password,
PasswordType, PasswordType,
PhoneNumber, PhoneNumber,
PhoneNumberType, PhoneNumberType,
ProxyDict, ProxyDict,
ScalarListType, ScalarListType,
ScalarListException, ScalarListException,
TSVectorType TSVectorType,
UUIDType,
) )

View File

@@ -14,6 +14,7 @@ from .number_range import (
from .password import Password, PasswordType from .password import Password, PasswordType
from .phone_number import PhoneNumber, PhoneNumberType from .phone_number import PhoneNumber, PhoneNumberType
from .scalar_list import ScalarListException, ScalarListType from .scalar_list import ScalarListException, ScalarListType
from .uuid import UUIDType
__all__ = ( __all__ = (
@@ -30,6 +31,7 @@ __all__ = (
PhoneNumberType, PhoneNumberType,
ScalarListException, ScalarListException,
ScalarListType, ScalarListType,
UUIDType,
) )

View File

@@ -24,7 +24,8 @@ class ColorType(types.TypeDecorator):
# Bail if colour is not found. # Bail if colour is not found.
if colour is None: if colour is None:
raise ImproperlyConfigured( raise ImproperlyConfigured(
"'colour' is required to use 'ColorType'") "'colour' package is required to use 'ColorType'"
)
super(ColorType, self).__init__(*args, **kwargs) super(ColorType, self).__init__(*args, **kwargs)
self.impl = types.Unicode(max_length) self.impl = types.Unicode(max_length)

View File

@@ -1,6 +1,12 @@
import six import six
ipaddress = None
try:
import ipaddress import ipaddress
except:
pass
from sqlalchemy import types from sqlalchemy import types
from sqlalchemy_utils import ImproperlyConfigured
class IPAddressType(types.TypeDecorator): class IPAddressType(types.TypeDecorator):
@@ -11,6 +17,11 @@ class IPAddressType(types.TypeDecorator):
impl = types.Unicode(50) impl = types.Unicode(50)
def __init__(self, max_length=50, *args, **kwargs): def __init__(self, max_length=50, *args, **kwargs):
if not ipaddress:
raise ImproperlyConfigured(
"'ipaddress' package is required to use 'IPAddressType'"
)
super(IPAddressType, self).__init__(*args, **kwargs) super(IPAddressType, self).__init__(*args, **kwargs)
self.impl = types.Unicode(max_length) self.impl = types.Unicode(max_length)

View File

@@ -0,0 +1,66 @@
from __future__ import absolute_import
import uuid
from sqlalchemy import types
from sqlalchemy.dialects import postgresql
class UUIDType(types.TypeDecorator):
"""
Stores a UUID in the database natively when it can and falls back to
a BINARY(16) or a CHAR(32) when it can't.
"""
impl = types.BINARY(16)
python_type = uuid.UUID
def __init__(self, binary=True):
"""
:param binary: Whether to use a BINARY(16) or CHAR(32) fallback.
"""
self.binary = binary
def load_dialect_impl(self, dialect):
if dialect.name == 'postgresql':
# Use the native UUID type.
return dialect.type_descriptor(postgresql.UUID())
else:
# Fallback to either a BINARY or a CHAR.
kind = self.impl if self.binary else types.CHAR(32)
return dialect.type_descriptor(kind)
@staticmethod
def _coerce(value):
if value and not isinstance(value, uuid.UUID):
try:
value = uuid.UUID(value)
except (TypeError, ValueError):
value = uuid.UUID(bytes=value)
return value
def process_bind_param(self, value, dialect):
if value is None:
return value
if not isinstance(value, uuid.UUID):
value = self._coerce(value)
if dialect == 'postgresql':
return str(value)
return value.bytes if self.binary else value.hex
def process_result_value(self, value, dialect):
if value is None:
return value
if dialect == 'postgresql':
return uuid.UUID(value)
return uuid.UUID(bytes=value) if self.binary else uuid.UUID(value)
def coercion_listener(self, target, value, oldvalue, initiator):
return self._coerce(value)

41
tests/test_uuid.py Normal file
View File

@@ -0,0 +1,41 @@
import sqlalchemy as sa
from tests import TestCase
from sqlalchemy_utils import UUIDType, coercion_listener
import uuid
class TestUUIDType(TestCase):
def create_models(self):
class User(self.Base):
__tablename__ = 'user'
id = sa.Column(UUIDType, default=uuid.uuid4, primary_key=True)
def __repr__(self):
return 'User(%r)' % self.id
self.User = User
sa.event.listen(sa.orm.mapper, 'mapper_configured', coercion_listener)
def test_commit(self):
obj = self.User()
obj.id = uuid.uuid4().hex
self.session.add(obj)
self.session.commit()
u = self.session.query(self.User).one()
assert u.id == obj.id
def test_coerce(self):
obj = self.User()
obj.id = identifier = uuid.uuid4().hex
assert isinstance(obj.id, uuid.UUID)
assert obj.id.hex == identifier
obj.id = identifier = uuid.uuid4().bytes
assert isinstance(obj.id, uuid.UUID)
assert obj.id.bytes == identifier