Python web service call logger

I wanted to log every access to my web services (using soaplib): Get the client IP, what method is it calling, what parameters, and what class. Rather than write the code to log the access in every method I've written a decorator (logWebServiceCall) so you only need to add it on the top of your web method, like this:

  2. class MyService(SimpleWSGISoapApp )
  3. @logWebServiceCall
  4. @soapmethod(String, String, _returns=Array(String))
  5. def myMethod(self, param1, param2):
  6. ...code here...

Decorator code:

  2. from soaplib.wsgi_soap import request
  4. def logWebServiceCall(func):
  5. def wrapped(*args, **kwargs):
  6. if hasattr(request, "environ"):
  7. if not kwargs.has_key('_soap_descriptor'):
  8. message = request.environ['SCRIPT_NAME']
  9. message += " - " + request.environ['HTTP_SOAPACTION']
  10. message += " - " + request.environ['REMOTE_ADDR']
  11. message += " - " + str(list(args)[1:])
  12. print message
  13. result = func(*args, **kwargs)
  14. return result
  15. wrapped.__doc__ = func.__doc__
  16. wrapped.func_name = func.func_name
  17. wrapped._is_soap_method = True
  18. return wrapped

Just finished a decorator for validation. Same usage than the previous one.

  2. def validateUserFirst(func):
  3. def wrapped(*args, **kwargs):
  4. if hasattr(request, "environ"):
  5. if not kwargs.has_key('_soap_descriptor'):
  6. argumentsList = list(args)[1:]
  7. if len(argumentsList) < 2:
  8. raise Exception('This method requires username and password at least')
  10. username = argumentsList[0]
  11. password = argumentsList[1]
  13. if not validate(username, password): # define the validation method as you want
  14. raise Exception('Invalid username or password')
  16. result = func(*args, **kwargs)
  17. return result
  18. wrapped.__doc__ = func.__doc__
  19. wrapped.func_name = func.func_name
  20. wrapped._is_soap_method = True
  21. return wrapped

Mocking access to files in the filesystem

A common test to check a function that operates on the filesystem is to create dummy files in the filesystem. However that is dirty because you have to make sure you've got the permissions in the filesystem and you delete the dummy files after the tests and so.
Mocker lets you fake system calls so you can write the same test without creating files.
In the next sample I am testing a function that lists all files in a given folder, avoiding symbolic links.
I first fake the contents of the directory and then I say that one of them is a symb link. The outcome is nice:

  2. import unittest
  3. import os
  4. import re
  5. from postclonado import Utils
  6. import mocker
  8. class UtilsTests(mocker.MockerTestCase):
  10. def testDiscardSymbolicLinksInFilesSearch(self):
  11. stubFile = 'filetmp'
  12. stubSymbolicLink = 'linktmp'
  13. basedir = '/tmp'
  15. mockListDir = self.mocker.replace("os.listdir")
  16. mockListDir(basedir)
  17. self.mocker.result([stubFile, stubSymbolicLink])
  18. mockIsLink = self.mocker.replace("os.path.islink")
  19. mockIsLink(stubSymbolicLink)
  20. self.mocker.result(True)
  22. self.mocker.replay()
  24. files = Utils.getAllDirectoryFilesRecursively(basedir)
  25. for file in files:
  26. print "-------", file
  27. if, file):
  28.'Error, symbolic links are not being ignored')

When Utils.getAllDirectoryFilesRecursively call os.listdir and os.path.islink, it will be calling the replaced mock.

Pyunit test suite builder

Pyunit is a unit tests framework for Python. I believe it is the standard one. It works very well, however to run all the tests within a folder might be not trivial. You can do either hardcode the tests names to build the test suite or use this helper. The helper uses introspection (what is called reflection on .Net):

  2. import unittest
  3. import os
  4. import re
  5. import sys
  6. import inspect
  7. import types
  9. def makeSuite(testsFolder = './tests', testsPackageName = 'tests', testClassSuffix='Tests'):
  10. """Given the testsFolder parameter, loads all the .py files to search for classes
  11. which name contains testClassSuffix and inherit from unittest.TestCase.
  12. It takes these classes to buid the TestSuite and run all the tests.
  13. """
  14. testClasses = []
  15. testModules = {}
  16. testPackage = None
  17. suite = unittest.TestSuite()
  18. folder = os.listdir(testsFolder)
  19. for moduleName in folder:
  20. modulePath = os.path.join(testsFolder, moduleName)
  21. if not os.path.isdir(modulePath) and".py$", moduleName) :
  22. fullModuleName = testsPackageName + '.' + moduleName[:-3]
  23. # curious: this imports the package not the module
  24. testPackage = __import__(fullModuleName)
  25. # it is weird indeed as __import__('packageName') does not load its modules
  26. for packageItem in dir(testPackage):
  27. if, packageItem):
  28. if not testModules.has_key(packageItem):
  29. testModules[packageItem] = True
  30. moduleHandler = getattr(testPackage, packageItem)
  31. for className, classHandler in inspect.getmembers(moduleHandler, callable):
  32. if inspect.isclass(classHandler) and issubclass(classHandler, unittest.TestCase):
  33. testClasses.append(classHandler)
  34. for classHandler in testClasses:
  35. for name, value in inspect.getmembers(classHandler, callable):
  36. if re.match("test", name):
  37. print " - Test:", name
  38. suite.addTest(classHandler(name))
  39. return suite
  41. if __name__ == "__main__":
  42. suite = makeSuite()
  43. runner = unittest.TextTestRunner()

Mock objects on Python

Update: We have developed pyDoubles, a test doubles framework for Python which supports mock objects among other test doubles. We no longer use mocker (described in this post).

The best article I've found on Mock Objects is again written by one of my favorite authors, Martin Fowler: Mocks aren't Stubs.

On Python, I've been trying several frameworks and eventually I've choosen Mocker because it is an active project, it is documented and it follows the same behavior than EasyMock.

Mock objects focus on behavior rather than state so it leads you to Behavior Driven Development, a particular flavor of Test Driven Development. I still don't know if I prefer BDD or classic TDD, I am just learning but in some cases I like to use mock objects, specially on Python.

  • Reason 1: Interpreted languages like Python, don't have a compiler to tell you that API has changed so that if you make changes in a class and don't look for references you get an runtime exception.
  • Reason 2: Third party tools might change their API as well, so you get to reason 1.
  • Reason 3: You can write down the expected behavior for the methods that are supposed to behave always in a certain manner.

My first test case mocks Django models to make sure the relationships work always as I want:

  1. from django.db import models
  2. from siga.managementServices import *
  3. import mocker
  4. import unittest
  5. import os
  7. class managementServicesTests(mocker.MockerTestCase):
  9. def testMockMarkConfigAsParticular(self):
  10. stubServer = Server()
  11. mockServer = self.mocker.proxy(stubServer)
  12. mockConfigFile = self.mocker.proxy(ConfigFile)
  13. mockConfigFile.particularServers.add(mockServer)
  14. self.mocker.result(None)
  16. self.mocker.result(None)
  17. self.mocker.replay()
  18. service = ServersManagementService()
  19. service.markConfigAsParticular(mockServer, mockConfigFile)
  20. self.mocker.verify()

The models are Server and ConfigFile. They have a many2many relationship. The test above says:
There is a Server object and a ConfigFile object. Calling service.markConfigAsParticular and passing in the Server and the ConfigFile instances, will result in two calls in the ConfigFile instance (particularServers.add(server) and save()), both returning None.
Once the expectations are defined, we turn mocker into replay mode and call the real method, the one we're testing:

  1. def markConfigAsParticular(self, server, configFile):
  2. retInfo = []
  3. if server == None:
  4. retInfo = ['1', 'Server does not exist']
  5. elif configFile == None:
  6. retInfo = ['2', 'Configuration file does not exist']
  7. else:
  8. configFile.particularServers.add(server)
  10. retInfo = ['0', 'Success']
  11. return retInfo

In the test method we not only define which methods are going to be invoked but also the order and their parameters. Another nice effect is that data is not persisted into database as the objects in the test are mocks.

I still don't know how much useful is this as I am actually rewriting the method somehow. I'd say if the method was more complex the test would be more useful because it just keeps control of the important behavior, no matter what the remaining implementation is.
As Martin says, I guess one need to work long time following this methodology to realize how productive it is. That happens with TDD but I trust on it for large projects. Time will say it mock objects are good for my current project.