From 10bb97e89a5bdf6fab31c95f8f5a7a07b5d534bc Mon Sep 17 00:00:00 2001 From: Ilya Pekelny Date: Fri, 8 Aug 2014 10:00:17 +0300 Subject: DropEnumType class available from postgres dialect --- lib/sqlalchemy/dialects/postgresql/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/sqlalchemy/dialects/postgresql/__init__.py b/lib/sqlalchemy/dialects/postgresql/__init__.py index d755e6aa1..1cff8e3a0 100644 --- a/lib/sqlalchemy/dialects/postgresql/__init__.py +++ b/lib/sqlalchemy/dialects/postgresql/__init__.py @@ -13,7 +13,7 @@ from .base import \ INTEGER, BIGINT, SMALLINT, VARCHAR, CHAR, TEXT, NUMERIC, FLOAT, REAL, \ INET, CIDR, UUID, BIT, MACADDR, OID, DOUBLE_PRECISION, TIMESTAMP, TIME, \ DATE, BYTEA, BOOLEAN, INTERVAL, ARRAY, ENUM, dialect, array, Any, All, \ - TSVECTOR + TSVECTOR, DropEnumType from .constraints import ExcludeConstraint from .hstore import HSTORE, hstore from .json import JSON, JSONElement, JSONB @@ -26,5 +26,6 @@ __all__ = ( 'DOUBLE_PRECISION', 'TIMESTAMP', 'TIME', 'DATE', 'BYTEA', 'BOOLEAN', 'INTERVAL', 'ARRAY', 'ENUM', 'dialect', 'Any', 'All', 'array', 'HSTORE', 'hstore', 'INT4RANGE', 'INT8RANGE', 'NUMRANGE', 'DATERANGE', - 'TSRANGE', 'TSTZRANGE', 'json', 'JSON', 'JSONB', 'JSONElement' + 'TSRANGE', 'TSTZRANGE', 'json', 'JSON', 'JSONB', 'JSONElement', + 'DropEnumType' ) -- cgit v1.2.1 From a0e0f4c289b46c0c9a051c08d7f9a1929e0e30ce Mon Sep 17 00:00:00 2001 From: Ilya Pekelny Date: Thu, 24 Jul 2014 19:27:18 +0300 Subject: Public inspector method to load enum list Provide opportunity to get enums list via an inspector instance public interface. --- lib/sqlalchemy/dialects/postgresql/base.py | 12 ++++++++++-- test/dialect/postgresql/test_reflection.py | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index 5ff2f7c61..3356e9d4b 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -1574,6 +1574,12 @@ class PGInspector(reflection.Inspector): return self.dialect.get_table_oid(self.bind, table_name, schema, info_cache=self.info_cache) + def load_enums(self, conn, schema=None): + """Return a enums list. + + A per-database and per-schema enums list.""" + schema = schema or self.default_schema_name + return self.dialect._load_enums(conn, schema) class CreateEnumType(schema._CreateDropBase): @@ -2424,7 +2430,8 @@ class PGDialect(default.DefaultDialect): for name, uc in uniques.items() ] - def _load_enums(self, connection): + def _load_enums(self, connection, schema=None): + schema = schema or self.default_schema_name if not self.supports_native_enum: return {} @@ -2440,8 +2447,9 @@ class PGDialect(default.DefaultDialect): LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace LEFT JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid WHERE t.typtype = 'e' + AND n.nspname = '%s' ORDER BY "name", e.oid -- e.oid gives us label order - """ + """ % schema s = sql.text(SQL_ENUMS, typemap={ 'attname': sqltypes.Unicode, diff --git a/test/dialect/postgresql/test_reflection.py b/test/dialect/postgresql/test_reflection.py index 1d6a41765..26de23902 100644 --- a/test/dialect/postgresql/test_reflection.py +++ b/test/dialect/postgresql/test_reflection.py @@ -1,5 +1,6 @@ # coding: utf-8 +from sqlalchemy.engine import reflection from sqlalchemy.testing.assertions import eq_, assert_raises, \ AssertsExecutionResults from sqlalchemy.testing import fixtures @@ -622,6 +623,26 @@ class ReflectionTest(fixtures.TestBase): for fk in fks: eq_(fk, fk_ref[fk['name']]) + @testing.provide_metadata + def test_inspect_enums_custom_schema(self): + conn = testing.db.connect() + enum_type = postgresql.ENUM('sad', 'ok', 'happy', name='mood', + metadata=self.metadata, schema='test_schema') + enum_type.create(conn) + inspector = reflection.Inspector.from_engine(conn.engine) + eq_(inspector.load_enums(conn, 'test_schema'), { + u'test_schema.mood': {'labels': [u'sad', u'ok', u'happy']}}) + + @testing.provide_metadata + def test_inspect_enums_schema(self): + conn = testing.db.connect() + enum_type = postgresql.ENUM('cat', 'dog', 'rat', name='pet', + metadata=self.metadata) + enum_type.create(conn) + inspector = reflection.Inspector.from_engine(conn.engine) + eq_(inspector.load_enums(conn), { + u'pet': {'labels': [u'cat', u'dog', u'rat']}}) + class CustomTypeReflectionTest(fixtures.TestBase): -- cgit v1.2.1 From f39767ad727fcc9493d41451d7112d4f3459e9c4 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 13 Aug 2014 17:42:33 -0400 Subject: - public method name is get_enums() - return a list of dicts like other methods do - don't combine 'schema' with 'name', leave them separate - support '*' argument so that we can retrieve cross-schema if needed - remove "conn" argument - use bound parameters for 'schema' in SQL - order by schema, name, label - adapt _load_enums changes to column reflection - changelog - module docs for get_enums() - add drop of enums to --dropfirst --- doc/build/changelog/changelog_10.rst | 8 +++ lib/sqlalchemy/dialects/postgresql/base.py | 93 ++++++++++++++++++++-------- lib/sqlalchemy/testing/plugin/plugin_base.py | 10 ++- test/dialect/postgresql/test_reflection.py | 69 +++++++++++++++++---- 4 files changed, 141 insertions(+), 39 deletions(-) diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index e4671ed9b..7762534e8 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -16,6 +16,14 @@ .. changelog:: :version: 1.0.0 + .. change:: + :tags: postgresql, feature + :pullreq: github:126 + + Added new method :meth:`.PGInspector.get_enums`, when using the + inspector for Postgresql will provide a list of ENUM types. + Pull request courtesy Ilya Pekelny. + .. change:: :tags: mysql, bug diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index 3356e9d4b..c2b1d66f4 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -401,6 +401,23 @@ The value passed to the keyword argument will be simply passed through to the underlying CREATE INDEX command, so it *must* be a valid index type for your version of PostgreSQL. +Special Reflection Options +-------------------------- + +The :class:`.Inspector` used for the Postgresql backend is an instance +of :class:`.PGInspector`, which offers additional methods:: + + from sqlalchemy import create_engine, inspect + + engine = create_engine("postgresql+psycopg2://localhost/test") + insp = inspect(engine) # will be a PGInspector + + print(insp.get_enums()) + +.. autoclass:: PGInspector + :members: + + """ from collections import defaultdict import re @@ -1570,16 +1587,31 @@ class PGInspector(reflection.Inspector): reflection.Inspector.__init__(self, conn) def get_table_oid(self, table_name, schema=None): - """Return the oid from `table_name` and `schema`.""" + """Return the OID for the given table name.""" return self.dialect.get_table_oid(self.bind, table_name, schema, info_cache=self.info_cache) - def load_enums(self, conn, schema=None): - """Return a enums list. - A per-database and per-schema enums list.""" + def get_enums(self, schema=None): + """Return a list of ENUM objects. + + Each member is a dictionary containing these fields: + + * name - name of the enum + * schema - the schema name for the enum. + * visible - boolean, whether or not this enum is visible + in the default search path. + * labels - a list of string labels that apply to the enum. + + :param schema: schema name. If None, the default schema + (typically 'public') is used. May also be set to '*' to + indicate load enums for all schemas. + + .. versionadded:: 1.0.0 + + """ schema = schema or self.default_schema_name - return self.dialect._load_enums(conn, schema) + return self.dialect._load_enums(self.bind, schema) class CreateEnumType(schema._CreateDropBase): @@ -2045,7 +2077,12 @@ class PGDialect(default.DefaultDialect): c = connection.execute(s, table_oid=table_oid) rows = c.fetchall() domains = self._load_domains(connection) - enums = self._load_enums(connection) + enums = dict( + ( + "%s.%s" % (rec['schema'], rec['name']) + if not rec['visible'] else rec['name'], rec) for rec in + self._load_enums(connection, schema='*') + ) # format columns columns = [] @@ -2119,10 +2156,9 @@ class PGDialect(default.DefaultDialect): elif attype in enums: enum = enums[attype] coltype = ENUM - if "." in attype: - kwargs['schema'], kwargs['name'] = attype.split('.') - else: - kwargs['name'] = attype + kwargs['name'] = enum['name'] + if not enum['visible']: + kwargs['schema'] = enum['schema'] args = tuple(enum['labels']) break elif attype in domains: @@ -2447,32 +2483,37 @@ class PGDialect(default.DefaultDialect): LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace LEFT JOIN pg_catalog.pg_enum e ON t.oid = e.enumtypid WHERE t.typtype = 'e' - AND n.nspname = '%s' - ORDER BY "name", e.oid -- e.oid gives us label order - """ % schema + """ + + if schema != '*': + SQL_ENUMS += "AND n.nspname = :schema " + + # e.oid gives us label order within an enum + SQL_ENUMS += 'ORDER BY "schema", "name", e.oid' s = sql.text(SQL_ENUMS, typemap={ 'attname': sqltypes.Unicode, 'label': sqltypes.Unicode}) + + if schema != '*': + s = s.bindparams(schema=schema) + c = connection.execute(s) - enums = {} + enums = [] + enum_by_name = {} for enum in c.fetchall(): - if enum['visible']: - # 'visible' just means whether or not the enum is in a - # schema that's on the search path -- or not overridden by - # a schema with higher precedence. If it's not visible, - # it will be prefixed with the schema-name when it's used. - name = enum['name'] - else: - name = "%s.%s" % (enum['schema'], enum['name']) - - if name in enums: - enums[name]['labels'].append(enum['label']) + key = (enum['schema'], enum['name']) + if key in enum_by_name: + enum_by_name[key]['labels'].append(enum['label']) else: - enums[name] = { + enum_by_name[key] = enum_rec = { + 'name': enum['name'], + 'schema': enum['schema'], + 'visible': enum['visible'], 'labels': [enum['label']], } + enums.append(enum_rec) return enums diff --git a/lib/sqlalchemy/testing/plugin/plugin_base.py b/lib/sqlalchemy/testing/plugin/plugin_base.py index 4c245e9e9..9c63a2e1d 100644 --- a/lib/sqlalchemy/testing/plugin/plugin_base.py +++ b/lib/sqlalchemy/testing/plugin/plugin_base.py @@ -315,7 +315,7 @@ def _setup_requirements(argument): @post def _prep_testing_database(options, file_config): from sqlalchemy.testing import config - from sqlalchemy import schema, inspect + from sqlalchemy import schema, inspect, testing if options.dropfirst: for cfg in config.Config.all_configs(): @@ -358,6 +358,14 @@ def _prep_testing_database(options, file_config): schema="test_schema") )) + if testing.against("postgresql"): + from sqlalchemy.dialects import postgresql + for enum in inspector.get_enums("*"): + e.execute(postgresql.DropEnumType( + postgresql.ENUM( + name=enum['name'], + schema=enum['schema']))) + @post def _set_table_options(options, file_config): diff --git a/test/dialect/postgresql/test_reflection.py b/test/dialect/postgresql/test_reflection.py index 26de23902..bab41b0f7 100644 --- a/test/dialect/postgresql/test_reflection.py +++ b/test/dialect/postgresql/test_reflection.py @@ -624,24 +624,69 @@ class ReflectionTest(fixtures.TestBase): eq_(fk, fk_ref[fk['name']]) @testing.provide_metadata - def test_inspect_enums_custom_schema(self): + def test_inspect_enums_schema(self): conn = testing.db.connect() - enum_type = postgresql.ENUM('sad', 'ok', 'happy', name='mood', - metadata=self.metadata, schema='test_schema') + enum_type = postgresql.ENUM( + 'sad', 'ok', 'happy', name='mood', + schema='test_schema', + metadata=self.metadata) enum_type.create(conn) inspector = reflection.Inspector.from_engine(conn.engine) - eq_(inspector.load_enums(conn, 'test_schema'), { - u'test_schema.mood': {'labels': [u'sad', u'ok', u'happy']}}) + eq_( + inspector.get_enums('test_schema'), [{ + 'visible': False, + 'name': 'mood', + 'schema': 'test_schema', + 'labels': ['sad', 'ok', 'happy'] + }]) @testing.provide_metadata - def test_inspect_enums_schema(self): - conn = testing.db.connect() - enum_type = postgresql.ENUM('cat', 'dog', 'rat', name='pet', + def test_inspect_enums(self): + enum_type = postgresql.ENUM( + 'cat', 'dog', 'rat', name='pet', metadata=self.metadata) + enum_type.create(testing.db) + inspector = reflection.Inspector.from_engine(testing.db) + eq_(inspector.get_enums(), [ + { + 'visible': True, + 'labels': ['cat', 'dog', 'rat'], + 'name': 'pet', + 'schema': 'public' + }]) + + @testing.provide_metadata + def test_inspect_enums_star(self): + enum_type = postgresql.ENUM( + 'cat', 'dog', 'rat', name='pet', metadata=self.metadata) + schema_enum_type = postgresql.ENUM( + 'sad', 'ok', 'happy', name='mood', + schema='test_schema', metadata=self.metadata) - enum_type.create(conn) - inspector = reflection.Inspector.from_engine(conn.engine) - eq_(inspector.load_enums(conn), { - u'pet': {'labels': [u'cat', u'dog', u'rat']}}) + enum_type.create(testing.db) + schema_enum_type.create(testing.db) + inspector = reflection.Inspector.from_engine(testing.db) + + eq_(inspector.get_enums(), [ + { + 'visible': True, + 'labels': ['cat', 'dog', 'rat'], + 'name': 'pet', + 'schema': 'public' + }]) + + eq_(inspector.get_enums('*'), [ + { + 'visible': True, + 'labels': ['cat', 'dog', 'rat'], + 'name': 'pet', + 'schema': 'public' + }, + { + 'visible': False, + 'name': 'mood', + 'schema': 'test_schema', + 'labels': ['sad', 'ok', 'happy'] + }]) class CustomTypeReflectionTest(fixtures.TestBase): -- cgit v1.2.1 From 38cb9bf78454b00792fc68f847165141c6f45be3 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 13 Aug 2014 18:38:52 -0400 Subject: flake8 cleanup --- lib/sqlalchemy/orm/base.py | 46 ++++++++++++++++++++++------------------ lib/sqlalchemy/orm/interfaces.py | 6 +++--- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/lib/sqlalchemy/orm/base.py b/lib/sqlalchemy/orm/base.py index a85f59f37..421d79630 100644 --- a/lib/sqlalchemy/orm/base.py +++ b/lib/sqlalchemy/orm/base.py @@ -144,38 +144,42 @@ _INSTRUMENTOR = ('mapper', 'instrumentor') EXT_CONTINUE = util.symbol('EXT_CONTINUE') EXT_STOP = util.symbol('EXT_STOP') -ONETOMANY = util.symbol('ONETOMANY', - """Indicates the one-to-many direction for a :func:`.relationship`. +ONETOMANY = util.symbol( + 'ONETOMANY', + """Indicates the one-to-many direction for a :func:`.relationship`. -This symbol is typically used by the internals but may be exposed within -certain API features. + This symbol is typically used by the internals but may be exposed within + certain API features. -""") + """) -MANYTOONE = util.symbol('MANYTOONE', - """Indicates the many-to-one direction for a :func:`.relationship`. +MANYTOONE = util.symbol( + 'MANYTOONE', + """Indicates the many-to-one direction for a :func:`.relationship`. -This symbol is typically used by the internals but may be exposed within -certain API features. + This symbol is typically used by the internals but may be exposed within + certain API features. -""") + """) -MANYTOMANY = util.symbol('MANYTOMANY', - """Indicates the many-to-many direction for a :func:`.relationship`. +MANYTOMANY = util.symbol( + 'MANYTOMANY', + """Indicates the many-to-many direction for a :func:`.relationship`. -This symbol is typically used by the internals but may be exposed within -certain API features. + This symbol is typically used by the internals but may be exposed within + certain API features. -""") + """) -NOT_EXTENSION = util.symbol('NOT_EXTENSION', - """Symbol indicating an :class:`_InspectionAttr` that's - not part of sqlalchemy.ext. +NOT_EXTENSION = util.symbol( + 'NOT_EXTENSION', + """Symbol indicating an :class:`_InspectionAttr` that's + not part of sqlalchemy.ext. - Is assigned to the :attr:`._InspectionAttr.extension_type` - attibute. + Is assigned to the :attr:`._InspectionAttr.extension_type` + attibute. -""") + """) _none_set = frozenset([None, NEVER_SET, PASSIVE_NO_RESULT]) diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 9bc1c3dd0..d95b78f24 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -19,15 +19,15 @@ classes within should be considered mostly private. from __future__ import absolute_import -from .. import exc as sa_exc, util, inspect +from .. import util from ..sql import operators -from collections import deque from .base import (ONETOMANY, MANYTOONE, MANYTOMANY, EXT_CONTINUE, EXT_STOP, NOT_EXTENSION) from .base import _InspectionAttr, _MappedAttribute -from .path_registry import PathRegistry import collections +# imported later +MapperExtension = SessionExtension = AttributeExtension = None __all__ = ( 'AttributeExtension', -- cgit v1.2.1 From 44d21de457424f5623362474325aa6e5d3fe9e6d Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 13 Aug 2014 18:47:52 -0400 Subject: - rename _InspectionAttr to InspectionAttr --- doc/build/orm/internals.rst | 2 +- lib/sqlalchemy/ext/associationproxy.py | 6 +++--- lib/sqlalchemy/ext/hybrid.py | 12 ++++++------ lib/sqlalchemy/orm/attributes.py | 2 +- lib/sqlalchemy/orm/base.py | 8 ++++---- lib/sqlalchemy/orm/instrumentation.py | 4 ++-- lib/sqlalchemy/orm/interfaces.py | 4 ++-- lib/sqlalchemy/orm/mapper.py | 10 +++++----- lib/sqlalchemy/orm/state.py | 2 +- lib/sqlalchemy/orm/util.py | 4 ++-- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/doc/build/orm/internals.rst b/doc/build/orm/internals.rst index 857ea78d5..0283f6cac 100644 --- a/doc/build/orm/internals.rst +++ b/doc/build/orm/internals.rst @@ -27,7 +27,7 @@ sections, are listed here. :members: -.. autoclass:: sqlalchemy.orm.interfaces._InspectionAttr +.. autoclass:: sqlalchemy.orm.interfaces.InspectionAttr :members: diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py index a987ab413..1aa68ac32 100644 --- a/lib/sqlalchemy/ext/associationproxy.py +++ b/lib/sqlalchemy/ext/associationproxy.py @@ -77,16 +77,16 @@ def association_proxy(target_collection, attr, **kw): ASSOCIATION_PROXY = util.symbol('ASSOCIATION_PROXY') -"""Symbol indicating an :class:`_InspectionAttr` that's +"""Symbol indicating an :class:`InspectionAttr` that's of type :class:`.AssociationProxy`. - Is assigned to the :attr:`._InspectionAttr.extension_type` + Is assigned to the :attr:`.InspectionAttr.extension_type` attibute. """ -class AssociationProxy(interfaces._InspectionAttr): +class AssociationProxy(interfaces.InspectionAttr): """A descriptor that presents a read/write view of an object attribute.""" is_attribute = False diff --git a/lib/sqlalchemy/ext/hybrid.py b/lib/sqlalchemy/ext/hybrid.py index 9f4e09e92..e2739d1de 100644 --- a/lib/sqlalchemy/ext/hybrid.py +++ b/lib/sqlalchemy/ext/hybrid.py @@ -634,10 +634,10 @@ from .. import util from ..orm import attributes, interfaces HYBRID_METHOD = util.symbol('HYBRID_METHOD') -"""Symbol indicating an :class:`_InspectionAttr` that's +"""Symbol indicating an :class:`InspectionAttr` that's of type :class:`.hybrid_method`. - Is assigned to the :attr:`._InspectionAttr.extension_type` + Is assigned to the :attr:`.InspectionAttr.extension_type` attibute. .. seealso:: @@ -647,10 +647,10 @@ HYBRID_METHOD = util.symbol('HYBRID_METHOD') """ HYBRID_PROPERTY = util.symbol('HYBRID_PROPERTY') -"""Symbol indicating an :class:`_InspectionAttr` that's +"""Symbol indicating an :class:`InspectionAttr` that's of type :class:`.hybrid_method`. - Is assigned to the :attr:`._InspectionAttr.extension_type` + Is assigned to the :attr:`.InspectionAttr.extension_type` attibute. .. seealso:: @@ -660,7 +660,7 @@ HYBRID_PROPERTY = util.symbol('HYBRID_PROPERTY') """ -class hybrid_method(interfaces._InspectionAttr): +class hybrid_method(interfaces.InspectionAttr): """A decorator which allows definition of a Python object method with both instance-level and class-level behavior. @@ -703,7 +703,7 @@ class hybrid_method(interfaces._InspectionAttr): return self -class hybrid_property(interfaces._InspectionAttr): +class hybrid_property(interfaces.InspectionAttr): """A decorator which allows definition of a Python descriptor with both instance-level and class-level behavior. diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 67e4dca9b..66197ba0e 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -30,7 +30,7 @@ from .base import state_str, instance_str @inspection._self_inspects class QueryableAttribute(interfaces._MappedAttribute, - interfaces._InspectionAttr, + interfaces.InspectionAttr, interfaces.PropComparator): """Base class for :term:`descriptor` objects that intercept attribute events on behalf of a :class:`.MapperProperty` diff --git a/lib/sqlalchemy/orm/base.py b/lib/sqlalchemy/orm/base.py index 421d79630..3097b8590 100644 --- a/lib/sqlalchemy/orm/base.py +++ b/lib/sqlalchemy/orm/base.py @@ -173,10 +173,10 @@ MANYTOMANY = util.symbol( NOT_EXTENSION = util.symbol( 'NOT_EXTENSION', - """Symbol indicating an :class:`_InspectionAttr` that's + """Symbol indicating an :class:`InspectionAttr` that's not part of sqlalchemy.ext. - Is assigned to the :attr:`._InspectionAttr.extension_type` + Is assigned to the :attr:`.InspectionAttr.extension_type` attibute. """) @@ -423,7 +423,7 @@ def class_mapper(class_, configure=True): return mapper -class _InspectionAttr(object): +class InspectionAttr(object): """A base class applied to all ORM objects that can be returned by the :func:`.inspect` function. @@ -460,7 +460,7 @@ class _InspectionAttr(object): :class:`.QueryableAttribute` which handles attributes events on behalf of a :class:`.MapperProperty`. But can also be an extension type such as :class:`.AssociationProxy` or :class:`.hybrid_property`. - The :attr:`._InspectionAttr.extension_type` will refer to a constant + The :attr:`.InspectionAttr.extension_type` will refer to a constant identifying the specific subtype. .. seealso:: diff --git a/lib/sqlalchemy/orm/instrumentation.py b/lib/sqlalchemy/orm/instrumentation.py index f58b8807f..eb5b65baa 100644 --- a/lib/sqlalchemy/orm/instrumentation.py +++ b/lib/sqlalchemy/orm/instrumentation.py @@ -97,7 +97,7 @@ class ClassManager(dict): def _all_sqla_attributes(self, exclude=None): """return an iterator of all classbound attributes that are - implement :class:`._InspectionAttr`. + implement :class:`.InspectionAttr`. This includes :class:`.QueryableAttribute` as well as extension types such as :class:`.hybrid_property` and @@ -110,7 +110,7 @@ class ClassManager(dict): for key in set(supercls.__dict__).difference(exclude): exclude.add(key) val = supercls.__dict__[key] - if isinstance(val, interfaces._InspectionAttr): + if isinstance(val, interfaces.InspectionAttr): yield key, val def _attr_has_impl(self, key): diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index d95b78f24..2dfaf6243 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -23,7 +23,7 @@ from .. import util from ..sql import operators from .base import (ONETOMANY, MANYTOONE, MANYTOMANY, EXT_CONTINUE, EXT_STOP, NOT_EXTENSION) -from .base import _InspectionAttr, _MappedAttribute +from .base import InspectionAttr, _MappedAttribute import collections # imported later @@ -47,7 +47,7 @@ __all__ = ( ) -class MapperProperty(_MappedAttribute, _InspectionAttr): +class MapperProperty(_MappedAttribute, InspectionAttr): """Manage the relationship of a ``Mapper`` to a single class attribute, as well as that attribute as it appears on individual instances of the class, including attribute instrumentation, diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 7e5166393..06ec2bf14 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -26,7 +26,7 @@ from ..sql import expression, visitors, operators, util as sql_util from . import instrumentation, attributes, exc as orm_exc, loading from . import properties from . import util as orm_util -from .interfaces import MapperProperty, _InspectionAttr, _MappedAttribute +from .interfaces import MapperProperty, InspectionAttr, _MappedAttribute from .base import _class_to_mapper, _state_mapper, class_mapper, \ state_str, _INSTRUMENTOR @@ -52,7 +52,7 @@ _CONFIGURE_MUTEX = util.threading.RLock() @inspection._self_inspects @log.class_logger -class Mapper(_InspectionAttr): +class Mapper(InspectionAttr): """Define the correlation of class attributes to database table columns. @@ -1979,7 +1979,7 @@ class Mapper(_InspectionAttr): @util.memoized_property def all_orm_descriptors(self): - """A namespace of all :class:`._InspectionAttr` attributes associated + """A namespace of all :class:`.InspectionAttr` attributes associated with the mapped class. These attributes are in all cases Python :term:`descriptors` @@ -1988,13 +1988,13 @@ class Mapper(_InspectionAttr): This namespace includes attributes that are mapped to the class as well as attributes declared by extension modules. It includes any Python descriptor type that inherits from - :class:`._InspectionAttr`. This includes + :class:`.InspectionAttr`. This includes :class:`.QueryableAttribute`, as well as extension types such as :class:`.hybrid_property`, :class:`.hybrid_method` and :class:`.AssociationProxy`. To distinguish between mapped attributes and extension attributes, - the attribute :attr:`._InspectionAttr.extension_type` will refer + the attribute :attr:`.InspectionAttr.extension_type` will refer to a constant that distinguishes between different extension types. When dealing with a :class:`.QueryableAttribute`, the diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index a9024b468..fe8ccd222 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -21,7 +21,7 @@ from .base import PASSIVE_NO_RESULT, SQL_OK, NEVER_SET, ATTR_WAS_SET, \ from . import base -class InstanceState(interfaces._InspectionAttr): +class InstanceState(interfaces.InspectionAttr): """tracks state information at the instance level. The :class:`.InstanceState` is a key object used by the diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 215de5f4b..ea7bfc294 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -15,7 +15,7 @@ import re from .base import instance_str, state_str, state_class_str, attribute_str, \ state_attribute_str, object_mapper, object_state, _none_set from .base import class_mapper, _class_to_mapper -from .base import _InspectionAttr +from .base import InspectionAttr from .path_registry import PathRegistry all_cascades = frozenset(("delete", "delete-orphan", "all", "merge", @@ -412,7 +412,7 @@ class AliasedClass(object): id(self), self._aliased_insp._target.__name__) -class AliasedInsp(_InspectionAttr): +class AliasedInsp(InspectionAttr): """Provide an inspection interface for an :class:`.AliasedClass` object. -- cgit v1.2.1 From ea85c7053dc9532a95fd487628768fdfc1ca5c30 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 13 Aug 2014 19:20:44 -0400 Subject: - The :meth:`.InspectionAttr.info` collection is now moved down to :class:`.InspectionAttr`, where in addition to being available on all :class:`.MapperProperty` objects, it is also now available on hybrid properties, association proxies, when accessed via :attr:`.Mapper.all_orm_descriptors`. fixes #2971 --- doc/build/changelog/changelog_10.rst | 10 ++++++++++ lib/sqlalchemy/orm/base.py | 26 ++++++++++++++++++++++++++ lib/sqlalchemy/orm/interfaces.py | 22 ---------------------- test/ext/test_hybrid.py | 18 ++++++++++++++++++ 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 20023af44..c59d7c912 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -16,6 +16,16 @@ .. changelog:: :version: 1.0.0 + .. change:: + :tags: orm, feature + :tickets: 2971 + + The :meth:`.InspectionAttr.info` collection is now moved down to + :class:`.InspectionAttr`, where in addition to being available + on all :class:`.MapperProperty` objects, it is also now available + on hybrid properties, association proxies, when accessed via + :attr:`.Mapper.all_orm_descriptors`. + .. change:: :tags: sql, feature :tickets: 3027 diff --git a/lib/sqlalchemy/orm/base.py b/lib/sqlalchemy/orm/base.py index 3097b8590..3390ceec4 100644 --- a/lib/sqlalchemy/orm/base.py +++ b/lib/sqlalchemy/orm/base.py @@ -488,6 +488,32 @@ class InspectionAttr(object): """ + @util.memoized_property + def info(self): + """Info dictionary associated with the object, allowing user-defined + data to be associated with this :class:`.InspectionAttr`. + + The dictionary is generated when first accessed. Alternatively, + it can be specified as a constructor argument to the + :func:`.column_property`, :func:`.relationship`, or :func:`.composite` + functions. + + .. versionadded:: 0.8 Added support for .info to all + :class:`.MapperProperty` subclasses. + + .. versionchanged:: 1.0.0 :attr:`.InspectionAttr.info` moved + from :class:`.MapperProperty` so that it can apply to a wider + variety of ORM and extension constructs. + + .. seealso:: + + :attr:`.QueryableAttribute.info` + + :attr:`.SchemaItem.info` + + """ + return {} + class _MappedAttribute(object): """Mixin for attributes which should be replaced by mapper-assigned diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 2dfaf6243..49ec99ce4 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -109,28 +109,6 @@ class MapperProperty(_MappedAttribute, InspectionAttr): def instrument_class(self, mapper): # pragma: no-coverage raise NotImplementedError() - @util.memoized_property - def info(self): - """Info dictionary associated with the object, allowing user-defined - data to be associated with this :class:`.MapperProperty`. - - The dictionary is generated when first accessed. Alternatively, - it can be specified as a constructor argument to the - :func:`.column_property`, :func:`.relationship`, or :func:`.composite` - functions. - - .. versionadded:: 0.8 Added support for .info to all - :class:`.MapperProperty` subclasses. - - .. seealso:: - - :attr:`.QueryableAttribute.info` - - :attr:`.SchemaItem.info` - - """ - return {} - _configure_started = False _configure_finished = False diff --git a/test/ext/test_hybrid.py b/test/ext/test_hybrid.py index e7f392a33..b895d2fb2 100644 --- a/test/ext/test_hybrid.py +++ b/test/ext/test_hybrid.py @@ -5,6 +5,7 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext import hybrid from sqlalchemy.testing import eq_, AssertsCompiledSQL, assert_raises_message from sqlalchemy.testing import fixtures +from sqlalchemy import inspect class PropertyComparatorTest(fixtures.TestBase, AssertsCompiledSQL): __dialect__ = 'default' @@ -140,6 +141,14 @@ class PropertyExpressionTest(fixtures.TestBase, AssertsCompiledSQL): return A, B + def test_info(self): + A = self._fixture() + inspect(A).all_orm_descriptors.value.info["some key"] = "some value" + eq_( + inspect(A).all_orm_descriptors.value.info, + {"some key": "some value"} + ) + def test_set_get(self): A = self._fixture() a1 = A(value=5) @@ -267,6 +276,15 @@ class MethodExpressionTest(fixtures.TestBase, AssertsCompiledSQL): "foo(a.value, :foo_1) + :foo_2" ) + def test_info(self): + A = self._fixture() + inspect(A).all_orm_descriptors.value.info["some key"] = "some value" + eq_( + inspect(A).all_orm_descriptors.value.info, + {"some key": "some value"} + ) + + def test_aliased_expression(self): A = self._fixture() self.assert_compile( -- cgit v1.2.1 From 7fc08fe89af9760750899346cf81bd74e0d9150f Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 13 Aug 2014 19:45:34 -0400 Subject: - The ``info`` parameter has been added to the constructor for :class:`.SynonymProperty` and :class:`.ComparableProperty`. - The ``info`` parameter has been added as a constructor argument to all schema constructs including :class:`.MetaData`, :class:`.Index`, :class:`.ForeignKey`, :class:`.ForeignKeyConstraint`, :class:`.UniqueConstraint`, :class:`.PrimaryKeyConstraint`, :class:`.CheckConstraint`. fixes #2963 --- doc/build/changelog/changelog_10.rst | 17 +++++++++ lib/sqlalchemy/orm/descriptor_props.py | 19 ++++++++-- lib/sqlalchemy/sql/schema.py | 54 ++++++++++++++++++++++++---- lib/sqlalchemy/util/langhelpers.py | 4 ++- test/orm/test_mapper.py | 4 ++- test/sql/test_metadata.py | 65 ++++++++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 11 deletions(-) diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index c59d7c912..815de72c7 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -16,6 +16,23 @@ .. changelog:: :version: 1.0.0 + .. change:: + :tags: orm, feature + :tickets: 2963 + + The ``info`` parameter has been added to the constructor for + :class:`.SynonymProperty` and :class:`.ComparableProperty`. + + .. change:: + :tags: sql, feature + :tickets: 2963 + + The ``info`` parameter has been added as a constructor argument + to all schema constructs including :class:`.MetaData`, + :class:`.Index`, :class:`.ForeignKey`, :class:`.ForeignKeyConstraint`, + :class:`.UniqueConstraint`, :class:`.PrimaryKeyConstraint`, + :class:`.CheckConstraint`. + .. change:: :tags: orm, feature :tickets: 2971 diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index 5ed24b8c0..f0f9a6468 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -496,7 +496,7 @@ class SynonymProperty(DescriptorProperty): def __init__(self, name, map_column=None, descriptor=None, comparator_factory=None, - doc=None): + doc=None, info=None): """Denote an attribute name as a synonym to a mapped property, in that the attribute will mirror the value and expression behavior of another attribute. @@ -531,6 +531,11 @@ class SynonymProperty(DescriptorProperty): conjunction with the ``descriptor`` argument in order to link a user-defined descriptor as a "wrapper" for an existing column. + :param info: Optional data dictionary which will be populated into the + :attr:`.InspectionAttr.info` attribute of this object. + + .. versionadded:: 1.0.0 + :param comparator_factory: A subclass of :class:`.PropComparator` that will provide custom comparison behavior at the SQL expression level. @@ -556,6 +561,8 @@ class SynonymProperty(DescriptorProperty): self.descriptor = descriptor self.comparator_factory = comparator_factory self.doc = doc or (descriptor and descriptor.__doc__) or None + if info: + self.info = info util.set_creation_order(self) @@ -608,7 +615,8 @@ class SynonymProperty(DescriptorProperty): class ComparableProperty(DescriptorProperty): """Instruments a Python property for use in query expressions.""" - def __init__(self, comparator_factory, descriptor=None, doc=None): + def __init__( + self, comparator_factory, descriptor=None, doc=None, info=None): """Provides a method of applying a :class:`.PropComparator` to any Python descriptor attribute. @@ -670,10 +678,17 @@ class ComparableProperty(DescriptorProperty): The like-named descriptor will be automatically retrieved from the mapped class if left blank in a ``properties`` declaration. + :param info: Optional data dictionary which will be populated into the + :attr:`.InspectionAttr.info` attribute of this object. + + .. versionadded:: 1.0.0 + """ self.descriptor = descriptor self.comparator_factory = comparator_factory self.doc = doc or (descriptor and descriptor.__doc__) or None + if info: + self.info = info util.set_creation_order(self) def _comparator_factory(self, mapper): diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py index 69b3af306..8099dca75 100644 --- a/lib/sqlalchemy/sql/schema.py +++ b/lib/sqlalchemy/sql/schema.py @@ -76,7 +76,7 @@ class SchemaItem(SchemaEventTarget, visitors.Visitable): return [] def __repr__(self): - return util.generic_repr(self) + return util.generic_repr(self, omit_kwarg=['info']) @property @util.deprecated('0.9', 'Use ``.name.quote``') @@ -1403,6 +1403,7 @@ class ForeignKey(DialectKWArgs, SchemaItem): def __init__(self, column, _constraint=None, use_alter=False, name=None, onupdate=None, ondelete=None, deferrable=None, initially=None, link_to_name=False, match=None, + info=None, **dialect_kw): """ Construct a column-level FOREIGN KEY. @@ -1453,6 +1454,11 @@ class ForeignKey(DialectKWArgs, SchemaItem): DDL for this constraint. Typical values include SIMPLE, PARTIAL and FULL. + :param info: Optional data dictionary which will be populated into the + :attr:`.SchemaItem.info` attribute of this object. + + .. versionadded:: 1.0.0 + :param \**dialect_kw: Additional keyword arguments are dialect specific, and passed in the form ``_``. The arguments are ultimately handled by a corresponding @@ -1499,6 +1505,8 @@ class ForeignKey(DialectKWArgs, SchemaItem): self.initially = initially self.link_to_name = link_to_name self.match = match + if info: + self.info = info self._unvalidated_dialect_kw = dialect_kw def __repr__(self): @@ -2223,7 +2231,7 @@ class Constraint(DialectKWArgs, SchemaItem): __visit_name__ = 'constraint' def __init__(self, name=None, deferrable=None, initially=None, - _create_rule=None, + _create_rule=None, info=None, **dialect_kw): """Create a SQL constraint. @@ -2238,6 +2246,11 @@ class Constraint(DialectKWArgs, SchemaItem): Optional string. If set, emit INITIALLY when issuing DDL for this constraint. + :param info: Optional data dictionary which will be populated into the + :attr:`.SchemaItem.info` attribute of this object. + + .. versionadded:: 1.0.0 + :param _create_rule: a callable which is passed the DDLCompiler object during compilation. Returns True or False to signal inline generation of @@ -2265,6 +2278,8 @@ class Constraint(DialectKWArgs, SchemaItem): self.name = name self.deferrable = deferrable self.initially = initially + if info: + self.info = info self._create_rule = _create_rule util.set_creation_order(self) self._validate_dialect_kwargs(dialect_kw) @@ -2381,7 +2396,7 @@ class CheckConstraint(Constraint): """ def __init__(self, sqltext, name=None, deferrable=None, - initially=None, table=None, _create_rule=None, + initially=None, table=None, info=None, _create_rule=None, _autoattach=True): """Construct a CHECK constraint. @@ -2404,10 +2419,15 @@ class CheckConstraint(Constraint): Optional string. If set, emit INITIALLY when issuing DDL for this constraint. + :param info: Optional data dictionary which will be populated into the + :attr:`.SchemaItem.info` attribute of this object. + + .. versionadded:: 1.0.0 + """ super(CheckConstraint, self).\ - __init__(name, deferrable, initially, _create_rule) + __init__(name, deferrable, initially, _create_rule, info=info) self.sqltext = _literal_as_text(sqltext) if table is not None: self._set_parent_with_dispatch(table) @@ -2463,7 +2483,7 @@ class ForeignKeyConstraint(Constraint): def __init__(self, columns, refcolumns, name=None, onupdate=None, ondelete=None, deferrable=None, initially=None, use_alter=False, link_to_name=False, match=None, - table=None, **dialect_kw): + table=None, info=None, **dialect_kw): """Construct a composite-capable FOREIGN KEY. :param columns: A sequence of local column names. The named columns @@ -2508,6 +2528,11 @@ class ForeignKeyConstraint(Constraint): DDL for this constraint. Typical values include SIMPLE, PARTIAL and FULL. + :param info: Optional data dictionary which will be populated into the + :attr:`.SchemaItem.info` attribute of this object. + + .. versionadded:: 1.0.0 + :param \**dialect_kw: Additional keyword arguments are dialect specific, and passed in the form ``_``. See the documentation regarding an individual dialect at @@ -2517,7 +2542,7 @@ class ForeignKeyConstraint(Constraint): """ super(ForeignKeyConstraint, self).\ - __init__(name, deferrable, initially, **dialect_kw) + __init__(name, deferrable, initially, info=info, **dialect_kw) self.onupdate = onupdate self.ondelete = ondelete @@ -2888,6 +2913,11 @@ class Index(DialectKWArgs, ColumnCollectionMixin, SchemaItem): the index. Works in the same manner as that of :paramref:`.Column.quote`. + :param info=None: Optional data dictionary which will be populated + into the :attr:`.SchemaItem.info` attribute of this object. + + .. versionadded:: 1.0.0 + :param \**kw: Additional keyword arguments not mentioned above are dialect specific, and passed in the form ``_``. See the documentation regarding an @@ -2910,6 +2940,8 @@ class Index(DialectKWArgs, ColumnCollectionMixin, SchemaItem): self.expressions = expressions self.name = quoted_name(name, kw.pop("quote", None)) self.unique = kw.pop('unique', False) + if 'info' in kw: + self.info = kw.pop('info') self._validate_dialect_kwargs(kw) # will call _set_parent() if table-bound column @@ -3020,7 +3052,8 @@ class MetaData(SchemaItem): def __init__(self, bind=None, reflect=False, schema=None, quote_schema=None, - naming_convention=DEFAULT_NAMING_CONVENTION + naming_convention=DEFAULT_NAMING_CONVENTION, + info=None ): """Create a new MetaData object. @@ -3046,6 +3079,11 @@ class MetaData(SchemaItem): :class:`.Sequence`, and other objects which make usage of the local ``schema`` name. + :param info: Optional data dictionary which will be populated into the + :attr:`.SchemaItem.info` attribute of this object. + + .. versionadded:: 1.0.0 + :param naming_convention: a dictionary referring to values which will establish default naming conventions for :class:`.Constraint` and :class:`.Index` objects, for those objects which are not given @@ -3117,6 +3155,8 @@ class MetaData(SchemaItem): self.tables = util.immutabledict() self.schema = quoted_name(schema, quote_schema) self.naming_convention = naming_convention + if info: + self.info = info self._schemas = set() self._sequences = {} self._fk_memos = collections.defaultdict(list) diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index 8d6fe5a28..828e8f1f3 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -435,7 +435,7 @@ def unbound_method_to_callable(func_or_cls): return func_or_cls -def generic_repr(obj, additional_kw=(), to_inspect=None): +def generic_repr(obj, additional_kw=(), to_inspect=None, omit_kwarg=()): """Produce a __repr__() based on direct association of the __init__() specification vs. same-named attributes present. @@ -484,6 +484,8 @@ def generic_repr(obj, additional_kw=(), to_inspect=None): output.extend([repr(val) for val in getattr(obj, vargs)]) for arg, defval in kw_args.items(): + if arg in omit_kwarg: + continue try: val = getattr(obj, arg, missing) if val is not missing and val != defval: diff --git a/test/orm/test_mapper.py b/test/orm/test_mapper.py index e33c93977..0a9cbfc71 100644 --- a/test/orm/test_mapper.py +++ b/test/orm/test_mapper.py @@ -414,7 +414,9 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL): for constructor, args in [ (column_property, (users.c.name,)), (relationship, (Address,)), - (composite, (MyComposite, 'id', 'name')) + (composite, (MyComposite, 'id', 'name')), + (synonym, 'foo'), + (comparable_property, 'foo') ]: obj = constructor(info={"x": "y"}, *args) eq_(obj.info, {"x": "y"}) diff --git a/test/sql/test_metadata.py b/test/sql/test_metadata.py index 3a252c646..ff2755ab1 100644 --- a/test/sql/test_metadata.py +++ b/test/sql/test_metadata.py @@ -956,6 +956,71 @@ class ToMetaDataTest(fixtures.TestBase, ComparesTables): 'mytable.myid = othertable.myid') +class InfoTest(fixtures.TestBase): + def test_metadata_info(self): + m1 = MetaData() + eq_(m1.info, {}) + + m1 = MetaData(info={"foo": "bar"}) + eq_(m1.info, {"foo": "bar"}) + + def test_foreignkey_constraint_info(self): + fkc = ForeignKeyConstraint(['a'], ['b'], name='bar') + eq_(fkc.info, {}) + + fkc = ForeignKeyConstraint( + ['a'], ['b'], name='bar', info={"foo": "bar"}) + eq_(fkc.info, {"foo": "bar"}) + + def test_foreignkey_info(self): + fkc = ForeignKey('a') + eq_(fkc.info, {}) + + fkc = ForeignKey('a', info={"foo": "bar"}) + eq_(fkc.info, {"foo": "bar"}) + + def test_primarykey_constraint_info(self): + pkc = PrimaryKeyConstraint('a', name='x') + eq_(pkc.info, {}) + + pkc = PrimaryKeyConstraint('a', name='x', info={'foo': 'bar'}) + eq_(pkc.info, {'foo': 'bar'}) + + def test_unique_constraint_info(self): + uc = UniqueConstraint('a', name='x') + eq_(uc.info, {}) + + uc = UniqueConstraint('a', name='x', info={'foo': 'bar'}) + eq_(uc.info, {'foo': 'bar'}) + + def test_check_constraint_info(self): + cc = CheckConstraint('foo=bar', name='x') + eq_(cc.info, {}) + + cc = CheckConstraint('foo=bar', name='x', info={'foo': 'bar'}) + eq_(cc.info, {'foo': 'bar'}) + + def test_index_info(self): + ix = Index('x', 'a') + eq_(ix.info, {}) + + ix = Index('x', 'a', info={'foo': 'bar'}) + eq_(ix.info, {'foo': 'bar'}) + + def test_column_info(self): + c = Column('x', Integer) + eq_(c.info, {}) + + c = Column('x', Integer, info={'foo': 'bar'}) + eq_(c.info, {'foo': 'bar'}) + + def test_table_info(self): + t = Table('x', MetaData()) + eq_(t.info, {}) + + t = Table('x', MetaData(), info={'foo': 'bar'}) + eq_(t.info, {'foo': 'bar'}) + class TableTest(fixtures.TestBase, AssertsCompiledSQL): @testing.skip_if('mssql', 'different col format') -- cgit v1.2.1 From e2d05259caf2c7c033a0a9376c0d3b7a1b040183 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 14 Aug 2014 00:03:03 -0400 Subject: - repair against use here --- lib/sqlalchemy/testing/plugin/plugin_base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/sqlalchemy/testing/plugin/plugin_base.py b/lib/sqlalchemy/testing/plugin/plugin_base.py index 9c63a2e1d..c02f0556b 100644 --- a/lib/sqlalchemy/testing/plugin/plugin_base.py +++ b/lib/sqlalchemy/testing/plugin/plugin_base.py @@ -315,7 +315,8 @@ def _setup_requirements(argument): @post def _prep_testing_database(options, file_config): from sqlalchemy.testing import config - from sqlalchemy import schema, inspect, testing + from sqlalchemy.testing.exclusions import against + from sqlalchemy import schema, inspect if options.dropfirst: for cfg in config.Config.all_configs(): @@ -358,7 +359,7 @@ def _prep_testing_database(options, file_config): schema="test_schema") )) - if testing.against("postgresql"): + if against(cfg, "postgresql"): from sqlalchemy.dialects import postgresql for enum in inspector.get_enums("*"): e.execute(postgresql.DropEnumType( -- cgit v1.2.1