Reputation: 4894
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.
Upvotes: 15
Views: 7837
Reputation: 2296
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()
Upvotes: 6
Reputation: 17076
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:
You can use it simply:
class MyMappedClass(TimeStampMixin, Base):
pass
MyMappedClass.register()
Simple, very clear, no magic, but still encapsulates like you want.
Upvotes: 16
Reputation: 485
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)
Upvotes: 39
Reputation: 620
you can also do it like this:
from sqlalchemy.orm.interfaces import MapperExtension
class BaseExtension(MapperExtension):
"""Base entension class for all entities """
def before_insert(self, mapper, connection, instance):
""" set the created_at """
instance.created = datetime.datetime.now()
def before_update(self, mapper, connection, instance):
""" set the updated_at """
instance.modified = datetime.datetime.now()
class TimeStampMixin(object):
id = Column(Integer, primary_key=True, autoincrement=True)
created = Column(DateTime())
modified = Column(DateTime())
__table_args__ = {
'mysql_engine': 'InnoDB',
'mysql_charset': 'utf8'
}
__mapper_args__ = { 'extension': BaseExtension() }
and define your classes like:
class User(TimeStampMixin, Base):
Upvotes: 3