Cannot use classproperty for class attributes

Issue #1749 resolved
Former user created an issue

Well, there is a simple usecase:

class MyHistoryMixin:
    @classproperty
    def __tablename__(cls):
        return cls.parent_name + '_changelog'

class MyModel(Base, MyHistoryMixin):
    parent_name = 'foo'

gives me that traceback:

Traceback (most recent call last):
  File "/usr/lib/python2.6/site-packages/nose/case.py", line 183, in runTest
    self.test(*self.arg)
  File "/home/ente/Projects/sqlalchemy/test/ext/test_declarative.py", line 1938, in test_table_name_inhertited_from_property
    class MyModel(Base, MyHistoryMixin):
  File "/home/ente/Projects/sqlalchemy/lib/sqlalchemy/ext/declarative.py", line 712, in __init__
    _as_declarative(cls, classname, cls.__dict__)
  File "/home/ente/Projects/sqlalchemy/lib/sqlalchemy/ext/declarative.py", line 656, in _as_declarative
    "specified and does not inherit from an existing table-mapped class." % cls
InvalidRequestError: Class <class 'test.ext.test_declarative.MyModel'> does not have a __table__ or __tablename__ specified and does not inherit from an existing table-mapped class.

I just already created a testcase for that: http://paste.pocoo.org/show/194202/

I guess SQLAlchemy is doing something wrong here, since it works properly on other classes not using sqlalchemy stuff.

Would be cool if something like that is possible.

Regards, Christopher.

Comments (5)

  1. Mike Bayer repo owner

    this is the likely patch:

    --- a/lib/sqlalchemy/ext/declarative.py Fri Mar 26 13:48:13 2010 -0400
    +++ b/lib/sqlalchemy/ext/declarative.py Fri Mar 26 13:58:05 2010 -0400
    @@ -548,9 +548,9 @@
                     obj = getattr(base,name, None)
                     if isinstance(obj, Column):
                         dict_[name](name)=column_copies[obj](obj)=obj.copy()
    -            get_mapper_args = get_mapper_args or getattr(base,'__mapper_args__',None)
    -            get_table_args = get_table_args or getattr(base,'__table_args__',None)
    -            tablename = getattr(base,'__tablename__',None)
    +            get_mapper_args = get_mapper_args or getattr(cls,'__mapper_args__',None)
    +            get_table_args = get_table_args or getattr(cls,'__table_args__',None)
    +            tablename = getattr(cls,'__tablename__',None)
                 if tablename:
                     # subtle: if tablename is a descriptor here, we actually
                     # put the wrong value in, but it serves as a marker to get
    
  2. Mike Bayer repo owner

    this is a refinement of that patch. but I'm still not remembering why we have to dance around cls.foo as much as we are here.

    --- a/lib/sqlalchemy/ext/declarative.py Fri Mar 26 13:48:13 2010 -0400
    +++ b/lib/sqlalchemy/ext/declarative.py Fri Mar 26 14:12:14 2010 -0400
    @@ -531,31 +531,32 @@
    
     def _as_declarative(cls, classname, dict_):
    
    -    # doing it this way enables these attributes to be descriptors,
    -    # see below...
    -    get_mapper_args = '__mapper_args__' in dict_
    -    get_table_args = '__table_args__' in dict_
    -    
         # dict_ will be a dictproxy, which we can't write to, and we need to!
         dict_ = dict(dict_)
    
         column_copies = dict()
    -
    +    unmapped_mixins = False
         for base in cls.__bases__:
             names = dir(base)
             if not _is_mapped_class(base):
    +            unmapped_mixins = True
                 for name in names:
                     obj = getattr(base,name, None)
                     if isinstance(obj, Column):
                         dict_[name](name)=column_copies[obj](obj)=obj.copy()
    -            get_mapper_args = get_mapper_args or getattr(base,'__mapper_args__',None)
    -            get_table_args = get_table_args or getattr(base,'__table_args__',None)
    -            tablename = getattr(base,'__tablename__',None)
    -            if tablename:
    -                # subtle: if tablename is a descriptor here, we actually
    -                # put the wrong value in, but it serves as a marker to get
    -                # the right value value...
    -                dict_['__tablename__']('__tablename__')=tablename
    +
    +    # doing it this way enables these attributes to be descriptors
    +    get_mapper_args = '__mapper_args__' in dict_
    +    get_table_args = '__table_args__' in dict_
    +    if unmapped_mixins:
    +        get_mapper_args = get_mapper_args or getattr(cls,'__mapper_args__',None)
    +        get_table_args = get_table_args or getattr(cls,'__table_args__',None)
    +        tablename = getattr(cls,'__tablename__',None)
    +        if tablename:
    +            # subtle: if tablename is a descriptor here, we actually
    +            # put the wrong value in, but it serves as a marker to get
    +            # the right value value...
    +            dict_['__tablename__']('__tablename__')=tablename
    
         # now that we know whether or not to get these, get them from the class
         # if we should, enabling them to be decorators
    
  3. Log in to comment