test_subclass_registry.subclass_registry_module()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 15
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 15
rs 9.75
c 0
b 0
f 0
cc 1
nop 0
1
import typing as t
2
3
import pytest
4
5
6
@pytest.fixture
7
def subclass_registry_module():
8
    from software_patterns import SubclassRegistry
9
    from software_patterns.subclass_registry import (
10
        InstantiationError,
11
        UnknownClassError,
12
    )
13
14
    return type(
15
        'M',
16
        (),
17
        {
18
            'SubclassRegistry': SubclassRegistry,
19
            'InstantiationError': InstantiationError,
20
            'UnknownClassError': UnknownClassError,
21
        },
22
    )
23
24
25
@pytest.fixture
26
def register_class():
27
    from software_patterns import SubclassRegistry
28
29
    def _register_class(subclass_id: str, inherit=False):
30
        class ParentClass(metaclass=SubclassRegistry):
31
            pass
32
33
        if inherit:
34
35
            @ParentClass.register_as_subclass(subclass_id)
36
            class Child1(ParentClass):
37
                pass
38
39
        else:
40
41
            @ParentClass.register_as_subclass(subclass_id)
42
            class Child2:
43
                pass
44
45
        child_instance: t.Any = ParentClass.create(subclass_id)
46
47
        return {
48
            'class_registry': ParentClass,
49
            'child': ParentClass.subclasses[subclass_id],
50
            'child_instance': child_instance,
51
        }
52
53
    return _register_class
54
55
56
@pytest.fixture
57
def use_metaclass(register_class, assert_correct_metaclass_behaviour):
58
    inherited_from_parent = {
59
        True: lambda classes: isinstance(classes['child_instance'], classes['class_registry']),
60
        False: lambda classes: not isinstance(
61
            classes['child_instance'], classes['class_registry']
62
        ),
63
    }
64
65
    def _use_metaclass_in_scenario(subclass_id: str, inherit=False):
66
        classes = register_class(subclass_id, inherit=inherit)
67
        assert_correct_metaclass_behaviour(classes, subclass_id)
68
        assert bool(inherited_from_parent[inherit])
69
        return classes['child_instance'], classes['child'], classes['class_registry']
70
71
    return _use_metaclass_in_scenario
72
73
74
@pytest.fixture
75
def assert_correct_metaclass_behaviour():
76
    def assert_metaclass_behaviour(classes, subclass_id):
77
        assert classes['class_registry'].subclasses[subclass_id] == classes['child']
78
        assert type(classes['child_instance']) == classes['child']
79
        assert isinstance(classes['child_instance'], classes['child'])
80
81
    return assert_metaclass_behaviour
82
83
84
def test_metaclass_usage():
85
    from software_patterns import SubclassRegistry
86
87
    class ParentClass(metaclass=SubclassRegistry):
88
        pass
89
90
    assert type(ParentClass) == SubclassRegistry
91
    assert hasattr(ParentClass, 'subclasses')
92
    assert hasattr(ParentClass, 'create')
93
    assert hasattr(ParentClass, 'register_as_subclass')
94
    assert ParentClass.subclasses == {}
95
96
97
def test_subclass_registry(use_metaclass, subclass_registry_module):
98
    child1_instance1, Child1, ParentClass = use_metaclass('child1', inherit=True)
99
100
    non_existent_identifier = 'child2'
101
102
    exception_message_regex = (
103
        f'Bad "{str(ParentClass.__name__)}" subclass request; requested subclass with identifier '
104
        f'{non_existent_identifier}, but known identifiers are '
105
        rf'\[{", ".join(subclass_identifier for subclass_identifier in ParentClass.subclasses.keys())}\]'
106
    )
107
108
    with pytest.raises(
109
        subclass_registry_module.UnknownClassError, match=exception_message_regex
110
    ):
111
        ParentClass.create(non_existent_identifier)
112
113
    child1_instance2, Child2, ParentClass2 = use_metaclass('child2', inherit=False)
114
    assert ParentClass.subclasses['child1'] == Child1
115
116
117
def test_create_wrong_input():
118
    from software_patterns import SubclassRegistry
119
    from software_patterns.subclass_registry import InstantiationError
120
121
    class ClassRegistry(metaclass=SubclassRegistry):
122
        pass
123
124
    @ClassRegistry.register_as_subclass('id-1')
125
    class ConcreteClassA:
126
        pass
127
128
    @ClassRegistry.register_as_subclass('id-2')
129
    class ConcreteClassB:
130
        def __init__(self, a):
131
            self.a = a
132
133
    with pytest.raises(InstantiationError):
134
        ClassRegistry.create('id-1', 'extra-argument')
135
136
    with pytest.raises(InstantiationError):
137
        ClassRegistry.create('id-1', extra_key='extra-kwarg')
138
139
    ClassRegistry.create('id-2', 'argument-a-provided')
140
    with pytest.raises(InstantiationError):
141
        ClassRegistry.create('id-2')
142