Passed
Push — dev ( 996b14...d06756 )
by Konstantinos
03:56
created

test_subclass_registry.subclass_registry_class()   A

Complexity

Conditions 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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