Python Programming, news on the Voidspace Python Projects and all things techie.
Python on Google Plus
As you may (or perhaps not) have noticed, I've been blogging a lot less in the last year. A new job with Canonical (although I've been there over a year now) and an eight month old daughter all make blogging harder. For Python news I've been posting more on Google+:
I've made some interesting posts on PyPy, Python 3, interesting new libraries and other snippets of news. I particularly try and post about new PEPs and big changes to Python.
Sphinx doctests and the execution namespace
The mock documentation is built with the excellent Sphinx (of course!) and as many as possible of the examples in the documentation are doctests, to ensure that the examples are still up to date for new releases.
doctests mimic executing your examples at the interactive interpreter, but they aren't exactly the same. One big difference is that the execution context for a doctest is not a real module, but a dictionary. This is particularly important for mock examples, because the following code will work at the interactive interpreter but not in a doctest:
>>> from mock import patch >>> class Foo(object): ... pass ... >>> with patch('__main__.Foo') as mock_foo: ... assert Foo is mock_foo ...
The name (__name__) of the doctest execution namespace is __builtin__, but this is a lie. The namespace is a dictionary, internal to the DocTest. Whilst executing doctests under Sphinx, the real __main__ module is the sphinx-build script.
To get the example code above working I either need to rewrite it (and make it less readable - probably by shoving the class object I'm patching out into a module), or I need to somehow make the current execution context into __main__.
Fortunately the Sphinx doctest extension provides the doctest_global_setup config option. This allows me to put a string into my conf.py, which will be executed before the doctests of every page in my documentation (doctests from each page share an execution context).
I solved the problem by creating a proxy object that delegates attribute access to the current globals() dictionary. I shove this into sys.modules as the __main__ module (remembering to store a reference to the real __main__ so that it doesn't get garbage collected). When patch accesses or changes an attribute on __main__ it actually uses the current execution context.
Here's the code from conf.py:
doctest_global_setup = """ import sys import __main__ # keep a reference to __main__ __main = __main__ class ProxyModule(object): def __getattr__(self, name): return globals()[name] def __setattr__(self, name, value): globals()[name] = value def __delattr__(self, name): del globals()[name] sys.modules['__main__'] = ProxyModule() """ doctest_global_cleanup = """ sys.modules['__main__'] = __main """
The corresponding doctest_global_cleanup option restores the real __main__ when the test completes.
In the comments Nick Coghlan suggests a simplification for the ProxyModule:
class ProxyModule(object): def __init__(self): self.__dict__ = globals()
This work is licensed under a Creative Commons Attribution-Share Alike 2.0 License.