Python Programming, news on the Voidspace Python Projects and all things techie.

Callable object with state using generators

emoticon:beaker It's often convenient to create callable objects that maintain some kind of state. In Python we can do this with objects that implement the __call__ method and store the state as instance attributes. Here's the canonical example with a counter:

>>> class Counter(object):
...     def __init__(self, start):
...         self.value = start
...     def __call__(self):
...         value = self.value
...         self.value += 1
...         return value
...
>>> counter = Counter(0)
>>> counter()
0
>>> counter()
1
>>> counter()
2

Generators can be seen as objects that implement the iteration protocol, but maintain state within the generator function instead of as instance attributes. This makes them much simpler to write, and much easier to read, than manually implementing the iteration protocol.

This example iterator never terminates, so we obtain values by manually calling next:

>>> class Iter(object):
...     def __init__(self, start):
...         self.value = start
...     def __iter__(self):
...         return self
...     def next(self):
...         value = self.value
...         self.value += 1
...         return value
...
>>> counter = Iter(0)
>>> next(counter)
0
>>> next(counter)
1
>>> next(counter)
2

The same iterator implemented as a generator is much simpler and the state is stored as local variables in the generator:

>>> def generator(start):
...     value = start
...     while True:
...         yield value
...         value += 1
...
>>> gen = generator(0)
>>> next(gen)
0
>>> next(gen)
1
>>> next(gen)
2

In recent versions of Python generators were enhanced with a send method to enable them to act like coroutines.

>>> def echo():
...     result = None
...     while True:
...         result = (yield result)
...
>>> f = echo()
>>> next(f)  # initialise generator
>>> f.send('fish')
'fish'
>>> f.send('eggs')
'eggs'
>>> f.send('ham')
'ham'

(Note that we can't send to an unstarted generator - hence the first call to next to initialise the generator.)

We can use the send method as a way of providing a callable object with state. I first saw this trick in this recipe for a highly optimized lru cache by Raymond Hettinger. The callable object is send method itself, and as with any generator the state is stored as local variables.

Here's our counter as a generator:

>>> def counter(start):
...     yield None
...     value = start
...     while True:
...         ignored = yield value
...         value += 1
...
>>> gen = counter(0)
>>> next(gen)
>>> f = gen.send
>>> f(None)
0
>>> f(None)
1
>>> f(None)
2
>>> f(None)
3

Some observations. Firstly send takes one argument and one argument only. In this example we're ignoring the value sent into the generator, but send must be called with one argument and can't be called with more. So it's mostly useful for callable objects that take a single argument...

Secondly, this performs very well. Function calls are expensive (relatively) in Python because each invocation creates a new frame object (or reuses a zombie frame from the pool - but I digress) for storing the local variables etc. Generators are implemented with a "trick" that keeps the frame object alive, so that the next step of the generator can simply continue execution after the last yield. So our callable object implemented as a generator doesn't have the overhead of a normal function call...

The main advantage this approach has is that it's more readable than the version with __call__. To make it more pleasant to use, we can wrap creating our counter in a convenience function:

>>> def get_counter(start):
...     c = counter(start)
...     next(c)
...     return c.send
...
>>> c = get_counter(0)
>>> c(None)
0
>>> c(None)
1
>>> c(None)
2

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2012-01-22 15:05:46 | |

Categories: , Tags: ,


Simple mocking of open as a context manager

emoticon:speaker Using open as a context manager is a great way to ensure your file handles are closed properly and is becoming common:

with open('/some/path', 'w') as f:
    f.write('something')

The issue is that even if you mock out the call to open it is the returned object that is used as a context manager (and has __enter__ and __exit__ called).

Using MagicMock from the mock library, we can mock out context managers very simply. However, mocking open is fiddly enough that a helper function is useful. Here mock_open creates and configures a MagicMock that behaves as a file context manager.

from mock import inPy3k, MagicMock

if inPy3k:
    file_spec = ['_CHUNK_SIZE', '__enter__', '__eq__', '__exit__',
        '__format__', '__ge__', '__gt__', '__hash__', '__iter__', '__le__',
        '__lt__', '__ne__', '__next__', '__repr__', '__str__',
        '_checkClosed', '_checkReadable', '_checkSeekable',
        '_checkWritable', 'buffer', 'close', 'closed', 'detach',
        'encoding', 'errors', 'fileno', 'flush', 'isatty',
        'line_buffering', 'mode', 'name',
        'newlines', 'peek', 'raw', 'read', 'read1', 'readable',
        'readinto', 'readline', 'readlines', 'seek', 'seekable', 'tell',
        'truncate', 'writable', 'write', 'writelines']
else:
    file_spec = file

def mock_open(mock=None, data=None):
    if mock is None:
        mock = MagicMock(spec=file_spec)

    handle = MagicMock(spec=file_spec)
    handle.write.return_value = None
    if data is None:
        handle.__enter__.return_value = handle
    else:
        handle.__enter__.return_value = data
    mock.return_value = handle
    return mock
>>> m = mock_open()
>>> with patch('__main__.open', m, create=True):
...     with open('foo', 'w') as h:
...         h.write('some stuff')
...
>>> m.assert_called_once_with('foo', 'w')
>>> m.mock_calls
[call('foo', 'w'),
 call().__enter__(),
 call().write('some stuff'),
 call().__exit__(None, None, None)]
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')

And for reading files, using a StringIO to represent the file handle:

>>> from StringIO import StringIO
>>> m = mock_open(data=StringIO('foo bar baz'))
>>> with patch('__main__.open', m, create=True):
...     with open('foo') as h:
...         result = h.read()
...
>>> m.assert_called_once_with('foo')
>>> assert result == 'foo bar baz'

Note that the StringIO will only be used for the data if open is used as a context manager. If you just configure and use mocks they will work whichever way open is used.

This helper function will be built into mock 0.9.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2012-01-13 12:18:35 | |

Categories: , , Tags: , ,


Mocks with some attributes not present

emoticon:dove Mock objects, from the mock library, create attributes on demand. This allows them to pretend to be objects of any type.

What mock isn't so good at is pretending not to have attributes. You may want a mock object to return False to a hasattr call, or raise an AttributeError when an attribute is fetched. You can do this by providing an object as a spec for a mock, but that isn't always convenient.

Below is a subclass of Mock that allows you to "block" attributes by deleting them. Once deleted, accessing an attribute will raise an AttributeError.

from mock import Mock

deleted = object()
missing = object()

class DeletingMock(Mock):
    def __delattr__(self, attr):
        if attr in self.__dict__:
            return super(DeletingMock, self).__delattr__(attr)
        obj = self._mock_children.get(attr, missing)
        if obj is deleted:
            raise AttributeError(attr)
        if obj is not missing:
            del self._mock_children[attr]
        self._mock_children[attr] = deleted

    def __getattr__(self, attr):
        result = super(DeletingMock, self).__getattr__(attr)
        if result is deleted:
            raise AttributeError(attr)
        return result
>>> mock = DeletingMock()
>>> hasattr(mock, 'm')
True
>>> del mock.m
>>> hasattr(mock, 'm')
False
>>> del mock.f
>>> mock.f
Traceback (most recent call last):
  ...
AttributeError: f

This functionality will probably be built into 0.9, or whichever version of mock comes after 0.8...

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2012-01-12 12:33:15 | |

Categories: , , Tags: , ,


mock 0.8rc2: new release and development docs

emoticon:car I've pushed out a new release of mock. This fixes an inconsistency in the create_autospec api I discovered whilst working on the docs (yes I've really been working on the docs), and a fix for a bug with using ANY. (Thanks to Tom Davis for reporting this.)

You can download mock 0.8rc2 or install it with:

pip install -U mock==dev

mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects. The latest stable release is 0.7.2, which you can download from pypi.

There are two changes in 0.8rc2:

  • Removed the configure keyword argument to create_autospec and allow arbitrary keyword arguments (for the Mock constructor) instead
  • Fixed ANY equality with some types in assert_called_with calls

Unfortunately, if you were using the configure argument to create_autospec then you'll have to change code when you update to 0.8rc2, sorry. The good news is that there's now some documentation for the new features - including for create_autospec.

I've now switched mock to using one of the standard Sphinx themes, and the development documentation (updated automatically on every commit) is available at mock.readthedocs.org.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2012-01-11 02:13:32 | |

Categories: , Tags: , ,


Python on Google Plus

emoticon:movies 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.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2012-01-03 11:41:02 | |

Categories: , ,


Sphinx doctests and the execution namespace

emoticon:car I've finally started work on the documentation for mock 0.8 release, and much of it involves converting the write-ups I did in the blog entries.

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.

Note

In the comments Nick Coghlan suggests a simplification for the ProxyModule:

class ProxyModule(object):
    def __init__(self):
        self.__dict__ = globals()

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2012-01-01 00:28:03 | |

Categories: , Tags: , ,


mock 0.8 release candidate 1 and handling mutable arguments

emoticon:ghostradio I've released mock 0.8 release candidate 1. You can download it it or install it with:

pip install -U mock==dev

mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects. The latest stable release is 0.7.2, which you can download from pypi.

The main improvement in 0.8 rc1 is performance improvements, particularly in constructing MagiMock. Instantiating MagicMock is substantially faster than it was in 0.7 already, however patch creates a MagicMock by default now (in 0.7 patch creates a Mock by default). Instantiating MagicMock in 0.8 is still slower than instantiating a Mock in 0.7, but the difference is much less than it used to be.

There are also a couple of bugfixes. The full changelog for 0.8 rc1 is:

  • create_autospec on the return value of a mock class will use __call__ for the signature rather than __init__
  • Performance improvement instantiating Mock and MagicMock
  • Mocks used as magic methods have the same type as their parent instead of being hardcoded to MagicMock

The additional changes in 0.8 are explained in these blog entries:

Special thanks to Julian Berman for his help with diagnosing and improving performance in this release. I have started work on the doc changes and I'm hoping that the 0.8 final release won't be too far off.

Mock, and its now multifarious variants, are designed to "remember" arguments they are called with for making assertions about how they have been used. There are two ways it could do this: it could either keep references to the arguments or it could copy them.

Mock chooses to keep a reference to the arguments rather than copying them, mainly because this was easier to implement and met all my use cases at the time. As a consequence, if a mock is called with mutable arguments (like a set or a dictionary) that are changed (mutated) in between the call and the assertion then you have to make the assertion against the changed value rather than the value as it was at the call time. Here's an example of what I mean:

>>> from mock import MagicMock
>>> arg = set()
>>> m = MagicMock(return_value=None)
>>> m(arg)
>>> arg.add(1)
>>> m.assert_called_with(set())
Traceback (most recent call last):
  ...
AssertionError: Expected call: mock(set([]))
Actual call: mock(set([1]))

Even though m was called with an empty set, an assertion with an empty set fails.

The alternative would be to copy all arguments for every call (using copy.deepcopy). Unfortunately deepcopy is slow and fragile, and although it would solve this problem with mutable arguments it would break assertions that rely on object identity.

The mock documentation has an example showing two ways to solve this problem. There's a third way which is possibly more elegant, a subclass of MagicMock that deep-copies all arguments:

>>> from copy import deepcopy
>>> class CopyingMock(MagicMock):
...     def __call__(self, *args, **kwargs):
...         args = deepcopy(args)
...         kwargs = deepcopy(kwargs)
...         return super(CopyingMock, self).__call__(*args, **kwargs)
...
>>> c = CopyingMock(return_value=None)
>>> arg = set()
>>> c(arg)
>>> arg.add(1)
>>> c.assert_called_with(set())
>>> c.assert_called_with(arg)
Traceback (most recent call last):
  ...
AssertionError: Expected call: mock(set([1]))
Actual call: mock(set([]))
>>> c.foo
<CopyingMock name='mock.foo' id='...'>

When you subclass Mock or MagicMock all dynamically created attributes, and the return_value will use your subclass automatically. That means all children of a CopyingMock will also have the type CopyingMock.

This goes nicely with the new-in-0.8 new_callable argument to patch that allows you to specify the mock class to use:

with patch('some.object', new_callable=CopyMock) as mock_thing:
        # do things...

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2011-12-29 13:04:47 | |

Categories: , Tags: , , , , ,


mock 0.8 beta 4 released: bugfix and minor features

emoticon:knot I've released mock 0.8 beta 4. You can download it it or install it with:

pip install -U mock==dev

mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects. The latest stable release is 0.7.2, which you can download from pypi.

This is intended to be the last beta before final release. The only outstanding work before final release is extensive documentation reworking for the new features. Changes in beta 4 are:

  • patch lookup is done at use time not at decoration time
  • When attaching a Mock to another Mock as a magic method, calls are recorded in mock_calls
  • Addition of attach_mock method
  • Renamed the internal classes Sentinel and SentinelObject to prevent abuse
  • BUGFIX: various issues around circular references with mocks (setting a mock return value to be itself etc)

The additional changes in 0.8 are explained in these blog entries:

The most important change is a bugfix. Due to a new feature in 0.8, mocks tracking calls to their return values in mock_calls, setting a mock as its own return value no longer worked. This and some other issues with circular references between mocks are now fixed.

There are a couple of other minor features.

The patch decorator, when you specify the object to be patched as a string, does the import to find the target when the patch is activated (the decorated method is called) instead of when the patch is created. This allows you to use patch with modules that don't exist when the test is imported but will exist when the test is executed (for example they are imported in a setUp or created dynamically by the system under test). It also means that if you specify the module incorrectly (a typo) you get a test failure rather than the whole test run being aborted.

There is a new method called attach_mock, this allows you to attach a mock to another mock so that calls to it will be tracked in method_calls and mock_calls. New in 0.8 you can do this anyway with freshly created mocks just by setting a mock as an attribute on another one. This doesn't work (calls aren't tracked) if the mock has a name. This limitation is to allow you to bypass the new feature, and to prevent it happening just because your test code sets a mock as an attribute on another mock (they may represent un-related objects).

Unfortunately, when patch creates a mock it gives it a name. This prevents you tracking calls to mocks created by patch by setting them as attributes on a "manager mock" (but is useful for debugging when you want to know where your mock comes from). You may want to do this where you want to track the order of calls to mocks created by patch. Here's an example of using the new attach_mock method with patch to assert not just that the expected calls are made, but that they are made in the expected order:

>>> import sys
>>> from mock import MagicMock, call, patch
>>> sys.modules['sqeee'] = MagicMock()
>>> with patch('sqeee.first') as first:
...   with patch('sqeee.second') as second:
...     manager = MagicMock(name='manager')
...     manager.attach_mock(first, 'first')
...     manager.attach_mock(second, 'second')
...     first(1)
...     second(2)
...
<MagicMock name='manager.first()' id='4300465424'>
<MagicMock name='manager.second()' id='4300494800'>
>>> assert manager.mock_calls == [call.first(1), call.second(2)]

Calls to the mocks created by the patches are tracked by the manager, so manager.mock_calls can be interrogated for asserts about how the mocks are used.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2011-10-10 01:05:00 | |

Categories: , Tags: , , ,


matplotlib and numpy for Python 2.7 on Mac OS X Lion

emoticon:tooth Unfortunately, due to an API change, the latest released version of matplotlib is incompatible with libpng 1.5. Take a wild guess as to which version comes with Mac OS X Lion. :-/

Fortunately this is fixed in the matplotlib repository. Here's how I got matplotlib working on Mac OS X Lion (with Python 2.7 - but these instructions should work fine for other versions of Python too).

First matplotlib requires numpy. The latest version is 1.6.1, from here. The precompiled Mac OS X binaries are compiled to be compatible with Mac OS X 1.3 and up, which means they are 32 bit only. By default Python will run as 64 bit on OS X Lion, which means you'll see this when attempting to import numpy:

>>> import numpy
Traceback (most recent call last):
 ...
ImportError: dlopen(/.../site-packages/numpy/core/multiarray.so, 2): no suitable image found.  Did find:
        /.../site-packages/numpy/core/multiarray.so: no matching architecture in universal wrapper

You can get round this by launching python as a 32 bit process. I have the following alias in my .bash_profile:

alias py32="arch -i386 python"

The next problem is the matplotlib one. This blog entry shows how to build matplotlib from the git repo, using homebrew. I don't want to use a homebrew installed Python, so I modified the instructions to only install the dependencies with homebrew. I also set the correct flags to compile a 32bit version of matplotlib to match the 32bit numpy.

brew install pkg-config
brew install gfortran
cd matplotlib
export ARCHFLAGS="-arch i386"
py32 setup.py build
py32 setup.py install

And it appears to work. So far anyway:

>>> import pylab
>>> x = pylab.randn(10000)
>>> pylab.hist(x, 100)
>>> pylab.show()

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2011-09-05 00:18:13 | |

Categories: , , , Tags: , ,


mock 0.8 beta 3 released: feature complete

emoticon:eggs I've released mock 0.8 beta 3. You can download it it or install it with:

pip install -U mock==dev

mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects. The latest stable release is 0.7.2, which you can download from pypi.

mock 0.8 beta 3 is now feature complete, and is hopefully the code that will be released as the final version. All that is left is documentation work, so now is the ideal time to try the beta!

The changes in beta 3 are:

  • Improved repr for Mock.call_args and entries in Mock.call_args_list, Mock.method_calls and Mock.mock_calls
  • Improved repr for mocks
  • Mocks attached as attributes or return values to other mocks have calls recorded in method_calls and mock_calls of the parent (unless a name is already set on the child)
  • Addition of mock_add_spec method for adding (or changing) a spec on an existing mock
  • BUGFIX: minor fixes in the way mock_calls is worked out, especially for "intermediate" mocks in a call chain

The additional changes in 0.8 are explained in these blog entries:

The first two changes in 0.8 beta 3 are very unassuming, improved reprs. They're possibly one of the most useful changes in 0.8, particularly for introspecting a mock when it hasn't been used how you expected.

Firstly, mocks have a repr that tells you where they came from (even if they are the return value of another mock):

>>> m = MagicMock()
>>> m().foo().bar.baz()
<MagicMock name='mock().foo().bar.baz()' id='4300950800'>

Secondly, the call_args, call_args_list, method_calls and mock_calls all have much more friendly reprs:

>>> m = MagicMock(return_value=None)
>>> m(1, 2, 3)
>>> m(a='fish')
>>> m.call_args
call(a='fish')
>>> m.call_args_list
[call(1, 2, 3), call(a='fish')]

>>> m = MagicMock()
>>> m.foo(), m.bar(1), m().baz(a='foo')
(<MagicMock name='mock.foo()' ...)
>>> m.mock_calls
[call.foo(), call.bar(1), call(), call().baz(a='foo')]

These reprs are coincidentally the same way you construct lists using the new call object for comparing with method_calls, call_args_list or mock_calls:

>>> expected_calls = [call.foo(), call.bar(1), call(), call().baz(a='foo')]
>>> m.mock_calls == expected_calls
True

The other two features in 0.8 beta 3 make it slightly easier to configure mocks. mock_add_spec allows you to add (or change) a spec on a mock. It even works with magic methods:

>>> class Foo(object):
...     one = 1
...
>>> p = patch('__main__.Foo')
>>> m = p.start()
>>> m.one.mock_add_spec(int)
>>> int(m.one)
1
>>> list(m.one)
Traceback (most recent call last):
  ...
TypeError: 'MagicMock' object is not iterable
>>> m.one.mock_add_spec(list)
>>> list(m.one)
[]
>>> int(m.one)
Traceback (most recent call last):
  ...
TypeError: int() argument must be a string or a number, not 'MagicMock'

A more powerful way of creating mocks with specced attributes is autospec, but mock_add_spec can still be useful.

An alternative way of configuring a mock is to manually create mocks and set them as attributes. If you do this calls to these mocks will now be recorded in method_calls and mock_calls of the parent:

>>> m = MagicMock()
>>> m.one = MagicMock(spec=int)
>>> int(m.one)
1
>>> m.mock_calls
[call.one.__int__()]

(It also works for setting mocks as return values. Calls to the 'child mock' will be recorded in the mock_calls of the parent.)

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2011-08-17 23:35:36 | |

Categories: , Tags: , ,


mock 0.8 beta 2:bug fix and side_effect iterables

emoticon:pill I've released mock 0.8 beta 2. You can download it it or install it with:

pip install -U mock==dev

mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects. The latest stable release is 0.7.2, which you can download from pypi.

There are only a few changes from the beta 1 release, the most important of these is a bugfix. 0.8 beta 1 turned call_args, and entries in call_args_list, into tuples with three members instead of two members as previously. This has now been fixed. In addition yhere is one api change and a couple of features added since the beta 1 release:

  • Setting side_effect to an iterable will cause calls to the mock to return the next value from the iterable
  • Added assert_any_call method
  • Moved assert_has_calls from call lists onto mocks
  • BUGFIX: call_args and all members of call_args_list are two tuples of (args, kwargs) again instead of three tuples of (name, args, kwargs)

For the full list of new features in the 0.8 release, read these blog entries:

The first of the new features in beta 2 simplifies creating mocks that return a sequence of values for a series of calls.

>>> from mock import Mock
>>> m = Mock(side_effect=[1, 2, 3])
>>> m(), m(), m()
(1, 2, 3)

There are also two new assert methods on mocks. The first of these, assert_any_call, checks that the mock has been called with particular arguments. Unlike assert_called_with it doesn't just check the most recent call, but will pass if any of the calls (in call_args_list) match the call you are checking:

>>> m = MagicMock(return_value=None)
>>> m(5, 6, 7, 8)
>>> m(1, 2, 3, 4)
>>> m.assert_any_call(5, 6, 7, 8)
>>> m.assert_any_call(a=3)
Traceback (most recent call last):
 ...
AssertionError: mock(a=3) call not found

The second new method is assert_has_calls. This method allows you to check multiple calls, and as it uses mock_calls you can make asserts for calls on child mocks. You provide a list of calls and assert_has_calls checks that those calls appear sequentially in the mock_calls_list but it doesn't fail if there are extra calls before or after the list of calls. If you switch on any_order then assert_has_calls just checks that all of the calls you provide in the list are present, but without worrying about order at all:

>>> m = MagicMock()
>>> m.foo()
>>> m.bar.baz(1, 2, 3)
>>> m.bing.bong()
>>> m.assert_has_calls([call.foo(), call.bar.baz(1, 2, 3)])
>>> kalls = [call.bing.bong(), call.foo()]
>>> m.assert_has_calls(kalls, any_order=True)

There are now only two new features being considered for 0.8 final (both minor and to do with the repr of callargs objects). They are issues 108 & 113.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2011-08-04 10:48:24 | |

Categories: , Tags: , , ,


Hosted by Webfaction

Counter...