On Feb 10, 2014, at 2:36 PM, Chris Withers <ch...@simplistix.co.uk> wrote:

> Hi All,
> 
> I'm trying to be efficient with my code, but it seems to tripping SQLAlchemy 
> up.
> 
> So, basically I want to have a schema with two tables, content and project, 
> to represent two three types of object:

if you poke around with print statements or pdb, you can see that the “id” 
declared_attr is run only once, for Content.   this is because Content is 
mapped and your “id” declared_attr function is replaced with the mapped 
attribute “id”.

If you make a mixin like HasId and put it there, it still doesn’t work.   
Declarative finds the HasId mixin and the “id” function, but then when it 
resolves what “Project.id” really is, it again just calls getattr() and doesn’t 
call your function.   

Why’s it like that?  I don’t know, but I’m 99% sure at least three tests will 
fail if I change it, lets try:

Code like this:

class HasId(object):
    @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))

    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()

class Project(Content):
    currency  = Column(String(3))
    target = Column(Integer)
    current = Column(Integer)

so lets tell declarative to pull off of HasId.id when it scans “Project”, 
instead of getattr(Project, “id”):

--- a/lib/sqlalchemy/ext/declarative/base.py
+++ b/lib/sqlalchemy/ext/declarative/base.py
@@ -126,7 +126,7 @@ def _as_declarative(cls, classname, dict_):
                         "on declarative mixin classes.")
                 elif isinstance(obj, declarative_props):
                     dict_[name] = ret = \
-                            column_copies[obj] = getattr(cls, name)
+                            column_copies[obj] = getattr(base, name)
                     if isinstance(ret, (Column, MapperProperty)) and \
                         ret.doc is None:
                         ret.doc = obj.__doc__

your test case works, but lets see the tests - six errors and four failures: 
https://gist.github.com/zzzeek/8925277

So we can’t do that.    You know I love the mixins/declared_attr thing, but 
it’s hard.  There’s craploads of tests for it and the amount of decisions we’ve 
packed in there is already pretty intricate.

There’s some other variants that reduce the number of test failures, like this:

diff --git a/lib/sqlalchemy/ext/declarative/base.py 
b/lib/sqlalchemy/ext/declarative/base.py
index 4fda9c7..b74809e 100644
--- a/lib/sqlalchemy/ext/declarative/base.py
+++ b/lib/sqlalchemy/ext/declarative/base.py
@@ -126,7 +126,7 @@ def _as_declarative(cls, classname, dict_):
                         "on declarative mixin classes.")
                 elif isinstance(obj, declarative_props):
                     dict_[name] = ret = \
-                            column_copies[obj] = getattr(cls, name)
+                            column_copies[obj] = obj.__get__(obj, cls)
                     if isinstance(ret, (Column, MapperProperty)) and \
                         ret.doc is None:
                         ret.doc = obj.__doc__


but still lots of failures; there’s a lot of use cases that just expect 
@declared_attr to hit once, then replace itself on the mapped class.

So the next idea is to just make a whole different command to do this, I 
sketched that up here:  www.sqlalchemy.org/trac/ticket/2952  

but I’m not really sure what I want to do with that.   mixins work great for 
single classes but within a hierarchy not so much, and adding more options 
makes me worried that we’re just adding flags rather than fixing the central 
idea somehow.  


Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

Reply via email to