Overwriting __init__() after class definition breaks sqlalchemy with declarative

55 views
Skip to first unread message

Eric Atkin

unread,
Aug 14, 2015, 2:41:31 AM8/14/15
to sqlalchemy
Hi,

I've written a class decorator to define a boilerplate __init__ on some of my models that inherit from a declarative_base superclass. The problem is that sqlalchemy.orm.instrumentation._generate_init() has already installed an __init__ and when I overwrite that, things break with "object has no attribute '_sa_instance_state'" exceptions. I've provided a sample below. It actually throws a different exception than my code but I think the root issue is the same, that is, my __init__ replaced the generated one and so the ClassManager events are not being emitted.

Is there some blessed way to add an __init__ method after a class is already defined or is that just impossible with sqlalchemy's metaprogramming environment?

Love the library. Very powerful and excellent documentation. Thanks.
Eric

$ python
Python 2.7.10 (default, May 26 2015, 04:16:29)
[GCC 5.1.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import psycopg2
>>> import sqlalchemy
>>>
>>> psycopg2.__version__
'2.6 (dt dec pq3 ext lo64)'
>>> conn = psycopg2.connect("dbname=dispatch_dev")
>>> cur = conn.cursor()
>>> cur.execute("SELECT version();")
>>> cur.fetchone()
('PostgreSQL 9.4.4 on x86_64-unknown-linux-gnu, compiled by gcc (GCC) 5.1.0, 64-bit',)
>>>
>>> sqlalchemy.__version__
'0.9.7'
>>>
>>> from collections import OrderedDict
>>> from decimal import Decimal
>>> from sqlalchemy import Column, Integer, Numeric
>>> from sqlalchemy.ext.declarative import declarative_base
>>>
>>> def licensed(licenses):
...     def decorate(cls):
...         def ___init__(self, **kwargs):
...             for k, v in self.licenses.items():
...                 kwargs.setdefault('{}_license_rate'.format(k), v)
...             super(cls, self).__init__(**kwargs)
...         cls.__init__ = ___init__
...         for k, v in licenses.items():
...             licenses[k] = Decimal(v)
...         cls.licenses = licenses
...         for license in licenses:
...             setattr(cls, '{}_license_rate'.format(license), Column(Numeric, nullable=False))
...         return cls
...     return decorate
...
>>>
>>> Base = declarative_base()
>>>
>>> @licensed(OrderedDict((('foo', 100), ('bar', 150))))
... class Instance_Link(Base):
...     __tablename__ = 'instance_link'
...     id = Column(Integer, primary_key=True)
...
>>> Instance_Link(foo_license_rate=50)
Traceback (most recent call last):
 
File "<stdin>", line 1, in <module>
 
File "<stdin>", line 6, in ___init__
 
File "(...)/env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 526, in _declarative_constructor
    setattr
(self, k, kwargs[k])
 
File "(...)/env/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py", line 225, in __set__
   
self.impl.set(instance_state(instance),
AttributeError: 'NoneType' object has no attribute 'set'
>>>

Mike Bayer

unread,
Aug 14, 2015, 11:07:48 AM8/14/15
to sqlal...@googlegroups.com
this code is incorrect from a Python perspective.   You're removing the original `__init__` method entirely and it is never called; the attempt to call it using super() just calls object.__init__.    SQLAlchemy is already decorating the __init__ method of the mapped class so you can't just throw it away, you can decorate it but you need to make sure its still called.

Here is a plain demonstration without any SQLAlchemy:

def licensed():
    def decorate(cls):
        def ___init__(self, **kwargs):
            print "magic new init!"
            super(cls, self).__init__(**kwargs)
        cls.__init__ = ___init__
        return cls
    return decorate


@licensed()
class SomeClass(object):
    def __init__(self):
        print "normal init!"


SomeClass()

Only "magic new init!" is printed.   SomeClass.__init__ is never called.


Here's the correct way to decorate a function in this context:

def licensed(licenses):
    def decorate(cls):
        orig_init = cls.__init__

        def ___init__(self, **kwargs):

            for k, v in self.licenses.items():
                kwargs.setdefault('{}_license_rate'.format(k), v)
            orig_init(self, **kwargs)
        cls.__init__ = ___init__
--
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.
To post to this group, send email to sqlal...@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Eric Atkin

unread,
Aug 14, 2015, 2:15:29 PM8/14/15
to sqlal...@googlegroups.com
This works. Thank you for the quick response and great libraries (I use Mako as well).
Eric

--
You received this message because you are subscribed to a topic in the Google Groups "sqlalchemy" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/sqlalchemy/u_WdnuCSvCU/unsubscribe.
To unsubscribe from this group and all its topics, send an email to sqlalchemy+...@googlegroups.com.

To post to this group, send email to sqlal...@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.



--
CONFIDENTIALITY NOTICE:  This electronic message transmission contains 
information from Eric G. Atkin, which may be confidential, may be protected by 
the attorney-client or other applicable privileges or otherwise constitutes 
non-public information.  If you are not the intended recipient of this message 
please be aware that any disclosure, copying, distribution or other use of this 
information is unauthorized and may be unlawful.  If you have received this 
transmission in error please immediately notify the sender by replying to this 
message and then permanently delete the communication from your computer and/or 
network systems.

Reply all
Reply to author
Forward
0 new messages