summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2012-08-27 16:44:34 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2012-08-27 16:44:34 -0400
commit656cb6461b264935027580a32ce3820a7d73bd7e (patch)
tree49f1489773a87e3bc8542e53f6d58ceb0386fa2e /lib/sqlalchemy
parent640625bc9e98dd4060a1e61c717ddc98f8b3808b (diff)
downloadsqlalchemy-656cb6461b264935027580a32ce3820a7d73bd7e.tar.gz
- [feature] declared_attr can now be used with
attributes that are not Column or MapperProperty; including any user-defined value as well as association proxy objects. [ticket:2517]
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/ext/declarative/__init__.py84
-rw-r--r--lib/sqlalchemy/ext/declarative/api.py11
-rw-r--r--lib/sqlalchemy/ext/declarative/base.py5
3 files changed, 91 insertions, 9 deletions
diff --git a/lib/sqlalchemy/ext/declarative/__init__.py b/lib/sqlalchemy/ext/declarative/__init__.py
index 8bf03748e..4849a58dc 100644
--- a/lib/sqlalchemy/ext/declarative/__init__.py
+++ b/lib/sqlalchemy/ext/declarative/__init__.py
@@ -909,14 +909,14 @@ to get it's name::
primaryjoin="Target.id==%s.target_id" % cls.__name__
)
-Mixing in deferred(), column_property(), etc.
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Mixing in deferred(), column_property(), and other MapperProperty classes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Like :func:`~sqlalchemy.orm.relationship`, all
:class:`~sqlalchemy.orm.interfaces.MapperProperty` subclasses such as
:func:`~sqlalchemy.orm.deferred`, :func:`~sqlalchemy.orm.column_property`,
etc. ultimately involve references to columns, and therefore, when
-used with declarative mixins, have the :func:`.declared_attr`
+used with declarative mixins, have the :class:`.declared_attr`
requirement so that no reliance on copying is needed::
class SomethingMixin(object):
@@ -928,6 +928,84 @@ requirement so that no reliance on copying is needed::
class Something(SomethingMixin, Base):
__tablename__ = "something"
+Mixing in Association Proxy and Other Attributes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Mixins can specify user-defined attributes as well as other extension
+units such as :func:`.association_proxy`. The usage of :class:`.declared_attr`
+is required in those cases where the attribute must be tailored specifically
+to the target subclass. An example is when constructing multiple
+:func:`.association_proxy` attributes which each target a different type
+of child object. Below is an :func:`.association_proxy` / mixin example
+which provides a scalar list of string values to an implementing class::
+
+ from sqlalchemy import Column, Integer, ForeignKey, String
+ from sqlalchemy.orm import relationship
+ from sqlalchemy.ext.associationproxy import association_proxy
+ from sqlalchemy.ext.declarative import declarative_base, declared_attr
+
+ Base = declarative_base()
+
+ class HasStringCollection(object):
+ @declared_attr
+ def _strings(cls):
+ class StringAttribute(Base):
+ __tablename__ = cls.string_table_name
+ id = Column(Integer, primary_key=True)
+ value = Column(String(50), nullable=False)
+ parent_id = Column(Integer,
+ ForeignKey('%s.id' % cls.__tablename__),
+ nullable=False)
+ def __init__(self, value):
+ self.value = value
+
+ return relationship(StringAttribute)
+
+ @declared_attr
+ def strings(cls):
+ return association_proxy('_strings', 'value')
+
+ class TypeA(HasStringCollection, Base):
+ __tablename__ = 'type_a'
+ string_table_name = 'type_a_strings'
+ id = Column(Integer(), primary_key=True)
+
+ class TypeB(HasStringCollection, Base):
+ __tablename__ = 'type_b'
+ string_table_name = 'type_b_strings'
+ id = Column(Integer(), primary_key=True)
+
+Above, the ``HasStringCollection`` mixin produces a :func:`.relationship`
+which refers to a newly generated class called ``StringAttribute``. The
+``StringAttribute`` class is generated with it's own :class:`.Table`
+definition which is local to the parent class making usage of the
+``HasStringCollection`` mixin. It also produces an :func:`.association_proxy`
+object which proxies references to the ``strings`` attribute onto the ``value``
+attribute of each ``StringAttribute`` instance.
+
+``TypeA`` or ``TypeB`` can be instantiated given the constructor
+argument ``strings``, a list of strings::
+
+ ta = TypeA(strings=['foo', 'bar'])
+ tb = TypeA(strings=['bat', 'bar'])
+
+This list will generate a collection
+of ``StringAttribute`` objects, which are persisted into a table that's
+local to either the ``type_a_strings`` or ``type_b_strings`` table::
+
+ >>> print ta._strings
+ [<__main__.StringAttribute object at 0x10151cd90>,
+ <__main__.StringAttribute object at 0x10151ce10>]
+
+When constructing the :func:`.association_proxy`, the
+:class:`.declared_attr` decorator must be used so that a distinct
+:func:`.association_proxy` object is created for each of the ``TypeA``
+and ``TypeB`` classes.
+
+.. versionadded:: 0.8 :class:`.declared_attr` is usable with non-mapped
+ attributes, including user-defined attributes as well as
+ :func:`.association_proxy`.
+
Controlling table inheritance with mixins
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py
index 143468c13..1a73e4f6d 100644
--- a/lib/sqlalchemy/ext/declarative/api.py
+++ b/lib/sqlalchemy/ext/declarative/api.py
@@ -101,11 +101,6 @@ class declared_attr(interfaces._MappedAttribute, property):
"""Mark a class-level method as representing the definition of
a mapped property or special declarative member name.
- .. versionchanged:: 0.6.{2,3,4}
- ``@declared_attr`` is available as
- ``sqlalchemy.util.classproperty`` for SQLAlchemy versions
- 0.6.2, 0.6.3, 0.6.4.
-
@declared_attr turns the attribute into a scalar-like
property that can be invoked from the uninstantiated class.
Declarative treats attributes specifically marked with
@@ -146,6 +141,12 @@ class declared_attr(interfaces._MappedAttribute, property):
else:
return {"polymorphic_identity":cls.__name__}
+ .. versionchanged:: 0.8 :class:`.declared_attr` can be used with
+ non-ORM or extension attributes, such as user-defined attributes
+ or :func:`.association_proxy` objects, which will be assigned
+ to the class at class construction time.
+
+
"""
def __init__(self, fget, *arg, **kw):
diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py
index e42ec2645..40c8c6ef6 100644
--- a/lib/sqlalchemy/ext/declarative/base.py
+++ b/lib/sqlalchemy/ext/declarative/base.py
@@ -136,7 +136,7 @@ def _as_declarative(cls, classname, dict_):
clsregistry.add_class(classname, cls)
our_stuff = util.OrderedDict()
- for k in dict_:
+ for k in list(dict_):
# TODO: improve this ? all dunders ?
if k in ('__table__', '__tablename__', '__mapper_args__'):
@@ -153,6 +153,9 @@ def _as_declarative(cls, classname, dict_):
"left at the end of the line?" % k)
continue
if not isinstance(value, (Column, MapperProperty)):
+ if not k.startswith('__'):
+ dict_.pop(k)
+ setattr(cls, k, value)
continue
if k == 'metadata':
raise exc.InvalidRequestError(