Add a singletom Metaclass

This commit is contained in:
Phill 2019-07-20 13:27:28 +01:00
parent 4f0ee2b0d3
commit dd4d9b9255
9 changed files with 74 additions and 85 deletions

View File

@ -172,6 +172,21 @@ class SlideLimits(object):
Next = 3
class Singleton(type):
"""
Provide a `Singleton` metaclass https://stackoverflow.com/questions/6760685/creating-a-singleton-in-python
"""
_instances = {}
def __call__(cls, *args, **kwargs):
"""
Create a new instance if one does not already exist.
"""
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
def de_hump(name):
"""
Change any Camel Case string to python string

View File

@ -29,7 +29,7 @@ from collections import namedtuple
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import is_macosx, is_win
from openlp.core.common import Singleton, is_macosx, is_win
from openlp.core.common.applocation import AppLocation
from openlp.core.common.settings import Settings
@ -327,22 +327,11 @@ class LanguageManager(object):
return LanguageManager.__qm_list__
class UiStrings(object):
class UiStrings(metaclass=Singleton):
"""
Provide standard strings for objects to use.
"""
__instance__ = None
def __new__(cls):
"""
Override the default object creation method to return a single instance.
"""
if not cls.__instance__:
cls.__instance__ = super().__new__(cls)
cls.__instance__.load()
return cls.__instance__
def load(self):
def __init__(self):
"""
These strings should need a good reason to be retranslated elsewhere.
Should some/more/less of these have an & attached?

View File

@ -23,29 +23,19 @@
Provide Registry Services
"""
import logging
import sys
from openlp.core.common import de_hump, trace_error_handler
from openlp.core.common import Singleton, de_hump, trace_error_handler
log = logging.getLogger(__name__)
class Registry(object):
class Registry(metaclass=Singleton):
"""
This is the Component Registry. It is a singleton object and is used to provide a look up service for common
objects.
"""
log.info('Registry loaded')
__instance__ = None
def __new__(cls):
"""
Re-implement the __new__ method to make sure we create a true singleton.
"""
if not cls.__instance__:
cls.__instance__ = object.__new__(cls)
return cls.__instance__
@classmethod
def create(cls):
@ -57,20 +47,9 @@ class Registry(object):
registry.service_list = {}
registry.functions_list = {}
registry.working_flags = {}
# Allow the tests to remove Registry entries but not the live system
registry.running_under_test = 'nose' in sys.argv[0] or 'pytest' in sys.argv[0]
registry.initialising = True
return registry
@classmethod
def destroy(cls):
"""
Destroy the Registry.
"""
if cls.__instance__.running_under_test:
del cls.__instance__
cls.__instance__ = None
def get(self, key):
"""
Extracts the registry value from the list based on the key passed in

View File

@ -28,6 +28,7 @@ from functools import cmp_to_key
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Singleton
from openlp.core.common.i18n import translate
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
@ -142,24 +143,15 @@ class Screen(object):
screen_dict['custom_geometry']['height'])
class ScreenList(object):
class ScreenList(metaclass=Singleton):
"""
Wrapper to handle the parameters of the display screen.
To get access to the screen list call ``ScreenList()``.
"""
log.info('Screen loaded')
__instance__ = None
screens = []
def __new__(cls):
"""
Re-implement __new__ to create a true singleton.
"""
if not cls.__instance__:
cls.__instance__ = object.__new__(cls)
return cls.__instance__
def __iter__(self):
"""
Convert this object into an iterable, so that we can iterate over it instead of the inner list

View File

@ -28,6 +28,7 @@ contained within the openlp.core module.
"""
import logging
from openlp.core.common import Singleton
from openlp.core.common.registry import Registry
from openlp.core.common.mixins import LogMixin
from openlp.core.lib.plugin import PluginStatus
@ -52,17 +53,7 @@ class StateModule(LogMixin):
self.text = None
class State(LogMixin):
__instance__ = None
def __new__(cls):
"""
Re-implement the __new__ method to make sure we create a true singleton.
"""
if not cls.__instance__:
cls.__instance__ = object.__new__(cls)
return cls.__instance__
class State(LogMixin, metaclass=Singleton):
def load_settings(self):
self.modules = {}

View File

@ -27,6 +27,7 @@ import logging
import qtawesome as qta
from PyQt5 import QtGui, QtWidgets
from openlp.core.common import Singleton
from openlp.core.common.applocation import AppLocation
from openlp.core.lib import build_icon
@ -34,22 +35,11 @@ from openlp.core.lib import build_icon
log = logging.getLogger(__name__)
class UiIcons(object):
class UiIcons(metaclass=Singleton):
"""
Provide standard icons for objects to use.
"""
__instance__ = None
def __new__(cls):
"""
Override the default object creation method to return a single instance.
"""
if not cls.__instance__:
cls.__instance__ = super().__new__(cls)
cls.__instance__.load()
return cls.__instance__
def load(self):
def __init__(self):
"""
These are the font icons used in the code.
"""
@ -165,6 +155,7 @@ class UiIcons(object):
'volunteer': {'icon': 'fa.group'}
}
self.load_icons(icon_list)
self.main_icon = build_icon(':/icon/openlp-logo.svg')
def load_icons(self, icon_list):
"""
@ -184,7 +175,6 @@ class UiIcons(object):
setattr(self, key, qta.icon('fa.plus-circle', color='red'))
except Exception:
setattr(self, key, qta.icon('fa.plus-circle', color='red'))
self.main_icon = build_icon(':/icon/openlp-logo.svg')
@staticmethod
def _print_icons():

View File

@ -26,6 +26,7 @@ plugin.
import logging
import re
from openlp.core.common import Singleton
from openlp.core.common.i18n import translate
from openlp.core.common.settings import Settings
@ -64,20 +65,10 @@ class LanguageSelection(object):
English = 2
class BibleStrings(object):
class BibleStrings(metaclass=Singleton):
"""
Provide standard strings for objects to use.
"""
__instance__ = None
def __new__(cls):
"""
Override the default object creation method to return a single instance.
"""
if not cls.__instance__:
cls.__instance__ = object.__new__(cls)
return cls.__instance__
def __init__(self):
"""
These strings should need a good reason to be retranslated elsewhere.

View File

@ -26,7 +26,7 @@ from pathlib import Path
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
from openlp.core.common import clean_button_text, de_hump, extension_loader, is_linux, is_macosx, is_win, \
from openlp.core.common import Singleton, clean_button_text, de_hump, extension_loader, is_linux, is_macosx, is_win, \
normalize_str, path_to_module, trace_error_handler
@ -163,6 +163,48 @@ class TestCommonFunctions(TestCase):
mocked_logger.error.assert_called_with(
'OpenLP Error trace\n File openlp.fake at line 56 \n\t called trace_error_handler_test')
def test_singleton_metaclass_multiple_init(self):
"""
Test that a class using the Singleton Metaclass is only initialised once despite being called several times and
that the same instance is returned each time..
"""
# GIVEN: The Singleton Metaclass and a test class using it
class SingletonClass(metaclass=Singleton):
def __init__(self):
pass
with patch.object(SingletonClass, '__init__', return_value=None) as patched_init:
# WHEN: Initialising the class multiple times
inst_1 = SingletonClass()
inst_2 = SingletonClass()
# THEN: The __init__ method of the SingletonClass should have only been called once, and both returned values
# should be the same instance.
assert inst_1 is inst_2
assert patched_init.call_count == 1
def test_singleton_metaclass_multiple_classes(self):
"""
Test that multiple classes using the Singleton Metaclass return the different an appropriate instances.
"""
# GIVEN: Two different classes using the Singleton Metaclass
class SingletonClass1(metaclass=Singleton):
def __init__(self):
pass
class SingletonClass2(metaclass=Singleton):
def __init__(self):
pass
# WHEN: Initialising both classes
s_c1 = SingletonClass1()
s_c2 = SingletonClass2()
# THEN: The instances should be an instance of the appropriate class
assert isinstance(s_c1, SingletonClass1)
assert isinstance(s_c2, SingletonClass2)
def test_is_win(self):
"""
Test the is_win() function

View File

@ -33,7 +33,7 @@ from tests.helpers.testmixin import TestMixin
class TestIcons(TestCase, TestMixin):
@patch('openlp.core.ui.icons.UiIcons.load')
@patch('openlp.core.ui.icons.UiIcons.__init__', return_value=None)
def test_simple_icon(self, _):
# GIVEN: an basic set of icons
icons = UiIcons()