forked from openlp/openlp
Add a singletom Metaclass
This commit is contained in:
parent
4f0ee2b0d3
commit
dd4d9b9255
@ -172,6 +172,21 @@ class SlideLimits(object):
|
|||||||
Next = 3
|
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):
|
def de_hump(name):
|
||||||
"""
|
"""
|
||||||
Change any Camel Case string to python string
|
Change any Camel Case string to python string
|
||||||
|
@ -29,7 +29,7 @@ from collections import namedtuple
|
|||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
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.applocation import AppLocation
|
||||||
from openlp.core.common.settings import Settings
|
from openlp.core.common.settings import Settings
|
||||||
|
|
||||||
@ -327,22 +327,11 @@ class LanguageManager(object):
|
|||||||
return LanguageManager.__qm_list__
|
return LanguageManager.__qm_list__
|
||||||
|
|
||||||
|
|
||||||
class UiStrings(object):
|
class UiStrings(metaclass=Singleton):
|
||||||
"""
|
"""
|
||||||
Provide standard strings for objects to use.
|
Provide standard strings for objects to use.
|
||||||
"""
|
"""
|
||||||
__instance__ = None
|
def __init__(self):
|
||||||
|
|
||||||
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):
|
|
||||||
"""
|
"""
|
||||||
These strings should need a good reason to be retranslated elsewhere.
|
These strings should need a good reason to be retranslated elsewhere.
|
||||||
Should some/more/less of these have an & attached?
|
Should some/more/less of these have an & attached?
|
||||||
|
@ -23,29 +23,19 @@
|
|||||||
Provide Registry Services
|
Provide Registry Services
|
||||||
"""
|
"""
|
||||||
import logging
|
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__)
|
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
|
This is the Component Registry. It is a singleton object and is used to provide a look up service for common
|
||||||
objects.
|
objects.
|
||||||
"""
|
"""
|
||||||
log.info('Registry loaded')
|
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
|
@classmethod
|
||||||
def create(cls):
|
def create(cls):
|
||||||
@ -57,20 +47,9 @@ class Registry(object):
|
|||||||
registry.service_list = {}
|
registry.service_list = {}
|
||||||
registry.functions_list = {}
|
registry.functions_list = {}
|
||||||
registry.working_flags = {}
|
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
|
registry.initialising = True
|
||||||
return registry
|
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):
|
def get(self, key):
|
||||||
"""
|
"""
|
||||||
Extracts the registry value from the list based on the key passed in
|
Extracts the registry value from the list based on the key passed in
|
||||||
|
@ -28,6 +28,7 @@ from functools import cmp_to_key
|
|||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
|
from openlp.core.common import Singleton
|
||||||
from openlp.core.common.i18n import translate
|
from openlp.core.common.i18n import translate
|
||||||
from openlp.core.common.registry import Registry
|
from openlp.core.common.registry import Registry
|
||||||
from openlp.core.common.settings import Settings
|
from openlp.core.common.settings import Settings
|
||||||
@ -142,24 +143,15 @@ class Screen(object):
|
|||||||
screen_dict['custom_geometry']['height'])
|
screen_dict['custom_geometry']['height'])
|
||||||
|
|
||||||
|
|
||||||
class ScreenList(object):
|
class ScreenList(metaclass=Singleton):
|
||||||
"""
|
"""
|
||||||
Wrapper to handle the parameters of the display screen.
|
Wrapper to handle the parameters of the display screen.
|
||||||
|
|
||||||
To get access to the screen list call ``ScreenList()``.
|
To get access to the screen list call ``ScreenList()``.
|
||||||
"""
|
"""
|
||||||
log.info('Screen loaded')
|
log.info('Screen loaded')
|
||||||
__instance__ = None
|
|
||||||
screens = []
|
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):
|
def __iter__(self):
|
||||||
"""
|
"""
|
||||||
Convert this object into an iterable, so that we can iterate over it instead of the inner list
|
Convert this object into an iterable, so that we can iterate over it instead of the inner list
|
||||||
|
@ -28,6 +28,7 @@ contained within the openlp.core module.
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from openlp.core.common import Singleton
|
||||||
from openlp.core.common.registry import Registry
|
from openlp.core.common.registry import Registry
|
||||||
from openlp.core.common.mixins import LogMixin
|
from openlp.core.common.mixins import LogMixin
|
||||||
from openlp.core.lib.plugin import PluginStatus
|
from openlp.core.lib.plugin import PluginStatus
|
||||||
@ -52,17 +53,7 @@ class StateModule(LogMixin):
|
|||||||
self.text = None
|
self.text = None
|
||||||
|
|
||||||
|
|
||||||
class State(LogMixin):
|
class State(LogMixin, metaclass=Singleton):
|
||||||
|
|
||||||
__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__
|
|
||||||
|
|
||||||
def load_settings(self):
|
def load_settings(self):
|
||||||
self.modules = {}
|
self.modules = {}
|
||||||
|
@ -27,6 +27,7 @@ import logging
|
|||||||
import qtawesome as qta
|
import qtawesome as qta
|
||||||
from PyQt5 import QtGui, QtWidgets
|
from PyQt5 import QtGui, QtWidgets
|
||||||
|
|
||||||
|
from openlp.core.common import Singleton
|
||||||
from openlp.core.common.applocation import AppLocation
|
from openlp.core.common.applocation import AppLocation
|
||||||
from openlp.core.lib import build_icon
|
from openlp.core.lib import build_icon
|
||||||
|
|
||||||
@ -34,22 +35,11 @@ from openlp.core.lib import build_icon
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class UiIcons(object):
|
class UiIcons(metaclass=Singleton):
|
||||||
"""
|
"""
|
||||||
Provide standard icons for objects to use.
|
Provide standard icons for objects to use.
|
||||||
"""
|
"""
|
||||||
__instance__ = None
|
def __init__(self):
|
||||||
|
|
||||||
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):
|
|
||||||
"""
|
"""
|
||||||
These are the font icons used in the code.
|
These are the font icons used in the code.
|
||||||
"""
|
"""
|
||||||
@ -165,6 +155,7 @@ class UiIcons(object):
|
|||||||
'volunteer': {'icon': 'fa.group'}
|
'volunteer': {'icon': 'fa.group'}
|
||||||
}
|
}
|
||||||
self.load_icons(icon_list)
|
self.load_icons(icon_list)
|
||||||
|
self.main_icon = build_icon(':/icon/openlp-logo.svg')
|
||||||
|
|
||||||
def load_icons(self, icon_list):
|
def load_icons(self, icon_list):
|
||||||
"""
|
"""
|
||||||
@ -184,7 +175,6 @@ class UiIcons(object):
|
|||||||
setattr(self, key, qta.icon('fa.plus-circle', color='red'))
|
setattr(self, key, qta.icon('fa.plus-circle', color='red'))
|
||||||
except Exception:
|
except Exception:
|
||||||
setattr(self, key, qta.icon('fa.plus-circle', color='red'))
|
setattr(self, key, qta.icon('fa.plus-circle', color='red'))
|
||||||
self.main_icon = build_icon(':/icon/openlp-logo.svg')
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _print_icons():
|
def _print_icons():
|
||||||
|
@ -26,6 +26,7 @@ plugin.
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from openlp.core.common import Singleton
|
||||||
from openlp.core.common.i18n import translate
|
from openlp.core.common.i18n import translate
|
||||||
from openlp.core.common.settings import Settings
|
from openlp.core.common.settings import Settings
|
||||||
|
|
||||||
@ -64,20 +65,10 @@ class LanguageSelection(object):
|
|||||||
English = 2
|
English = 2
|
||||||
|
|
||||||
|
|
||||||
class BibleStrings(object):
|
class BibleStrings(metaclass=Singleton):
|
||||||
"""
|
"""
|
||||||
Provide standard strings for objects to use.
|
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):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
These strings should need a good reason to be retranslated elsewhere.
|
These strings should need a good reason to be retranslated elsewhere.
|
||||||
|
@ -26,7 +26,7 @@ from pathlib import Path
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock, call, patch
|
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
|
normalize_str, path_to_module, trace_error_handler
|
||||||
|
|
||||||
|
|
||||||
@ -163,6 +163,48 @@ class TestCommonFunctions(TestCase):
|
|||||||
mocked_logger.error.assert_called_with(
|
mocked_logger.error.assert_called_with(
|
||||||
'OpenLP Error trace\n File openlp.fake at line 56 \n\t called trace_error_handler_test')
|
'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):
|
def test_is_win(self):
|
||||||
"""
|
"""
|
||||||
Test the is_win() function
|
Test the is_win() function
|
||||||
|
@ -33,7 +33,7 @@ from tests.helpers.testmixin import TestMixin
|
|||||||
|
|
||||||
class TestIcons(TestCase, 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, _):
|
def test_simple_icon(self, _):
|
||||||
# GIVEN: an basic set of icons
|
# GIVEN: an basic set of icons
|
||||||
icons = UiIcons()
|
icons = UiIcons()
|
||||||
|
Loading…
Reference in New Issue
Block a user