A lazy-committing database object with curry?

Jp Calderone exarkun at intarweb.us
Mon Jan 19 20:05:08 CET 2004

On Mon, Jan 19, 2004 at 08:45:45AM -0800, Tim Lesher wrote:
> I'm writing a database helper class that represents records in a SQL
> database as objects.  I want to be able to instantiate the objects
> (from the database), play with their values locally, then lazily
> commit the changes all at once (via an explicit commit call).  Other
> than the call to Commit(), I don't want clients of this class to have
> to think about the fact that there's a database behind it all--there are
> some interesting and non-obvious dependencies on the particular values 
> that are committed together, and I want to hide that in the Commit() call.

  Sounds familiar...

> I'm going to have a number of these, so I wanted to come up with a
> solution that's short on glue code and can easily be aggregated into
> all the classes that represent record types.  This is what I've come
> up with:
> # Simple currying class, no kwargs
> class curry:
>     def __init__(self, fun, *args):
>         self.fun = fun
>         self.pending = args[:]
>     def __call__(self, *args):
>         return self.fun(*(self.pending + args))

  You don't need to copy args, Python has done that for you already.

> # Simple table:  two columns, "title" and "description"
> class SomeRecordType(object):
>     def __init__(self, title, description):
>         self.__modifications = {}
>         # Avoid triggering propset on init
>         self.__dict__['title'] = title
>         self.__dict__['description'] = description

  Are you sure?  How will the initial values be committed to the database,
if you don't catch them in the modifications dict?  I guess you might be
handling initial values somewhere else, in code you didn't post...

>     def __getValue(name, self):
>         try:
>             return self.__modifications[name]
>         except KeyError:
>             return self.__dict__[name]
>     def __setValue(name, self, newValue):
>         self.modifications[name] = newValue
>     name = property(curry(__getValue, 'title'),
>                     curry(__setValue, 'title'))
>     description = property(curry(__getValue, 'description'),
> 			   curry(__setValue, 'description'))

  Nift.  You surely don't use the full capabilities of currying here, but
you are incurring a double function call overhead on every setattr now. 
Here's another way to do it:

      def makeModificationFunctions(name):
          def modget(self, value):
                  return self.__modifications[name]
              except KeyError:
                  return self.__dict__[name]
          def modset(self, value):
              self.__modifications[name] = value
          return modget, modset
      title = property(*makeModificationFunctions('title'))
      description = property(*makeModificationFunctions('description'))

  Of course, you can go one step better and use a metaclass:

      class MetaRecordType(type):
          def __new__(metaclass, name, bases, ns):
              for k, v in ns.items():
                  if v is makeModificationFunctions
                      ns[k] = property(*v(k))
              return type.__new__(metaclass, name, bases, ns)

  and now in the class definition simply say:

      __metaclass__ = MetaRecordType
      title = makeModificationFunctions
      description = makeModificationFunctions

>     def Commit(self):
>         if self.modifications != {}:

   This can be just "if self.__modifications:"

>             # - snip - Do database commit and clear modifications
>             self.__dict__.update(self.modifications)
>             self.modifications = {}
> So I can do this:
> foo = myDb.LookupTitleRecord('some title')
> print foo.description    # 'foo'
> foo.description = 'bar'  # not updated in the DB yet
> print foo.description    # 'bar'
> foo.Commit()             # now updated in the DB
> Are there any pitfalls to doing this?  Am I being dazzled by the shiny
> new toy that is currying?  Is there another simple solution, or a
> refinement of this one, that I'm not seeing?

  One possible problem is failed commits.  If you're doing commits
transactionally, and the transaction fails, your in memory objects will
retain their now-inconsistent values while the database remains unupdated. 
If you're not using transactions.. well that's a whole other problem :)

  You may want to look at xsdb and atop.  Neither uses SQL, but both have
developed pretty heavily on some of the ideas you're tinkering around with.


More information about the Python-list mailing list