[Shane Hathaway]
Here is what often happens in Zope:
def setFoo(self, value): try: self.foo = value except: LOG("Oops", ERROR, "Some error happened", error=sys.exc_info()) self.change_count += 1 self.get_indexes().update(self)
Some piece of code has a legitimate reason to catch but log all exceptions. Some other piece of code updates indexes. The database has now committed a partial transaction.
I need more words -- I don't see a commit() in that code, and I don't know what "partial transaction" might mean (in ZODB you can commit a transaction, or abort it -- there's no facility I know of for a partial commit (or a partial abort, for that matter)). Is a commit() hiding inside the update() call? If so, and we're assuming the bare "except" swallowed a ReadConflictError, then I have to repeat:
The logic in Connection.commit() raises ReadConflictError upon an attempt to commit an object that previously suffered a conflict during the transaction. How does that differ from what you want?
Even worse, this can happen within the indexes, making the indexes inconsistent with themselves.
Sorry, since I didn't understand the first part, I'm way lost here.
Once a conflict error has occurred on any object, the rest of the transaction is on shaky grounds.
Which is why the current ZODB releases intend to prevent committing a transaction if a conflict error occurred during the transaction. It shouldn't matter to this ZODB machinery if the application suppressed the original conflict error(s), ZODB remembers that it raised ReadConflictError regardless (via the Connection._conflicts set-implemented-as-a-dict). Hmm. The lack of design documentation is frustrating. Staring at the code, it could be that ZODB only prevents a transaction commit if the transaction tries to *commit* at least one object that suffered a conflict error, I don't know ... if you have a specific case in mind, please open a Collector report about it.
That's unfortunate. Now I'm inclined to never use hasattr again. Can we petition for "hasattr2" then? :-)
Dieter already wrote it <wink>. Zope/ZODB's use of Python's machinery is sometimes so far from the goals Guido had in mind that Zope is alone in wanting a Python change. For example, I don't know of other apps where calling hasattr() can have disastrous consequences. If so, there's slim constituency for adding another variant of hasattr to the core language. "Does a thing have attribute so-and-so?" is such a mind-numbingly complex question in Zope that I would indeed never use hasattr() in Zope/ZODB code to query general objects. Parts of ZODB 3.3 have been rewritten to use this instead when querying general objects: _marker = object() # The point of this is to avoid hiding exceptions (which the builtin # hasattr() does). def myhasattr(obj, attr): return getattr(obj, attr, _marker) is not _marker That's certainly not needed for "ordinary" uses, like if hasattr(errno, "WSAEWOULDBLOCK"): # Windows Uses of hasattr() on base Python objects and modules is safe (as I believe it is in *almost* all applications outside our part of the Python world).