Skip to content

Instantly share code, notes, and snippets.

@eevee
Created March 20, 2014 18:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eevee/9670592 to your computer and use it in GitHub Desktop.
Save eevee/9670592 to your computer and use it in GitHub Desktop.
sqlalchemy declarative deferred attributes
class DeferredAttribute(object):
"""Minor hackery used with `deferred_attr_factory`."""
# Hopefully, more or less, self-explanatory. The __call__ is triggered at
# class creation time by declared_attr, which passes in the class itself,
# and then we do the "post" setup by listening for mapper_configured.
def __init__(self, generator):
self.generator = generator
self.done = False
def __call__(self, mapped_class):
attribute = next(self.generator)
listen(mapped_class, "mapper_configured", self._on_mapper_configured)
return attribute
def _on_mapper_configured(self, mapper, mapped_class):
# Only do this setup ONCE! It's possible for mapper_configured to fire
# more than once, in the face of egregious shenanigans.
if self.done:
return
self.done = True
# Run the rest of the generator and close it
try:
self.generator.send(mapped_class)
except StopIteration:
pass
self.generator.close()
def deferred_attr_factory(f):
"""Wrap a function to produce "deferred" declarative attributes. The
wrapped function can provide a column (or whatever) to be added to some
mapped class, but it can also do some further setup after the class is
fully constructed.
Because the two steps may happen some time apart, the wrapped function MUST
be a generator, yielding the object it wants to "return" into the class
body. The generator will later be sent the entire class to do with as it
pleases.
This is most convenient when making custom column types, because you don't
necessarily know the name of the column until after the class is created.
For example:
@deferred_attr_factory
def make_some_kinda_column(*args):
column = Column(*args)
mapped_class = yield column
print(column.key)
class SomeTable(Base):
foo = make_some_kinda_column(Integer)
# At mapper config time, "foo" will be printed. Magic!
"""
def attribute(*args, **kwargs):
# This happens when the "function" is called in the body of a
# declarative class. This is the factory part.
return declared_attr(DeferredAttribute(f(*args, **kwargs)))
return attribute
@deferred_attr_factory
def SlugColumn(title_column, *args, **kwargs):
column = _make_column(args, kwargs, Unicode, nullable=False)
mapped_class = yield column
def set_slug(target, value, oldvalue, initiator):
setattr(target, title_column.key, to_slug(value))
title_attr = getattr(mapped_class, title_column.key)
listen(title_attr, 'set', set_slug)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment