diff --git a/CHANGES.rst b/CHANGES.rst index bc094fc..19448e8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,23 +4,25 @@ Changelog Here you can see the full list of changes between each SQLAlchemy-Utils release. -0.30.8 (2015-06-xx) +0.30.8 (2015-06-05) ^^^^^^^^^^^^^^^^^^^ -- Make has_index work with tables without primary keys (#148) +- Added Asterisk compiler +- Made quote function accept dialect object as the first paremeter +- Made has_index work with tables without primary keys (#148) 0.30.7 (2015-05-28) ^^^^^^^^^^^^^^^^^^^ -- Fix CompositeType null handling +- Fixed CompositeType null handling 0.30.6 (2015-05-28) ^^^^^^^^^^^^^^^^^^^ -- Make psycopg2 requirement optional (#145, #146) -- Make CompositeArray work with tuples given as bind parameters +- Made psycopg2 requirement optional (#145, #146) +- Made CompositeArray work with tuples given as bind parameters 0.30.5 (2015-05-27) diff --git a/sqlalchemy_utils/__init__.py b/sqlalchemy_utils/__init__.py index 16c3548..a786254 100644 --- a/sqlalchemy_utils/__init__.py +++ b/sqlalchemy_utils/__init__.py @@ -8,6 +8,7 @@ from .asserts import ( # noqa ) from .exceptions import ImproperlyConfigured # noqa from .expression_parser import ExpressionParser # noqa +from .expressions import Asterisk # noqa from .functions import ( # noqa analyze, create_database, diff --git a/sqlalchemy_utils/expressions.py b/sqlalchemy_utils/expressions.py index 8ac1a1d..e224cef 100644 --- a/sqlalchemy_utils/expressions.py +++ b/sqlalchemy_utils/expressions.py @@ -1,12 +1,16 @@ import sqlalchemy as sa from sqlalchemy.ext.compiler import compiles -from sqlalchemy.sql import expression +from sqlalchemy.sql.elements import ColumnClause from sqlalchemy.sql.expression import ( _literal_as_text, ClauseElement, - Executable + ColumnElement, + Executable, + FunctionElement ) +from sqlalchemy_utils.functions.orm import quote + class explain(Executable, ClauseElement): """ @@ -64,7 +68,7 @@ def pg_explain(element, compiler, **kw): return text -class array_get(expression.FunctionElement): +class array_get(FunctionElement): name = 'array_get' @@ -85,3 +89,13 @@ def compile_array_get(element, compiler, **kw): compiler.process(args[0]), sa.text(str(args[1].value + 1)) ) + + +class Asterisk(ColumnElement): + def __init__(self, selectable): + self.selectable = selectable + + +@compiles(Asterisk) +def compile_asterisk(element, compiler, **kw): + return '%s.*' % quote(compiler.dialect, element.selectable.name) diff --git a/sqlalchemy_utils/functions/orm.py b/sqlalchemy_utils/functions/orm.py index 6008a1b..32d97f0 100644 --- a/sqlalchemy_utils/functions/orm.py +++ b/sqlalchemy_utils/functions/orm.py @@ -13,6 +13,7 @@ from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import mapperlib from sqlalchemy.orm.attributes import InstrumentedAttribute from sqlalchemy.orm.exc import UnmappedInstanceError +from sqlalchemy.engine.interfaces import Dialect from sqlalchemy.orm.properties import ColumnProperty from sqlalchemy.orm.query import _ColumnEntity from sqlalchemy.orm.session import object_session @@ -407,10 +408,13 @@ def quote(mixed, ident): # 'some_other_identifier' - :param mixed: SQLAlchemy Session / Connection / Engine object. + :param mixed: SQLAlchemy Session / Connection / Engine / Dialect object. :param ident: identifier to conditionally quote """ - dialect = get_bind(mixed).dialect + if isinstance(mixed, Dialect): + dialect = mixed + else: + dialect = get_bind(mixed).dialect return dialect.preparer(dialect).quote(ident) diff --git a/tests/functions/test_quote.py b/tests/functions/test_quote.py index 4d86b3f..36939bd 100644 --- a/tests/functions/test_quote.py +++ b/tests/functions/test_quote.py @@ -1,3 +1,4 @@ +from sqlalchemy.dialects import postgresql from sqlalchemy_utils.functions import quote from tests import TestCase @@ -7,8 +8,10 @@ class TestQuote(TestCase): assert quote(self.connection, 'order') == '"order"' assert quote(self.session, 'order') == '"order"' assert quote(self.engine, 'order') == '"order"' + assert quote(postgresql.dialect(), 'order') == '"order"' def test_quote_with_non_preserved_keyword(self): assert quote(self.connection, 'some_order') == 'some_order' assert quote(self.session, 'some_order') == 'some_order' assert quote(self.engine, 'some_order') == 'some_order' + assert quote(postgresql.dialect(), 'some_order') == 'some_order' diff --git a/tests/test_expressions.py b/tests/test_expressions.py index c692fb0..270c227 100644 --- a/tests/test_expressions.py +++ b/tests/test_expressions.py @@ -1,6 +1,7 @@ import sqlalchemy as sa from sqlalchemy.dialects import postgresql +from sqlalchemy_utils import Asterisk from sqlalchemy_utils.expressions import explain, explain_analyze from tests import TestCase @@ -83,3 +84,25 @@ class TestExplainAnalyze(ExpressionTestCase): dialect=postgresql.dialect() ) ).startswith('EXPLAIN (ANALYZE true) SELECT') + + +class TestAsterisk(object): + def test_with_table_object(self): + Base = sa.ext.declarative.declarative_base() + + class Article(Base): + __tablename__ = 'article' + id = sa.Column(sa.Integer, primary_key=True) + + assert str(Asterisk(Article.__table__)) == 'article.*' + + def test_with_table_object(self): + Base = sa.ext.declarative.declarative_base() + + class User(Base): + __tablename__ = 'user' + id = sa.Column(sa.Integer, primary_key=True) + + assert str(Asterisk(User.__table__).compile( + dialect=postgresql.dialect() + )) == '"user".*'