Testoob's --capture
feature (written by Misha) replaces sys.stdout
and sys.stderr
for each test being run, and displays the output only if the test fails.
Leeor Aharon, of ff-activex-host fame, wanted to use it with Python's logging module, but the StreamHandler
class stores a reference to the output stream on initialization - it already has a reference to sys.stdout, so replacing it won't affect it.
We originally thought of sublcassing StreamHandler
and making retrieve the logger from a property, but we came up with this elegant code instead:
class LazyEvaluator(object): def __init__(self, factory): self.__factory = factory def __getattr__(self, name): return getattr(self.__factory(), name) lazy_stdout = LazyEvaluator(lambda:sys.stdout) handler = StreamHandler(strm=lazy_stdout)
The "lazy evaluator" is initialized with a factory callable, and every time it tries to access an attribute or method the object will be re-created by the factory. No changes necessary for StreamHandler
, and ./alltests.py --capture
works like a charm.
As usual, adding one more level of indirection to solve the world's problems :)
ReplyDeleteAn interesting solution! Nice..
ReplyDeleteNice, but has subtle corner cases.
ReplyDeleteExample: you have no __setattr__, so lazy_stdout.softspace will be written on lazy_stdout itself, which will work (because henceforth __dict__['softspace'] will override __getattr__) but will be distinct from stdout.softspace.
So if StreamHandler prints will be intermixed with other printing, softspace will develop a split personality syndrome...
OK, this might be lowest-impact bug I've ever seen!
But what I;m getting at is that you want a proxy, and implementing a full proxy in Python is non-trivial, so consider taking a working one, like http://code.activestate.com/recipes/496741/.