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.
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:
- Easily extend and change what you listen for and what you register.
- Override the create_time method for certain classes
- 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.
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)
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):
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()