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.
|
||||
|
||||
|
||||
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)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@@ -173,6 +173,24 @@ NumberRange supports some arithmetic operators:
|
||||
# '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
|
||||
-----------------
|
||||
|
||||
|
8
setup.py
8
setup.py
@@ -24,7 +24,7 @@ class PyTest(Command):
|
||||
|
||||
setup(
|
||||
name='SQLAlchemy-Utils',
|
||||
version='0.14.4',
|
||||
version='0.14.7',
|
||||
url='https://github.com/kvesteri/sqlalchemy-utils',
|
||||
license='BSD',
|
||||
author='Konsta Vesterinen',
|
||||
@@ -33,7 +33,7 @@ setup(
|
||||
'Various utility functions for SQLAlchemy.'
|
||||
),
|
||||
long_description=__doc__,
|
||||
packages=['sqlalchemy_utils'],
|
||||
packages=['sqlalchemy_utils', 'sqlalchemy_utils.types'],
|
||||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
platforms='any',
|
||||
@@ -55,8 +55,8 @@ setup(
|
||||
'flexmock>=0.9.7',
|
||||
],
|
||||
'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},
|
||||
classifiers=[
|
||||
|
@@ -21,11 +21,12 @@ from .types import (
|
||||
NumberRangeType,
|
||||
ScalarListType,
|
||||
ScalarListException,
|
||||
TSVectorType
|
||||
TSVectorType,
|
||||
UUIDType,
|
||||
)
|
||||
|
||||
|
||||
__version__ = '0.14.4'
|
||||
__version__ = '0.14.7'
|
||||
|
||||
|
||||
__all__ = (
|
||||
@@ -48,11 +49,13 @@ __all__ = (
|
||||
NumberRangeException,
|
||||
NumberRangeRawType,
|
||||
NumberRangeType,
|
||||
Password,
|
||||
PasswordType,
|
||||
PhoneNumber,
|
||||
PhoneNumberType,
|
||||
ProxyDict,
|
||||
ScalarListType,
|
||||
ScalarListException,
|
||||
TSVectorType
|
||||
TSVectorType,
|
||||
UUIDType,
|
||||
)
|
||||
|
@@ -14,6 +14,7 @@ from .number_range import (
|
||||
from .password import Password, PasswordType
|
||||
from .phone_number import PhoneNumber, PhoneNumberType
|
||||
from .scalar_list import ScalarListException, ScalarListType
|
||||
from .uuid import UUIDType
|
||||
|
||||
|
||||
__all__ = (
|
||||
@@ -30,6 +31,7 @@ __all__ = (
|
||||
PhoneNumberType,
|
||||
ScalarListException,
|
||||
ScalarListType,
|
||||
UUIDType,
|
||||
)
|
||||
|
||||
|
||||
|
@@ -24,7 +24,8 @@ class ColorType(types.TypeDecorator):
|
||||
# Bail if colour is not found.
|
||||
if colour is None:
|
||||
raise ImproperlyConfigured(
|
||||
"'colour' is required to use 'ColorType'")
|
||||
"'colour' package is required to use 'ColorType'"
|
||||
)
|
||||
|
||||
super(ColorType, self).__init__(*args, **kwargs)
|
||||
self.impl = types.Unicode(max_length)
|
||||
|
@@ -1,6 +1,12 @@
|
||||
import six
|
||||
import ipaddress
|
||||
|
||||
ipaddress = None
|
||||
try:
|
||||
import ipaddress
|
||||
except:
|
||||
pass
|
||||
from sqlalchemy import types
|
||||
from sqlalchemy_utils import ImproperlyConfigured
|
||||
|
||||
|
||||
class IPAddressType(types.TypeDecorator):
|
||||
@@ -11,6 +17,11 @@ class IPAddressType(types.TypeDecorator):
|
||||
impl = types.Unicode(50)
|
||||
|
||||
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)
|
||||
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