Initial UUID type implementation.
This commit is contained in:
@@ -19,7 +19,8 @@ from .types import (
|
|||||||
NumberRangeType,
|
NumberRangeType,
|
||||||
ScalarListType,
|
ScalarListType,
|
||||||
ScalarListException,
|
ScalarListException,
|
||||||
TSVectorType
|
TSVectorType,
|
||||||
|
UUIDType,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -51,5 +52,6 @@ __all__ = (
|
|||||||
ProxyDict,
|
ProxyDict,
|
||||||
ScalarListType,
|
ScalarListType,
|
||||||
ScalarListException,
|
ScalarListException,
|
||||||
TSVectorType
|
TSVectorType,
|
||||||
|
UUIDType,
|
||||||
)
|
)
|
||||||
|
@@ -13,6 +13,7 @@ from .number_range import (
|
|||||||
)
|
)
|
||||||
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__ = (
|
||||||
@@ -27,6 +28,7 @@ __all__ = (
|
|||||||
PhoneNumberType,
|
PhoneNumberType,
|
||||||
ScalarListException,
|
ScalarListException,
|
||||||
ScalarListType,
|
ScalarListType,
|
||||||
|
UUIDType,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
65
sqlalchemy_utils/types/uuid.py
Normal file
65
sqlalchemy_utils/types/uuid.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
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 = types.BINARY(16) 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(None, value, None, None)
|
||||||
|
|
||||||
|
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