summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRodrigo Menezes <rodrigo.menezes@moat.com>2014-08-14 17:08:55 -0400
committerRodrigo Menezes <rodrigo.menezes@moat.com>2014-08-14 17:08:55 -0400
commit8af9c7670e07037259dc89510559d34a4e7ccc6f (patch)
tree0c3a79ffbf82387177d1c236f4148731a0e06b4f
parent649f06759d933f4aacdfbb302e845e2bcb5e7641 (diff)
parente2d05259caf2c7c033a0a9376c0d3b7a1b040183 (diff)
downloadsqlalchemy-8af9c7670e07037259dc89510559d34a4e7ccc6f.tar.gz
Merge branch 'master' of https://github.com/rclmenezes/sqlalchemy
-rw-r--r--doc/build/changelog/changelog_10.rst35
-rw-r--r--doc/build/orm/internals.rst2
-rw-r--r--lib/sqlalchemy/dialects/postgresql/__init__.py5
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py91
-rw-r--r--lib/sqlalchemy/ext/associationproxy.py6
-rw-r--r--lib/sqlalchemy/ext/hybrid.py12
-rw-r--r--lib/sqlalchemy/orm/attributes.py2
-rw-r--r--lib/sqlalchemy/orm/base.py76
-rw-r--r--lib/sqlalchemy/orm/descriptor_props.py19
-rw-r--r--lib/sqlalchemy/orm/instrumentation.py4
-rw-r--r--lib/sqlalchemy/orm/interfaces.py32
-rw-r--r--lib/sqlalchemy/orm/mapper.py10
-rw-r--r--lib/sqlalchemy/orm/state.py2
-rw-r--r--lib/sqlalchemy/orm/util.py4
-rw-r--r--lib/sqlalchemy/sql/schema.py54
-rw-r--r--lib/sqlalchemy/testing/plugin/plugin_base.py9
-rw-r--r--lib/sqlalchemy/util/langhelpers.py4
-rw-r--r--test/dialect/postgresql/test_reflection.py66
-rw-r--r--test/ext/test_hybrid.py18
-rw-r--r--test/orm/test_mapper.py4
-rw-r--r--test/sql/test_metadata.py65
21 files changed, 415 insertions, 105 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst
index 1d501f85b..815de72c7 100644
--- a/doc/build/changelog/changelog_10.rst
+++ b/doc/build/changelog/changelog_10.rst
@@ -17,6 +17,33 @@
: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
+
+ 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
:pullrequest: bitbucket:29
@@ -26,6 +53,14 @@
courtesy Malik Diarra.
.. 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
The MySQL dialect will now disable :meth:`.ConnectionEvents.handle_error`
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/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'
)
diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py
index b3506f5d2..19d2c7ca4 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,11 +1587,32 @@ 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 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(self.bind, schema)
+
class CreateEnumType(schema._CreateDropBase):
__visit_name__ = "create_enum_type"
@@ -2056,7 +2094,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 = []
@@ -2130,10 +2173,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:
@@ -2444,7 +2486,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 {}
@@ -2460,31 +2503,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'
- ORDER BY "name", e.oid -- e.oid gives us label order
"""
+ 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/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 a85f59f37..3390ceec4 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])
@@ -419,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.
@@ -456,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::
@@ -484,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/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/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 9bc1c3dd0..49ec99ce4 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
+from .base import InspectionAttr, _MappedAttribute
import collections
+# imported later
+MapperExtension = SessionExtension = AttributeExtension = None
__all__ = (
'AttributeExtension',
@@ -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,
@@ -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/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.
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 ``<obj>.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 ``<dialectname>_<argname>``. 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 <value> 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 <value> 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 ``<dialectname>_<argname>``. 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
``<dialectname>_<argname>``. 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/testing/plugin/plugin_base.py b/lib/sqlalchemy/testing/plugin/plugin_base.py
index 4c245e9e9..c02f0556b 100644
--- a/lib/sqlalchemy/testing/plugin/plugin_base.py
+++ b/lib/sqlalchemy/testing/plugin/plugin_base.py
@@ -315,6 +315,7 @@ def _setup_requirements(argument):
@post
def _prep_testing_database(options, file_config):
from sqlalchemy.testing import config
+ from sqlalchemy.testing.exclusions import against
from sqlalchemy import schema, inspect
if options.dropfirst:
@@ -358,6 +359,14 @@ def _prep_testing_database(options, file_config):
schema="test_schema")
))
+ if against(cfg, "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/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/dialect/postgresql/test_reflection.py b/test/dialect/postgresql/test_reflection.py
index 313be0b37..678c35881 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
@@ -722,6 +723,71 @@ class ReflectionTest(fixtures.TestBase):
for fk in fks:
eq_(fk, fk_ref[fk['name']])
+ @testing.provide_metadata
+ def test_inspect_enums_schema(self):
+ conn = testing.db.connect()
+ 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.get_enums('test_schema'), [{
+ 'visible': False,
+ 'name': 'mood',
+ 'schema': 'test_schema',
+ 'labels': ['sad', 'ok', 'happy']
+ }])
+
+ @testing.provide_metadata
+ 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(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):
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(
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')