From 174ca931013c43c0d2f5ff4d017b297e3ae2fb35 Mon Sep 17 00:00:00 2001 From: Konsta Vesterinen Date: Mon, 20 Oct 2014 17:36:09 +0300 Subject: [PATCH] Add has_unique_index utility function --- CHANGES.rst | 1 + docs/database_helpers.rst | 6 ++++ sqlalchemy_utils/__init__.py | 1 + sqlalchemy_utils/functions/__init__.py | 1 + sqlalchemy_utils/functions/database.py | 43 ++++++++++++++++++++++ tests/__init__.py | 7 ++++ tests/functions/test_get_primary_keys.py | 1 - tests/functions/test_has_unique_index.py | 46 ++++++++++++++++++++++++ 8 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 tests/functions/test_has_unique_index.py diff --git a/CHANGES.rst b/CHANGES.rst index 437cb13..1264f56 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,7 @@ Here you can see the full list of changes between each SQLAlchemy-Utils release. ^^^^^^^^^^^^^^^^^^^ - Added support for more SQLAlchemy based objects and classes in get_tables function +- Added has_unique_index utility function 0.27.0 (2014-10-14) diff --git a/docs/database_helpers.rst b/docs/database_helpers.rst index 3bc9603..7ce8ff1 100644 --- a/docs/database_helpers.rst +++ b/docs/database_helpers.rst @@ -35,6 +35,12 @@ has_index .. autofunction:: has_index +has_unique_index +^^^^^^^^^^^^^^^^ + +.. autofunction:: has_unique_index + + render_expression ^^^^^^^^^^^^^^^^^ diff --git a/sqlalchemy_utils/__init__.py b/sqlalchemy_utils/__init__.py index fccaeba..2d8bf6c 100644 --- a/sqlalchemy_utils/__init__.py +++ b/sqlalchemy_utils/__init__.py @@ -26,6 +26,7 @@ from .functions import ( has_any_changes, has_changes, has_index, + has_unique_index, identity, merge_references, mock_engine, diff --git a/sqlalchemy_utils/functions/__init__.py b/sqlalchemy_utils/functions/__init__.py index 02f0513..7a6e71e 100644 --- a/sqlalchemy_utils/functions/__init__.py +++ b/sqlalchemy_utils/functions/__init__.py @@ -9,6 +9,7 @@ from .database import ( drop_database, escape_like, has_index, + has_unique_index, is_auto_assigned_date_column, ) from .foreign_keys import ( diff --git a/sqlalchemy_utils/functions/database.py b/sqlalchemy_utils/functions/database.py index fb6d344..af13d38 100644 --- a/sqlalchemy_utils/functions/database.py +++ b/sqlalchemy_utils/functions/database.py @@ -169,6 +169,49 @@ def has_index(column): ) +def has_unique_index(column): + """ + Return whether or not given column has a unique index. A column has a + unique index if it has a single column unique index or it is a part of + single column UniqueConstraint. + + :param column: SQLAlchemy Column object + + .. versionadded: 0.27.1 + + :: + + from sqlalchemy_utils import has_unique_index + + + class Article(Base): + __tablename__ = 'article' + id = sa.Column(sa.Integer, primary_key=True) + title = sa.Column(sa.String(100)) + is_published = sa.Column(sa.Boolean, unique=True) + is_deleted = sa.Column(sa.Boolean) + is_archived = sa.Column(sa.Boolean) + + + table = Article.__table__ + + has_unique_index(table.c.is_published) # True + has_unique_index(table.c.is_deleted) # False + has_unique_index(table.c.id) # True + """ + pks = column.table.primary_key.columns + return ( + (column is pks.values()[0] and len(pks) == 1) + or + any( + constraint.columns.values()[0] is column and + len(constraint.columns) == 1 + for constraint in column.table.constraints + if isinstance(constraint, sa.sql.schema.UniqueConstraint) + ) + ) + + def is_auto_assigned_date_column(column): """ Returns whether or not given SQLAlchemy Column object's is auto assigned diff --git a/tests/__init__.py b/tests/__init__.py index 67a29f3..6b885f7 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -111,3 +111,10 @@ class TestCase(object): self.User = User self.Category = Category self.Article = Article + + +def assert_contains(clause, query): + # Test that query executes + query.all() + assert clause in str(query) + diff --git a/tests/functions/test_get_primary_keys.py b/tests/functions/test_get_primary_keys.py index 09bfa29..060ebc5 100644 --- a/tests/functions/test_get_primary_keys.py +++ b/tests/functions/test_get_primary_keys.py @@ -6,7 +6,6 @@ import sqlalchemy as sa from sqlalchemy.ext.declarative import declarative_base from sqlalchemy_utils import get_primary_keys -from tests import TestCase class TestGetPrimaryKeys(object): diff --git a/tests/functions/test_has_unique_index.py b/tests/functions/test_has_unique_index.py new file mode 100644 index 0000000..f82f1a7 --- /dev/null +++ b/tests/functions/test_has_unique_index.py @@ -0,0 +1,46 @@ +import sqlalchemy as sa +from sqlalchemy.ext.declarative import declarative_base + +from sqlalchemy_utils import has_unique_index + + +class TestHasIndex(object): + def setup_method(self, method): + Base = declarative_base() + + class Article(Base): + __tablename__ = 'article' + id = sa.Column(sa.Integer, primary_key=True) + + class ArticleTranslation(Base): + __tablename__ = 'article_translation' + id = sa.Column(sa.Integer, primary_key=True) + locale = sa.Column(sa.String(10), primary_key=True) + title = sa.Column(sa.String(100)) + is_published = sa.Column(sa.Boolean, index=True) + is_deleted = sa.Column(sa.Boolean, unique=True) + is_archived = sa.Column(sa.Boolean) + + __table_args__ = ( + sa.Index('my_index', is_archived, is_published, unique=True), + ) + + self.articles = Article.__table__ + self.article_translations = ArticleTranslation.__table__ + + def test_primary_key(self): + assert has_unique_index(self.articles.c.id) + + def test_unique_index(self): + assert has_unique_index(self.article_translations.c.is_deleted) + + def test_compound_primary_key(self): + assert not has_unique_index(self.article_translations.c.id) + assert not has_unique_index(self.article_translations.c.locale) + + def test_single_column_index(self): + assert not has_unique_index(self.article_translations.c.is_published) + + def test_compound_column_unique_index(self): + assert not has_unique_index(self.article_translations.c.is_published) + assert not has_unique_index(self.article_translations.c.is_archived)