From 17603a2f416e3309434b82353c5dcc4d34a652b3 Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Mon, 15 Jul 2013 11:57:21 -0700 Subject: [PATCH 01/11] Initial UUID type implementation. --- sqlalchemy_utils/__init__.py | 6 ++- sqlalchemy_utils/types/__init__.py | 2 + sqlalchemy_utils/types/uuid.py | 65 ++++++++++++++++++++++++++++++ tests/test_uuid.py | 41 +++++++++++++++++++ 4 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 sqlalchemy_utils/types/uuid.py create mode 100644 tests/test_uuid.py diff --git a/sqlalchemy_utils/__init__.py b/sqlalchemy_utils/__init__.py index cf659af..aef926e 100644 --- a/sqlalchemy_utils/__init__.py +++ b/sqlalchemy_utils/__init__.py @@ -19,7 +19,8 @@ from .types import ( NumberRangeType, ScalarListType, ScalarListException, - TSVectorType + TSVectorType, + UUIDType, ) @@ -51,5 +52,6 @@ __all__ = ( ProxyDict, ScalarListType, ScalarListException, - TSVectorType + TSVectorType, + UUIDType, ) diff --git a/sqlalchemy_utils/types/__init__.py b/sqlalchemy_utils/types/__init__.py index 5cf5c49..96d9d95 100644 --- a/sqlalchemy_utils/types/__init__.py +++ b/sqlalchemy_utils/types/__init__.py @@ -13,6 +13,7 @@ from .number_range import ( ) from .phone_number import PhoneNumber, PhoneNumberType from .scalar_list import ScalarListException, ScalarListType +from .uuid import UUIDType __all__ = ( @@ -27,6 +28,7 @@ __all__ = ( PhoneNumberType, ScalarListException, ScalarListType, + UUIDType, ) diff --git a/sqlalchemy_utils/types/uuid.py b/sqlalchemy_utils/types/uuid.py new file mode 100644 index 0000000..43cfb4e --- /dev/null +++ b/sqlalchemy_utils/types/uuid.py @@ -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) diff --git a/tests/test_uuid.py b/tests/test_uuid.py new file mode 100644 index 0000000..e07465e --- /dev/null +++ b/tests/test_uuid.py @@ -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 From bfe1ec97ffd593035ddf616a9689fe5f5748d406 Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Mon, 15 Jul 2013 12:00:59 -0700 Subject: [PATCH 02/11] Add documentation blurb for UUIDType. --- docs/index.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index a7584bf..495ee72 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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 ----------------- From e9fa07f30e03e5be0d224300e1a7e8fde4d0e2f2 Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Mon, 15 Jul 2013 12:22:27 -0700 Subject: [PATCH 03/11] Add types sub-package to setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c61996c..fe2e4a3 100644 --- a/setup.py +++ b/setup.py @@ -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', From 2d403544fafcf5e52760984f0bf454e45f3fb336 Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Mon, 15 Jul 2013 15:06:26 -0700 Subject: [PATCH 04/11] Have impl be complete. --- sqlalchemy_utils/types/uuid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlalchemy_utils/types/uuid.py b/sqlalchemy_utils/types/uuid.py index 43cfb4e..2b4c03c 100644 --- a/sqlalchemy_utils/types/uuid.py +++ b/sqlalchemy_utils/types/uuid.py @@ -9,7 +9,7 @@ class UUIDType(types.TypeDecorator): a BINARY(16) or a CHAR(32) when it can't. """ - impl = types.BINARY + impl = types.BINARY(16) python_type = uuid.UUID @@ -26,7 +26,7 @@ class UUIDType(types.TypeDecorator): else: # Fallback to either a BINARY or a CHAR. - kind = types.BINARY(16) if self.binary else types.CHAR(32) + kind = self.impl if self.binary else types.CHAR(32) return dialect.type_descriptor(kind) @staticmethod From 5bd6f0d1fb4fd990bb01de8436e5ed027c9b9811 Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Mon, 15 Jul 2013 16:56:45 -0700 Subject: [PATCH 05/11] Fix invocation. --- sqlalchemy_utils/types/uuid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlalchemy_utils/types/uuid.py b/sqlalchemy_utils/types/uuid.py index 2b4c03c..d5d7d7e 100644 --- a/sqlalchemy_utils/types/uuid.py +++ b/sqlalchemy_utils/types/uuid.py @@ -45,7 +45,7 @@ class UUIDType(types.TypeDecorator): return value if not isinstance(value, uuid.UUID): - value = self._coerce(None, value, None, None) + value = self._coerce(value) if dialect == 'postgresql': return str(value) From 0daae1d425268a9b4c981882e8b8ef8ecb7e7760 Mon Sep 17 00:00:00 2001 From: Konsta Vesterinen Date: Mon, 22 Jul 2013 11:19:43 +0300 Subject: [PATCH 06/11] Updated changes --- CHANGES.rst | 6 ++++++ setup.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 09337d4..9363ee8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog Here you can see the full list of changes between each SQLAlchemy-Utils release. +0.14.5 (2013-07-22) +^^^^^^^^^^^^^^^^^^^ + +- Added UUID type + + 0.14.4 (2013-07-03) ^^^^^^^^^^^^^^^^^^^ diff --git a/setup.py b/setup.py index fe2e4a3..713aba8 100644 --- a/setup.py +++ b/setup.py @@ -55,7 +55,7 @@ setup( 'flexmock>=0.9.7', ], 'phone': ['phonenumbers3k==5.6b1'], - 'color': ['colour>=0.0.3'] + 'color': ['colour>=0.0.4'] }, cmdclass={'test': PyTest}, classifiers=[ From a5143e2b8abf2b51be7252882675d32cfd5e71c1 Mon Sep 17 00:00:00 2001 From: Konsta Vesterinen Date: Mon, 22 Jul 2013 11:21:06 +0300 Subject: [PATCH 07/11] Bumped version --- setup.py | 2 +- sqlalchemy_utils/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 713aba8..4cf1f64 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ class PyTest(Command): setup( name='SQLAlchemy-Utils', - version='0.14.4', + version='0.14.5', url='https://github.com/kvesteri/sqlalchemy-utils', license='BSD', author='Konsta Vesterinen', diff --git a/sqlalchemy_utils/__init__.py b/sqlalchemy_utils/__init__.py index aef926e..792090a 100644 --- a/sqlalchemy_utils/__init__.py +++ b/sqlalchemy_utils/__init__.py @@ -24,7 +24,7 @@ from .types import ( ) -__version__ = '0.14.4' +__version__ = '0.14.5' __all__ = ( From 4d5f9a24f87ca600d880665b4f1ea90b8701bc00 Mon Sep 17 00:00:00 2001 From: Konsta Vesterinen Date: Mon, 22 Jul 2013 11:55:07 +0300 Subject: [PATCH 08/11] Fixed UUID import issues --- CHANGES.rst | 6 ++++++ setup.py | 2 +- sqlalchemy_utils/types/uuid.py | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9363ee8..b75bd61 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog Here you can see the full list of changes between each SQLAlchemy-Utils release. +0.14.6 (2013-07-22) +^^^^^^^^^^^^^^^^^^^ + +- Fixed UUID import issues + + 0.14.5 (2013-07-22) ^^^^^^^^^^^^^^^^^^^ diff --git a/setup.py b/setup.py index 4cf1f64..c288ed5 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ class PyTest(Command): setup( name='SQLAlchemy-Utils', - version='0.14.5', + version='0.14.6', url='https://github.com/kvesteri/sqlalchemy-utils', license='BSD', author='Konsta Vesterinen', diff --git a/sqlalchemy_utils/types/uuid.py b/sqlalchemy_utils/types/uuid.py index d5d7d7e..71368b7 100644 --- a/sqlalchemy_utils/types/uuid.py +++ b/sqlalchemy_utils/types/uuid.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import uuid from sqlalchemy import types from sqlalchemy.dialects import postgresql From e663bb9cd5951a857f6bd93844abfbdb254674bd Mon Sep 17 00:00:00 2001 From: Konsta Vesterinen Date: Mon, 22 Jul 2013 11:55:31 +0300 Subject: [PATCH 09/11] Bumped version --- sqlalchemy_utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlalchemy_utils/__init__.py b/sqlalchemy_utils/__init__.py index 792090a..3f43e20 100644 --- a/sqlalchemy_utils/__init__.py +++ b/sqlalchemy_utils/__init__.py @@ -24,7 +24,7 @@ from .types import ( ) -__version__ = '0.14.5' +__version__ = '0.14.6' __all__ = ( From 165d9f4cfd789bfd77b914381e01da0674f2a163 Mon Sep 17 00:00:00 2001 From: Konsta Vesterinen Date: Tue, 23 Jul 2013 10:28:51 +0300 Subject: [PATCH 10/11] Lazy import for ipaddress package --- sqlalchemy_utils/types/color.py | 3 ++- sqlalchemy_utils/types/ip_address.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/sqlalchemy_utils/types/color.py b/sqlalchemy_utils/types/color.py index 6f77816..689ce9a 100644 --- a/sqlalchemy_utils/types/color.py +++ b/sqlalchemy_utils/types/color.py @@ -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) diff --git a/sqlalchemy_utils/types/ip_address.py b/sqlalchemy_utils/types/ip_address.py index 457d2ca..8065762 100644 --- a/sqlalchemy_utils/types/ip_address.py +++ b/sqlalchemy_utils/types/ip_address.py @@ -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) From b43b72af039c103189d4b2322d9da1f79a207173 Mon Sep 17 00:00:00 2001 From: Konsta Vesterinen Date: Tue, 23 Jul 2013 10:29:53 +0300 Subject: [PATCH 11/11] Bumped version --- CHANGES.rst | 6 ++++++ setup.py | 2 +- sqlalchemy_utils/__init__.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index b75bd61..a28f29c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ 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) ^^^^^^^^^^^^^^^^^^^ diff --git a/setup.py b/setup.py index c288ed5..7c7dcaa 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ class PyTest(Command): setup( name='SQLAlchemy-Utils', - version='0.14.6', + version='0.14.7', url='https://github.com/kvesteri/sqlalchemy-utils', license='BSD', author='Konsta Vesterinen', diff --git a/sqlalchemy_utils/__init__.py b/sqlalchemy_utils/__init__.py index 3f43e20..de0ff33 100644 --- a/sqlalchemy_utils/__init__.py +++ b/sqlalchemy_utils/__init__.py @@ -24,7 +24,7 @@ from .types import ( ) -__version__ = '0.14.6' +__version__ = '0.14.7' __all__ = (