Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sqlalchemy mixins / and event listener

I am attempting 2 new things at once, so assistance in both simplifying and clarifying is appreciated.

from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy import Column, Float, event

class TimeStampMixin(object):

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

    created = Column(Float)
    modified = Column(Float)
    def __init__(self, created = None,
                       modified = None):
        self.created = created
        self.modified = modified

def create_time(mapper, connection, target):
    target.created = time()

#def modified_time(mapper, connection, target):
#    target.modified = time()

event.listen(TimeStampMixin, 'before_insert', create_time)
#event.listen(TimeStampMixin, 'before_update', modified_time)

So I want to create a mixin I can apply in any class:

class MyClass(TimeStampMixin, Base):
    etc, etc, etc

This class inherits functionality that creates a timestamp on creation and creates/modifies a timestamp on update.

on import I get this error:

raise exc.UnmappedClassError(class_)
sqlalchemy.orm.exc.UnmappedClassError: Class 'db.database.TimeStampMixin' is not mapped

aaaand I'm stumped at this point.

like image 847
blueblank Avatar asked Oct 05 '12 20:10

blueblank


3 Answers

Attach your listener inside the class method and it will attach the event to the child class.

class TimeStampMixin(object):
    @staticmethod
    def create_time(mapper, connection, target):
        target.created = time()

    @classmethod
    def __declare_last__(cls):
        # get called after mappings are completed
        # http://docs.sqlalchemy.org/en/rel_0_7/orm/extensions/declarative.html#declare-last
        event.listen(cls, 'before_insert', cls.create_time)
like image 92
deBrice Avatar answered Nov 14 '22 23:11

deBrice


Here's what I'd do to listen on before_insert events: add a classmethod to your TimeStampMixin that registers the current class and handles setting creation time.

E.g.

class TimeStampMixin(object):

    # other class methods

    @staticmethod
    def create_time(mapper, connection, target):
        target.created = time()

    @classmethod
    def register(cls):
        sqlalchemy.event.listen(cls, 'before_insert', cls.create_time)

That way, you can:

  1. Easily extend and change what you listen for and what you register.
  2. Override the create_time method for certain classes
  3. Be explicit about which methods need to have their timestamps set.

You can use it simply:

class MyMappedClass(TimeStampMixin, Base):
    pass

MyMappedClass.register()

Simple, very clear, no magic, but still encapsulates like you want.

like image 16
Jeff Tratner Avatar answered Nov 14 '22 23:11

Jeff Tratner


The best way in modern SqlAlchemy is to use the @listens_for decorator with propagate=True.

from datetime import datetime
from sqlalchemy import Column, Float
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.event import listens_for

class TimestampMixin():
    @declared_attr
    def created(cls):
        return Column(DateTime(timezone=True))

@listens_for(TimeStampMixin, "init", propagate=True)
def timestamp_init(target, args, kwargs):
    kwargs["created"] = datetime.utcnow()
like image 5
Mike Haboustak Avatar answered Nov 14 '22 23:11

Mike Haboustak