Usage

One of the simplest ways to use singletons is using a factory decorator to make the return value of a function a singleton object. Create a shared.py file:

import uuid
    import singletons

@singletons.GlobalFactory
def my_uuid():
    return uuid.uuid4()

Any time you want to access the instance generated by your factory, just call the my_uuid() function.

Factory decorators include:

  • GlobalFactory
  • ProcessFactory
  • ThreadFactory
  • GreenthreadFactory
  • EventletFactory
  • GeventFactory

You can also declare a class as a singleton by using the metaclass keyword argument:

import singletons

class SharedCache(dict, metaclass=singletons.ThreadSingleton):
    pass

When SharedCache is called (using SharedCache()), if an object already exists for the current thread it is returned, otherwise it is constructed.

Singleton metaclasses include:

  • Singleton
  • ProcessSingleton
  • ThreadSingleton
  • GreenthreadSingleton
  • EventletSingleton
  • GeventSingleton

Writing Tests

A common need when working with singletons is to be able to use Mock objects for unit tests. singletons includes a helper class for making modules easily swappable to use Mocks for everything instead of the factories/classes defined. A common usage would be to put these lines at the bottom of your shared.py file:

class _Shared(singletons.SharedModule):
    globals = globals()
sys.modules[__name__] = _Shared()

To enable the Mock object replacement, call setup_mock() or set the environment variable SINGLETONS_SETUP_MOCK=1. This will replace all accesses of module attributes with Mock() instances. setup_mock can be called inside a TestCase setup() method or as part of a pytest fixture to ensure that each test has a clean set of Mock() instances.

Example test:

class MyTestCase(unittest.TestCase):
    def setup(self):
        shared.setup_mock()

    def test_get_documents():
        c = shared.session()
        # do thing
        c.request.assert_called_once()

To use custom Mock objects, set them as attributes on the module after calling setup_mock:

class MyTestCase(unittest.TestCase):
    def setup(self):
        shared.setup_mock()
        mock_instance = mock.Mock(spec=User)
        mock_instance.name = 'Jane Doe'
        mock_instance.username = 'jdoe123'
        shared.mock_instance = mock_instance

    def test_get_userdata():
        c = shared.mock_instance()
        # do thing
        c.request.assert_called_once()