Merge branch 'master' into topics/password
Conflicts: setup.py
This commit is contained in:
18
CHANGES.rst
18
CHANGES.rst
@@ -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)
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@@ -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
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
8
setup.py
8
setup.py
@@ -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=[
|
||||||
|
@@ -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,
|
||||||
)
|
)
|
||||||
|
@@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
@@ -1,6 +1,12 @@
|
|||||||
import six
|
import six
|
||||||
import ipaddress
|
|
||||||
|
ipaddress = None
|
||||||
|
try:
|
||||||
|
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)
|
||||||
|
|
||||||
|
66
sqlalchemy_utils/types/uuid.py
Normal file
66
sqlalchemy_utils/types/uuid.py
Normal 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
41
tests/test_uuid.py
Normal 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
|
Reference in New Issue
Block a user