Monday, October 26, 2015

Return a new return_value for each call when mocking tempfile in python

Here is a python snippet that mocks out tempfile.NamedTemporaryFile() to use a StringIO and add it to an array of file handles.

The somewhat unintuitive bit here is that by passing side_effect a function mock will use the result of the function as the return_value for the mocked tempfile.NamedTemporaryFile(). Or as the docs say: "The function is called with the same arguments as the mock, and unless it returns DEFAULT, the return value of this function is used as the return value."

def _GetTempFile(self, *args, **kwargs):
  tempfile = StringIO.StringIO() 
  tempfile.flush = mock.MagicMock()
  self.output_streams.append(tempfile)
  return tempfile

with mock.patch.object(tempfile, "NamedTemporaryFile", side_effect=self._GetTempFile):
  # mocked code...

Debugging pyinstaller binary install missing import "ImportError: No module named google.protobuf"

We were missing an import in our pyinstaller, the error was:
ImportError: No module named google.protobuf
There is a simple fix for this, you just need to add an __init__.py, but after doing that it seemed pyinstaller still wasn't picking up the library. So I wanted to check what it was actually importing. Here's how you can do that. First unzip the self-extracting installer:
unzip myinstaller.exe
Then inspect it with pyi-archive_viewer (which you should have if you pip installed pyinstaller):
$ pyi-archive_viewer myprogram.exe
 pos, length, uncompressed, iscompressed, type, name
[(0, 5392645, 5392645, 0, 'z', 'out00-PYZ.pyz'),
 (5392645, 174, 239, 1, 'm', 'struct'),
 (5392819, 1161, 2711, 1, 'm', 'pyi_os_path'),
 (5393980, 4803, 13033, 1, 'm', 'pyi_archive'),
 (5398783, 3976, 13864, 1, 'm', 'pyi_importers'),
 (5402759, 1682, 3769, 1, 's', '_pyi_bootstrap'),
 (5404441, 4173, 13142, 1, 's', 'pyi_carchive'),
 (5408614, 316, 646, 1, 's', 'pyi_rth_Tkinter'),
 (5408930, 433, 918, 1, 's', 'pyi_rth_pkgres'),
 (5409363, 981, 2129, 1, 's', 'pyi_rth_win32comgenpy'),
 (5410344, 941, 2291, 1, 's', 'client')]
? O out00-PYZ.pyz
{'BaseHTTPServer': (False, 1702685L, 8486),
 'ConfigParser': (False, 1070260L, 9034),
 'Cookie': (False, 2449334L, 9023),
 'Crypto': (True, 1151586L, 621),
 'Crypto.Cipher': (True, 4004931L, 1053),
 'Crypto.Cipher.AES': (False, 2553735L, 1656),
 'Crypto.Cipher.ARC4': (False, 5042987L, 1762),

[snip]

 'google': (True, 3743324L, 102),
 'google.protobuf': (True, 328350L, 111),
 'google.protobuf.descriptor': (False, 2112676L, 9136),
 'google.protobuf.descriptor_database': (False, 344343L, 1713),
 'google.protobuf.descriptor_pb2': (False, 4695199L, 6549),
 'google.protobuf.descriptor_pool': (False, 2080592L, 6890),
 'google.protobuf.internal': (True, 1403354L, 120),
 'google.protobuf.internal.api_implementation': (False, 5300293L, 697),
 'google.protobuf.internal.containers': (False, 4954671L, 3244),
 'google.protobuf.internal.cpp_message': (False, 1447735L, 8287),
 'google.protobuf.internal.decoder': (False, 1318001L, 7942),
 'google.protobuf.internal.encoder': (False, 5337352L, 7379),
 'google.protobuf.internal.enum_type_wrapper': (False, 5202459L, 1039),
 'google.protobuf.internal.message_listener': (False, 2642102L, 1112),
 'google.protobuf.internal.python_message': (False, 4574735L, 13240),
 'google.protobuf.internal.type_checkers': (False, 4723000L, 3257),
 'google.protobuf.internal.wire_format': (False, 4040106L, 2834),
 'google.protobuf.message': (False, 370977L, 3410),
 'google.protobuf.pyext': (True, 650580L, 116),
 'google.protobuf.pyext.cpp_message': (False, 349637L, 763),
 'google.protobuf.reflection': (False, 2578008L, 2557),
 'google.protobuf.symbol_database': (False, 3971343L, 2075),
 'google.protobuf.text_encoding': (False, 2736366L, 1447),
 'google.protobuf.text_format': (False, 4991343L, 8425),

This shows that google.protobuf was successfully included by pyinstaller. In my case pyinstaller was doing the right thing, I had another step in the installer generation process that was using an older version of the installer that was still missing the proto library.