Completed
Push — develop ( 7d38f7...827c8b )
by Jace
02:04
created

describe_attr()   D

Complexity

Conditions 10

Size

Total Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 63
rs 4.6153
cc 10

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like describe_attr() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# pylint: disable=unused-variable,expression-not-assigned
2
# pylint: disable=missing-docstring,no-self-use,no-member,misplaced-comparison-constant
3
4
import logging
5
from unittest.mock import patch, Mock
6
7
import pytest
8
from expecter import expect
9
10
from yorm import decorators
11
from yorm.bases import Converter
12
13
log = logging.getLogger(__name__)
14
15
16
class MockConverter(Converter):
17
    """Sample converter class."""
18
19
    @classmethod
20
    def create_default(cls):
21
        return None
22
23
    @classmethod
24
    def to_value(cls, *_):
25
        return None
26
27
    @classmethod
28
    def to_data(cls, _):
29
        return None
30
31
32
def describe_sync():
33
34
    def describe_object():
35
36
        @pytest.fixture
37
        def instance():
38
            cls = type('Sample', (), {})
39
            instance = cls()
40
            return instance
41
42
        @pytest.fixture
43
        def path(tmpdir):
44
            tmpdir.chdir()
45
            return "sample.yml"
46
47
        def with_no_attrs(instance, path):
48
            sample = decorators.sync(instance, path)
49
50
            expect(sample.__mapper__.path) == "sample.yml"
51
            expect(sample.__mapper__.attrs) == {}
52
53
        def with_attrs(instance, path):
54
            attrs = {'var1': MockConverter}
55
            sample = decorators.sync(instance, path, attrs)
56
57
            expect(sample.__mapper__.path) == "sample.yml"
58
            expect(sample.__mapper__.attrs) == {'var1': MockConverter}
59
60
        def cannot_be_called_twice(instance, path):
61
            sample = decorators.sync(instance, path)
62
63
            with pytest.raises(TypeError):
64
                decorators.sync(instance, path)
65
66
        @patch('yorm.diskutils.exists', Mock(return_value=True))
67
        @patch('yorm.diskutils.read', Mock(return_value="abc: 123"))
68
        @patch('yorm.diskutils.stamp', Mock())
69
        def reads_existing_files(instance, path):
70
            sample = decorators.sync(instance, path, auto_track=True)
71
72
            expect(sample.abc) == 123
73
74
75
@patch('yorm.diskutils.write', Mock())
76
@patch('yorm.diskutils.stamp', Mock())
77
@patch('yorm.diskutils.read', Mock(return_value=""))
78
class TestSyncInstances:
79
    """Unit tests for the `sync_instances` decorator."""
80
81
    @decorators.sync("sample.yml")
82
    class SampleDecorated:
83
        """Sample decorated class."""
84
85
        def __repr__(self):
86
            return "<decorated {}>".format(id(self))
87
88
    @decorators.sync("sample.yml", auto_track=True)
89
    class SampleDecoratedAutoTrack:
90
        """Sample decorated class with automatic attribute tracking."""
91
92
        def __repr__(self):
93
            return "<decorated {}>".format(id(self))
94
95
    @decorators.sync("{UUID}.yml")
96
    class SampleDecoratedIdentifiers:
97
        """Sample decorated class using UUIDs for paths."""
98
99
        def __repr__(self):
100
            return "<decorated w/ UUID {}>".format(id(self))
101
102
    @decorators.sync("tmp/path/to/{n}.yml", {'n': 'name'})
103
    class SampleDecoratedAttributes:
104
        """Sample decorated class using an attribute value for paths."""
105
106
        def __init__(self, name):
107
            self.name = name
108
109
        def __repr__(self):
110
            return "<decorated w/ specified attributes {}>".format(id(self))
111
112
    @decorators.sync("tmp/path/to/{self.name}.yml")
113
    class SampleDecoratedAttributesAutomatic:
114
        """Sample decorated class using an attribute value for paths."""
115
116
        def __init__(self, name):
117
            self.name = name
118
119
        def __repr__(self):
120
            return "<decorated w/ automatic attributes {}>".format(id(self))
121
122
    @decorators.sync("{self.a}/{self.b}/{c}.yml", {'self.b': 'b', 'c': 'c'})
123
    class SampleDecoratedAttributesCombination:
124
        """Sample decorated class using an attribute value for paths."""
125
126
        def __init__(self, a, b, c):
127
            self.a = a
128
            self.b = b
129
            self.c = c
130
131
        def __repr__(self):
132
            return "<decorated w/ attributes {}>".format(id(self))
133
134
    @decorators.sync("sample.yml", attrs={'var1': MockConverter})
135
    class SampleDecoratedWithAttributes:
136
        """Sample decorated class using a single path."""
137
138
    def test_no_attrs(self):
139
        """Verify mapping can be enabled with no attributes."""
140
        sample = self.SampleDecorated()
141
142
        expect(sample.__mapper__.path) == "sample.yml"
143
        expect(sample.__mapper__.attrs) == {}
144
145
    def test_with_attrs(self):
146
        """Verify mapping can be enabled with with attributes."""
147
        sample = self.SampleDecoratedWithAttributes()
148
        assert "sample.yml" == sample.__mapper__.path
149
        assert ['var1'] == list(sample.__mapper__.attrs.keys())
150
151
    @patch('yorm.diskutils.exists', Mock(return_value=True))
152
    def test_init_existing(self):
153
        """Verify an existing file is read."""
154
        with patch('yorm.diskutils.read', Mock(return_value="abc: 123")):
155
            sample = self.SampleDecoratedAutoTrack()
156
        assert 123 == sample.abc
157
158
    @patch('uuid.uuid4', Mock(return_value=Mock(hex='abc123')))
159
    def test_filename_uuid(self):
160
        """Verify UUIDs can be used for filename."""
161
        sample = self.SampleDecoratedIdentifiers()
162
        assert "abc123.yml" == sample.__mapper__.path
163
        assert {} == sample.__mapper__.attrs
164
165
    def test_filename_attributes(self):
166
        """Verify attributes can be used to determine filename."""
167
        sample1 = self.SampleDecoratedAttributes('one')
168
        sample2 = self.SampleDecoratedAttributes('two')
169
        assert "tmp/path/to/one.yml" == sample1.__mapper__.path
170
        assert "tmp/path/to/two.yml" == sample2.__mapper__.path
171
172
    def test_filename_attributes_automatic(self):
173
        """Verify attributes can be used to determine filename (auto save)."""
174
        sample1 = self.SampleDecoratedAttributesAutomatic('one')
175
        sample2 = self.SampleDecoratedAttributesAutomatic('two')
176
        assert "tmp/path/to/one.yml" == sample1.__mapper__.path
177
        assert "tmp/path/to/two.yml" == sample2.__mapper__.path
178
179
    def test_filename_attributes_combination(self):
180
        """Verify attributes can be used to determine filename (combo)."""
181
        log.info("Creating first object...")
182
        sample1 = self.SampleDecoratedAttributesCombination('A', 'B', 'C')
183
        log.info("Creating second object...")
184
        sample2 = self.SampleDecoratedAttributesCombination(1, 2, 3)
185
        assert "A/B/C.yml" == sample1.__mapper__.path
186
        assert "1/2/3.yml" == sample2.__mapper__.path
187
188
189
def describe_attr():
190
191
    class MockConverter1(MockConverter):
192
        """Sample converter class."""
193
194
    class MockConverter2(MockConverter):
195
        """Sample converter class."""
196
197
    @pytest.fixture
198
    def path(tmpdir):
199
        tmpdir.chdir()
200
        return "mock/path"
201
202
    def it_accepts_one_argument(path):
203
204
        @decorators.attr(var1=MockConverter1)
205
        @decorators.sync(path)
206
        class SampleDecoratedSingle:
207
            """Class using single `attr` decorator."""
208
209
        sample = SampleDecoratedSingle()
210
        expect(sample.__mapper__.attrs) == {'var1': MockConverter1}
211
212
    def it_rejects_zero_arguments():
213
        with expect.raises(ValueError):
214
            decorators.attr()
215
216
    def it_rejects_more_than_one_argument():
217
        with expect.raises(ValueError):
218
            decorators.attr(foo=1, bar=2)
219
220
    def it_can_be_applied_multiple_times(path):
221
222
        @decorators.attr(var1=MockConverter1)
223
        @decorators.attr(var2=MockConverter2)
224
        @decorators.sync(path)
225
        class SampleDecoratedMultiple:
226
            """Class using multiple `attr` decorators."""
227
228
        sample = SampleDecoratedMultiple()
229
        expect(sample.__mapper__.attrs) == {'var1': MockConverter1,
230
                                            'var2': MockConverter2}
231
232
    def it_can_be_applied_before_sync(path):
233
234
        @decorators.attr(var2=MockConverter2)
235
        @decorators.sync(path, attrs={'var1': MockConverter1})
236
        class SampleDecoratedCombo:
237
            """Class using `attr` decorator and providing a mapping."""
238
239
        sample = SampleDecoratedCombo()
240
        expect(sample.__mapper__.attrs) == {'var1': MockConverter1,
241
                                            'var2': MockConverter2}
242
243
    def it_can_be_applied_after_sync(path):
244
245
        @decorators.sync(path, attrs={'var1': MockConverter1})
246
        @decorators.attr(var2=MockConverter2)
247
        class SampleDecoratedBackwards:
248
            """Class using `attr` decorator after `sync` decorator."""
249
250
        sample = SampleDecoratedBackwards()
251
        expect(sample.__mapper__.attrs) == {'var1': MockConverter1,
252
                                            'var2': MockConverter2}
253