Re: A lazy-committing database object with curry?

From: Jp Calderone (exarkun_at_intarweb.us)
Date: 01/19/04


Date: Mon, 19 Jan 2004 14:05:08 -0500
To: python-list@python.org

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):
              try:
                  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.

  Jp



Relevant Pages

  • Re: Driver AutoCommit issue
    ... I am using the container managed transaction and I expect the container to handle that for me. ... Is there different driver class that i have to use in the connection pool configuration. ... Why I am saying the DML always gets committed is when I step thought the code I can see the updated data in the database immediately after the callable statement is executed Even before the EJB method that invoked the call is completed. ... I set the auto commit to false on connection as soon as I get the connection from the datasoruce. ...
    (microsoft.public.sqlserver.jdbcdriver)
  • Re: Support for XA
    ... but not directly at the database level. ... UR's object cache / Context acts as a software transaction ... After all the databases' SQL statements have ... it tells each DB transaction to commit. ...
    (perl.dbi.users)
  • Re: commit
    ... I am using SQLite, and I want to select the maxfrom a certain ... gives an error telling that the database is locked if in that moment ... allowing an update until the "transaction" finishes. ... Only then does process A commit its transaction and the rowA becomes ...
    (perl.beginners)
  • Re: Behavior of Connection.commit()
    ... By default the Sql Server database is in the equivalent of autoCommitstate, anything you do will permanently update the database. ... When we set autoCommit to off the expectation is that no SQL statements will be committed until you have explicitly told it to commit by invoking the commitmethod. ... transaction, start a new transaction to fake the fact that we are still in autoCommitstate. ...
    (microsoft.public.sqlserver.jdbcdriver)
  • Re: Simple transaction question ????
    ... in SQL data base through store procedure or function but then sounds more ... 1- I am not used to the SQL syntax in SQL server side, ... 2- Before storing those data in other database table, ... if you have to manage transaction that spawns a single database then ...
    (microsoft.public.dotnet.framework.adonet)