Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions radish/customtyperegistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@

# Keep for backwards compat:
from parse_type import TypeBuilder # noqa: F401
from singleton import singleton

from .exceptions import RadishError
from .utils import Singleton


@singleton()
class CustomTypeRegistry:
class CustomTypeRegistry(metaclass=Singleton):
"""
Registry for all custom argument expressions
"""
Expand Down
5 changes: 2 additions & 3 deletions radish/extensionregistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
Provide plugin interface for radish extensions
"""

from singleton import singleton
from .utils import Singleton


@singleton()
class ExtensionRegistry:
class ExtensionRegistry(metaclass=Singleton):
"""
Registers all extensions
"""
Expand Down
5 changes: 2 additions & 3 deletions radish/hookregistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
"""

import tagexpressions
from singleton import singleton

from . import utils
from .exceptions import HookError
from .utils import Singleton


@singleton()
class HookRegistry:
class HookRegistry(metaclass=Singleton):
"""
Represents an object with all registered hooks
"""
Expand Down
6 changes: 2 additions & 4 deletions radish/stepregistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
import inspect
import re

from singleton import singleton

from .exceptions import RadishError, SameStepError, StepRegexError
from .utils import Singleton


@singleton()
class StepRegistry:
class StepRegistry(metaclass=Singleton):
"""
Represents the step registry
"""
Expand Down
33 changes: 33 additions & 0 deletions radish/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import traceback
import warnings
from datetime import datetime, timedelta, timezone
from threading import Lock


class Failure:
Expand Down Expand Up @@ -230,3 +231,35 @@ def split_unescape(s, delim, escape="\\", unescape=True):
current.append(ch)
ret.append("".join(current))
return ret


class Singleton(type):
"""
Metaclass for singleton classes. To create a singleton class use this metaclass:

class MySingletonClass(metaclass=Singleton):
pass

Now Singleton will ensure that only one instance of MySingletonClass is created.
"""

_instances = {}
_lock = Lock()

def __call__(cls, *args, **kwargs):
"""
Called when you create a new instance of a class with this metaclass.

It ensures that only one instance of the class is created (singleton pattern).

When you call `MySingletonClass(args, kwargs)`, python calls
`Singleton.__call__(MySingletonClass, args, kwargs)`.

This method in turn check if an instance of `MySingletonClass` already exists.
If not, it creates one and stores it in the `_instances` dictionary.
"""
if cls not in cls._instances: # Only grab the lock if the instance is not in the dict
with cls._lock: # Ensure thread-safety
if cls not in cls._instances: # in case instance was added while waiting for lock
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pysingleton==0.2.1
colorful==0.5.8
docopt==0.6.2
ipython==7.34.0
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ def get_meta(name):
# mandatory requirements for the radish base features
requirements = [
"docopt",
"pysingleton",
"colorful>=0.3.11",
"tag-expressions>=2.0.0",
"parse_type>0.4.0",
Expand Down
44 changes: 44 additions & 0 deletions tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""

from datetime import datetime, timezone
from threading import Lock, Thread

import pytest
from freezegun import freeze_time
Expand Down Expand Up @@ -67,3 +68,46 @@ def test_make_unique_obj_list():
value_list.sort()

assert value_list == ["1", "2"]


def test_singleton_behavior():
"""Test that Singleton metaclass enforces singleton behavior"""

class MySingletonClass(metaclass=utils.Singleton):
def __init__(self):
self.value = 42

instance1 = MySingletonClass()
instance1.value = 100 # Modify value to test singleton behavior
instance2 = MySingletonClass()

assert instance1 is instance2 # They should be the same instance
assert instance1.value == instance2.value # Value should be the same changed value


def test_singleton_thread_safety():
"""
Test that Singleton metaclass is thread-safe by creating multiple instances in parallel threads
"""

class MyThreadSafeSingleton(metaclass=utils.Singleton):
def __init__(self):
self.value = 0

instances = []
lock = Lock()

def create_instance():
instance = MyThreadSafeSingleton()
with lock: # Ensure thread-safe appending
instances.append(instance)

threads = [Thread(target=create_instance) for _ in range(10)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()

first_instance = instances[0]
for instance in instances:
assert instance is first_instance
Loading