WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 9d31a7d

Browse files
authored
Merge pull request #494 from alexandrehassan/remove-pysingleton
Create a metaclass for Singleton's and remove pysingleton
2 parents 49dbe35 + 10fdc60 commit 9d31a7d

File tree

8 files changed

+85
-15
lines changed

8 files changed

+85
-15
lines changed

radish/customtyperegistry.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44

55
# Keep for backwards compat:
66
from parse_type import TypeBuilder # noqa: F401
7-
from singleton import singleton
87

98
from .exceptions import RadishError
9+
from .utils import Singleton
1010

1111

12-
@singleton()
13-
class CustomTypeRegistry:
12+
class CustomTypeRegistry(metaclass=Singleton):
1413
"""
1514
Registry for all custom argument expressions
1615
"""

radish/extensionregistry.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
Provide plugin interface for radish extensions
33
"""
44

5-
from singleton import singleton
5+
from .utils import Singleton
66

77

8-
@singleton()
9-
class ExtensionRegistry:
8+
class ExtensionRegistry(metaclass=Singleton):
109
"""
1110
Registers all extensions
1211
"""

radish/hookregistry.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
"""
44

55
import tagexpressions
6-
from singleton import singleton
76

87
from . import utils
98
from .exceptions import HookError
9+
from .utils import Singleton
1010

1111

12-
@singleton()
13-
class HookRegistry:
12+
class HookRegistry(metaclass=Singleton):
1413
"""
1514
Represents an object with all registered hooks
1615
"""

radish/stepregistry.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@
55
import inspect
66
import re
77

8-
from singleton import singleton
9-
108
from .exceptions import RadishError, SameStepError, StepRegexError
9+
from .utils import Singleton
1110

1211

13-
@singleton()
14-
class StepRegistry:
12+
class StepRegistry(metaclass=Singleton):
1513
"""
1614
Represents the step registry
1715
"""

radish/utils.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import traceback
1313
import warnings
1414
from datetime import datetime, timedelta, timezone
15+
from threading import Lock
1516

1617

1718
class Failure:
@@ -230,3 +231,35 @@ def split_unescape(s, delim, escape="\\", unescape=True):
230231
current.append(ch)
231232
ret.append("".join(current))
232233
return ret
234+
235+
236+
class Singleton(type):
237+
"""
238+
Metaclass for singleton classes. To create a singleton class use this metaclass:
239+
240+
class MySingletonClass(metaclass=Singleton):
241+
pass
242+
243+
Now Singleton will ensure that only one instance of MySingletonClass is created.
244+
"""
245+
246+
_instances = {}
247+
_lock = Lock()
248+
249+
def __call__(cls, *args, **kwargs):
250+
"""
251+
Called when you create a new instance of a class with this metaclass.
252+
253+
It ensures that only one instance of the class is created (singleton pattern).
254+
255+
When you call `MySingletonClass(args, kwargs)`, python calls
256+
`Singleton.__call__(MySingletonClass, args, kwargs)`.
257+
258+
This method in turn check if an instance of `MySingletonClass` already exists.
259+
If not, it creates one and stores it in the `_instances` dictionary.
260+
"""
261+
if cls not in cls._instances: # Only grab the lock if the instance is not in the dict
262+
with cls._lock: # Ensure thread-safety
263+
if cls not in cls._instances: # in case instance was added while waiting for lock
264+
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
265+
return cls._instances[cls]

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
pysingleton==0.2.1
21
colorful==0.5.8
32
docopt==0.6.2
43
ipython==7.34.0

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ def get_meta(name):
3737
# mandatory requirements for the radish base features
3838
requirements = [
3939
"docopt",
40-
"pysingleton",
4140
"colorful>=0.3.11",
4241
"tag-expressions>=2.0.0",
4342
"parse_type>0.4.0",

tests/unit/test_utils.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
from datetime import datetime, timezone
11+
from threading import Lock, Thread
1112

1213
import pytest
1314
from freezegun import freeze_time
@@ -67,3 +68,46 @@ def test_make_unique_obj_list():
6768
value_list.sort()
6869

6970
assert value_list == ["1", "2"]
71+
72+
73+
def test_singleton_behavior():
74+
"""Test that Singleton metaclass enforces singleton behavior"""
75+
76+
class MySingletonClass(metaclass=utils.Singleton):
77+
def __init__(self):
78+
self.value = 42
79+
80+
instance1 = MySingletonClass()
81+
instance1.value = 100 # Modify value to test singleton behavior
82+
instance2 = MySingletonClass()
83+
84+
assert instance1 is instance2 # They should be the same instance
85+
assert instance1.value == instance2.value # Value should be the same changed value
86+
87+
88+
def test_singleton_thread_safety():
89+
"""
90+
Test that Singleton metaclass is thread-safe by creating multiple instances in parallel threads
91+
"""
92+
93+
class MyThreadSafeSingleton(metaclass=utils.Singleton):
94+
def __init__(self):
95+
self.value = 0
96+
97+
instances = []
98+
lock = Lock()
99+
100+
def create_instance():
101+
instance = MyThreadSafeSingleton()
102+
with lock: # Ensure thread-safe appending
103+
instances.append(instance)
104+
105+
threads = [Thread(target=create_instance) for _ in range(10)]
106+
for thread in threads:
107+
thread.start()
108+
for thread in threads:
109+
thread.join()
110+
111+
first_instance = instances[0]
112+
for instance in instances:
113+
assert instance is first_instance

0 commit comments

Comments
 (0)