Skip to content

Commit e554cf7

Browse files
committed
Add Singleton pattern implementation with tests and configuration
1 parent c986c9e commit e554cf7

File tree

6 files changed

+154
-2
lines changed

6 files changed

+154
-2
lines changed

makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
VERSION = $(shell python get_version.py)
33

4-
MODULES = cache colors config http_code version
4+
MODULES = cache colors config http_code version singleton
55

66
TARGETS = $(addprefix dist/,$(addsuffix -$(VERSION).tar.gz,$(MODULES)))
77
TARGETS += $(addprefix dist/,$(addsuffix -$(VERSION)-py3-none-any.whl,$(MODULES)))
@@ -29,4 +29,4 @@ tests: $(addprefix tests/,$(MODULES))
2929

3030
clean:
3131
rm -rf dist
32-
rm -rf **/*.egg-info **/build
32+
rm -rf **/*.egg-info **/build

singleton/pyproject.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[project]
2+
name = "singleton"
3+
dynamic = ["version"]
4+
description = "Singleton pattern implementation in Python"
5+
authors = [
6+
{ name = "Antoine BUIREY", email = "antoine.buirey@gmail.com" }
7+
]
8+
requires-python = ">=3.10"
9+
10+
[tool.setuptools.packages.find]
11+
include = [
12+
"singleton"
13+
]
14+
15+
[tool.pytest.ini_options]
16+
minversion = "8.0"
17+
testpaths = [
18+
"tests"
19+
]
20+
python_files = [
21+
"*tests.py"
22+
]

singleton/setup.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""
2+
Setup script for the package, used by setuptools to build and install the package.
3+
"""
4+
5+
import os
6+
7+
from setuptools import setup
8+
9+
setup(
10+
version=os.environ.get("PACKAGE_VERSION", "0.0.0-dev")
11+
)

singleton/singleton/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .singleton import Singleton

singleton/singleton/singleton.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
3+
class Singleton:
4+
"""
5+
A implementation of Singleton pattern.
6+
This class ensures that only one instance of the class is created.
7+
"""
8+
9+
__instance : 'Singleton' = None
10+
11+
def __new__(cls, *args, **kwargs):
12+
"""
13+
Create a new instance of the class if it doesn't exist, otherwise return the existing instance.
14+
"""
15+
if not cls.__instance:
16+
cls.__instance = super(Singleton, cls).__new__(cls)
17+
return cls.__instance

singleton/tests/singleton_tests.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from singleton.singleton.singleton import Singleton
2+
import pytest
3+
4+
5+
@pytest.mark.parametrize(
6+
"args, kwargs",
7+
[
8+
((), {}),
9+
((1,), {}),
10+
(("foo", 42), {}),
11+
((), {"a": 1},),
12+
((1, 2), {"a": 3, "b": 4}),
13+
],
14+
ids = ["no_args", "single_arg", "multiple_args", "no_args_with_kwargs", "multiple_args_with_kwargs"]
15+
)
16+
def test_singleton_instance_uniqueness(args, kwargs):
17+
# Arrange
18+
19+
# Act
20+
instance1 = Singleton(*args, **kwargs)
21+
instance2 = Singleton(*args, **kwargs)
22+
23+
# Assert
24+
assert instance1 is instance2
25+
assert isinstance(instance1, Singleton)
26+
assert isinstance(instance2, Singleton)
27+
28+
def test_singleton_instance_is_singleton_across_different_args():
29+
# Arrange
30+
31+
# Act
32+
instance1 = Singleton(1, 2, a=3)
33+
instance2 = Singleton("foo", bar="baz")
34+
35+
# Assert
36+
assert instance1 is instance2
37+
38+
def test_singleton_instance_is_singleton_across_no_args_and_args():
39+
# Arrange
40+
41+
# Act
42+
instance1 = Singleton()
43+
instance2 = Singleton(123)
44+
instance3 = Singleton(a="b")
45+
46+
# Assert
47+
assert instance1 is instance2
48+
assert instance2 is instance3
49+
50+
51+
def test_singleton_instance_is_not_none():
52+
# Arrange
53+
54+
# Act
55+
instance = Singleton()
56+
57+
# Assert
58+
assert instance is not None
59+
60+
def test_singleton_instance_reset_for_test(monkeypatch):
61+
# Arrange
62+
monkeypatch.setattr(Singleton, "_Singleton__instance", None)
63+
64+
# Act
65+
instance1 = Singleton()
66+
instance2 = Singleton()
67+
68+
# Assert
69+
assert instance1 is instance2
70+
assert Singleton._Singleton__instance is instance1
71+
72+
@pytest.mark.parametrize(
73+
"pre_existing_instance, expected_new_instance",
74+
[
75+
(None, True),
76+
("dummy", False),
77+
],
78+
ids=["no_pre_existing_instance", "pre_existing_instance"]
79+
)
80+
def test_singleton_new_branch_coverage(monkeypatch, pre_existing_instance, expected_new_instance):
81+
# Arrange
82+
monkeypatch.setattr(Singleton, "_Singleton__instance", pre_existing_instance)
83+
84+
# Act
85+
instance = Singleton()
86+
87+
# Assert
88+
if expected_new_instance:
89+
assert isinstance(instance, Singleton)
90+
else:
91+
assert instance == pre_existing_instance
92+
93+
def test_singleton_direct_class_attribute_access():
94+
# Arrange
95+
96+
# Act
97+
instance = Singleton()
98+
class_instance = Singleton._Singleton__instance
99+
100+
# Assert
101+
assert instance is class_instance

0 commit comments

Comments
 (0)