views:

93

answers:

2

Is there an easy way to "de-instrument" an instantiated class coming from sqlalchemy's ORM, i.e., turn it into a regular object?

I.e., suppose I have a Worker class that's mapped to a worker table:

class Worker(object):
      def earnings(self):
          return self.wage*self.hours 

mapper(Worker,workers)

where workers is a reflected table containing lots of observations. The reason I want to do this is that methods like worker.earnings() are very slow, on account of all the sqlalchemy overhead (which I don't need for my application). E.g., accessing self.wage is about 10 times slower than it would be if self.wage was a property of a regular class.

A: 

If you know the names of the fields you want, say you have them in a list of strings called fields, and those of the methods you want, like earnings in your example, in a list of strings called methods, then:

def deinstrument(obj, fields, methods):
  cls = type(obj)
  class newcls(object): pass
  newobj = newcls()
  for f in fields:
    setattr(newobj, f, getattr(obj, f))
  for m in methods:
    setattr(newcls, m, getattr(cls, m).im_func)
  return newobj

You'll probably want __name__ among the fields strings, so that the new object's class has the same name as the one you're "de-instrumenting".

Alex Martelli
+2  A: 

If you need to permanently deinstrument a class, just dispose of the mapper:

sqlalchemy.orm.class_mapper(Worker).dispose()

SQLAlchemy instrumentation lives as property descriptors on the class object. So if you need separate deinstrumented versions of objects you'll need to create a version of the class that doesn't have the descriptors in it's type hierarchy.

A good way would be to have a persistent subclass for each model class and create the mappers to the persistent classes. Here's a class decorator that creates the subclass for you and adds it as a class attribute on the original:

def deinstrumentable(cls):
    """Create a deinstrumentable subclass of the class."""
    def deinstrument(self):
        """Create a non-instrumented copy of the object."""
        obj = cls.__new__(cls)
        obj.__dict__.update(self.__dict__)
        del obj._sa_instance_state
        return obj

    persistent = type('Persisted%s' % cls.__name__, (cls,), {
        'Base': cls,
        'deinstrument': deinstrument
    })

    return persistent

You would use it in the definition like this:

@deinstrumentable
class Worker(object):
    def earnings(self):
        return self.wage*self.hours

mapper(Worker, workers)

And when you have a persistent object, you can create a deinstrumented version of it like this:

worker = session.query(Worker).first()
detached_worker = worker.deinstrument()

You can create a deinstrumented version directly like this:

detached_worker = Worker.Base()
Ants Aasma
wow that rocks. heh. I was gonna say obj.__class__ = Foo.
zzzeek