Encapsulating insert/update logic of mapped classes in the class itself

39 views
Skip to first unread message

Alex Grönholm

unread,
Jun 2, 2014, 1:47:45 AM6/2/14
to sqlal...@googlegroups.com
This has been a problem for me for years.

class DeliveryAddress(Base, Address):
    ...
    delivery_method = Column(String)
    ...

@event.listens_for(DeliveryAddress, 'before_insert')
def before_insert_deliveryaddress(mapper, connection, target):
    settings = object_session(target).query(Settings).one()
    target.delivery_method = settings.default_delivery_method

Is there absolutely no way to encapsulate this logic cleanly in the actual class?

Michael Bayer

unread,
Jun 2, 2014, 7:27:47 AM6/2/14
to sqlal...@googlegroups.com
I use an attribute event for that, and more often just a simple @validates.

@validates(“default_delivery_method”)
def _set_delivery_method(self, key, value):
self.settings.delivery_method = value
return value



Alex Grönholm

unread,
Jun 2, 2014, 8:04:13 AM6/2/14
to sqlal...@googlegroups.com
That's the first thing I tried, but validators don't get called unless you explicitly set a value to the column.

So for something like session.add(DeliveryAddress()), the validator doesn't get called.

Michael Bayer

unread,
Jun 2, 2014, 9:44:33 AM6/2/14
to sqlal...@googlegroups.com
the ORM can’t persist any value for “delivery_method” unless there is an actual attribute set event.

If you are saying session.add(DeliveryAddress()) and flushing, these are the options for “deliveryaddress.delivery_method”:

1. the value has no setting and is not present in the INSERT statement - it gets set as NULL in the DB.

2. the value has no setting, but you have a python or server side default set up on it.  At before_insert() time, that value is still None so that can’t be what you’re doing.

3. The DeliveryAddress() constructor sets delivery_method, so there’s an attribute set event.

So I can only guess that you’re looking for the None here?   I guess there’s some odd relationship to this Settings object such that it already exists in the DB with some other value such that you actually need to positively set None.      In any case, to have something happen upon DeliveryAddress(), you can either place that logic as part of __init__() (regular Python!) or if that bothers you, you can also set up the init() event for the DeliveryAddress class.

If all of these options don’t suit you and you absolutely insist on the before_insert event, it is very easy to add to your declarative base a mixin that sets these up for you (see http://docs.sqlalchemy.org/en/rel_0_9/orm/extensions/declarative.html#declare-last):

class BeforeInsertMixin(object):
    @classmethod
    def __declare_last__(cls):
        if hasattr(cls, ‘before_insert’):
            event.listen(cls, ‘before_insert’, cls.before_insert)

Base = declarative_base(cls=BeforeInsertMixin)

class DeliveryAddress(Base):
    # …

    @classmethod
    def before_insert(cls, mapper, connection, target):
        #…


if you want to change the signature, no problem:

class BeforeInsertMixin(object):
    @classmethod
    def __declare_last__(cls):
        if hasattr(cls, ‘before_insert’):
            @event.listens_for(cls, ‘before_insert’)
            def before_insert(mapper, connection, target):
                target.before_insert(object_session(target))


class DeliveryAddress(Base):
    # …

    def before_insert(self, session):
        #…
            
These are “frameworky” types of hooks that SQLAlchemy would prefer to remain agnostic of, but it provides for you all the components you need to create whatever system of hooks you’d like.





-- 
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 tosqlalchemy+...@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.

Alex Grönholm

unread,
Jun 2, 2014, 11:42:25 AM6/2/14
to sqlal...@googlegroups.com
02.06.2014 16:44, Michael Bayer kirjoitti:
the ORM can’t persist any value for “delivery_method” unless there is an actual attribute set event.

If you are saying session.add(DeliveryAddress()) and flushing, these are the options for “deliveryaddress.delivery_method”:

1. the value has no setting and is not present in the INSERT statement - it gets set as NULL in the DB.

2. the value has no setting, but you have a python or server side default set up on it.  At before_insert() time, that value is still None so that can’t be what you’re doing.

3. The DeliveryAddress() constructor sets delivery_method, so there’s an attribute set event.

So I can only guess that you’re looking for the None here?   I guess there’s some odd relationship to this Settings object such that it already exists in the DB with some other value such that you actually need to positively set None.      In any case, to have something happen upon DeliveryAddress(), you can either place that logic as part of __init__() (regular Python!) or if that bothers you, you can also set up the init() event for the DeliveryAddress class.
Settings is a singleton (just one row with ID 1). It contains the default values for a number of things.

I will have a look at __declare_last__, thanks!

That said, sometimes I need to react to inserts and insert other rows then. Attribute events won't help me there.
So far I've ended up making a module for these listeners and I just had a single listener for each event (before_insert or before_flush) with a lot of if..elif...branching.
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/-s9GWB3hfso/unsubscribe.
To unsubscribe from this group and all its topics, send an email to sqlalchemy+...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages