"cascading" declared_attr ?
use case:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base, declared_attr, cascading_declared_attr
Base = declarative_base()
class HasId(object):
@cascading_declared_attr
def id(cls):
if cls.__name__=='Content':
return Column('id', Integer, primary_key=True)
else:
return Column('id', Integer, ForeignKey('content.id'), primary_key=True)
class Content(HasId, Base):
content_type = Column(String(20))
@cascading_declared_attr
def __tablename__(cls):
return cls.__name__.lower()
class Project(Content):
currency = Column(String(3))
target = Column(Integer)
current = Column(Integer)
#!diff
diff --git a/lib/sqlalchemy/ext/declarative/__init__.py b/lib/sqlalchemy/ext/declarative/__init__.py
index 0ee4e33..4566324 100644
--- a/lib/sqlalchemy/ext/declarative/__init__.py
+++ b/lib/sqlalchemy/ext/declarative/__init__.py
@@ -1300,7 +1300,7 @@ Mapped instances then make usage of
from .api import declarative_base, synonym_for, comparable_using, \
instrument_declarative, ConcreteBase, AbstractConcreteBase, \
DeclarativeMeta, DeferredReflection, has_inherited_table,\
- declared_attr, as_declarative
+ declared_attr, cascading_declared_attr, as_declarative
__all__ = ['synonym_for', 'has_inherited_table',
diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py
index 2418c6e..dc6c42b 100644
--- a/lib/sqlalchemy/ext/declarative/api.py
+++ b/lib/sqlalchemy/ext/declarative/api.py
@@ -162,6 +162,26 @@ class declared_attr(interfaces._MappedAttribute, property):
def __get__(desc, self, cls):
return desc.fget(cls)
+class cascading_declared_attr(declared_attr):
+ """A :class:`.declared_attr` that will be invoked for all subclasses.
+
+ Use :class:`.cascading_declared_attr` when a particular ``@declared_attr``
+ needs to be invoked individually for each subclass in a hierarchy::
+
+ class HasId(object):
+ @cascading_declared_attr
+ def id(cls):
+ if has_inherited_table(cls):
+ return Column(Integer, ForeignKey("content.id"), primary_key=True)
+ else:
+ return Column(Integer, primary_key=True)
+
+ class Content(HasId, Base):
+ pass
+
+ class SubContent(Content):
+ pass
+ """
def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
name='Base', constructor=_declarative_constructor,
diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py
index 4fda9c7..a8702cc 100644
--- a/lib/sqlalchemy/ext/declarative/base.py
+++ b/lib/sqlalchemy/ext/declarative/base.py
@@ -31,7 +31,7 @@ def _declared_mapping_info(cls):
def _as_declarative(cls, classname, dict_):
- from .api import declared_attr
+ from .api import declared_attr, cascading_declared_attr
# dict_ will be a dictproxy, which we can't write to, and we need to!
dict_ = dict(dict_)
@@ -96,6 +96,12 @@ def _as_declarative(cls, classname, dict_):
"not applying to subclass %s."
% (base.__name__, name, base, cls))
continue
+ elif isinstance(obj, cascading_declared_attr):
+ ret = obj.__get__(obj, cls)
+ dict_[name] = column_copies[obj] = ret
+ if isinstance(ret, (Column, MapperProperty)) and \
+ ret.doc is None:
+ ret.doc = obj.__doc__
elif base is not cls:
# we're a mixin.
if isinstance(obj, Column):
@@ -125,8 +131,8 @@ def _as_declarative(cls, classname, dict_):
"be declared as @declared_attr callables "
"on declarative mixin classes.")
elif isinstance(obj, declarative_props):
- dict_[name](name) = ret = \
- column_copies[obj] = getattr(cls, name)
+ ret = getattr(cls, name)
+ dict_[name] = column_copies[obj] = ret
if isinstance(ret, (Column, MapperProperty)) and \
ret.doc is None:
ret.doc = obj.__doc__
Comments (8)
-
reporter -
reporter - edited description
-
reporter - changed milestone to 1.0
-
Interesting. Not long ago I was trying to solve smth. like this in an inheritance context where I have a augmented base class, a (some) "parent" class(es) inheriting from base and a couple of "child" classes inheriting from "parent".
I'm setting the id/pk in the base class so all the "parents" have their id, but then had to set the foreign key on all "children" and thought it's un-DRY (wet? :) ). I tried to use has_parent_table (the way you do above) until I realised I can't because it doesn't work for columns.
In one case I needed to use a mixin for all the children (because of a unique constraint I had no idea how to create otherwise) so I could easily add the id with the foreign key to it. In the other case I still have the id's defined for every child.
It would be great if the solution you proposed would work for inheritance contexts too. (Maybe this is in your code already?)
-
reporter i dont really know. can you post a simple example? try out the patch here?
-
With your patch it works as I had desired.
from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.ext.declarative import declarative_base, declared_attr, cascading_declared_attr, has_inherited_table class Base(object): @cascading_declared_attr def id(cls): if has_inherited_table(cls): parent_table = cls.__bases__[0].__tablename__ foreign_key = '{0}.id'.format(parent_table) return Column(Integer, ForeignKey(foreign_key), primary_key=True) return Column(Integer, primary_key=True) @declared_attr def __tablename__(cls): return cls.__name__.lower() Base = declarative_base(cls=Base) class Content(Base): content_type = Column(String(20)) class Project(Content): currency = Column(String(3)) target = Column(Integer) current = Column(Integer)
PS: In my first comment I had mistakenly written that "has_parent_table()" had not worked but I actually confused it with @declared_attr. (What you said in this google group, might be worth adding to the docs? )
FWIW Given that tablename was already mapped on subclasses anyway it wouldn't be necessary to use @cascading_declared_attr for it or do I miss something?
Out of curiosity: What exactly blocks you from implementing it as a namespace attribute for @declared_attr?
If there is anything else (I think I haven't really done something yet) I can do for SQLAlchemy, I would be glad to try to.
-
reporter -
reporter - changed status to duplicate
Duplicate of
#3150. - Log in to comment
note that I don't like this name too much. It would be nicer if declared_attr could be the namespace, like: