wechatpy.fields   C
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 298
Duplicated Lines 0 %

Test Coverage

Coverage 96.43%

Importance

Changes 0
Metric Value
eloc 204
dl 0
loc 298
rs 6.96
c 0
b 0
f 0
ccs 162
cts 168
cp 0.9643
wmc 53

31 Methods

Rating   Name   Duplication   Size   Complexity  
A StringField.to_xml() 0 4 1
A BaseField.__repr__() 0 9 2
A BaseField.__init__() 0 3 1
A VoiceField.from_xml() 0 3 1
A MusicField.from_xml() 0 12 5
A Base64EncodeField.__base64_encode() 0 2 1
A StringField.from_xml() 0 3 1
A HardwareField.to_xml() 0 10 1
A ArticlesField.to_xml() 0 27 2
A ImageField.from_xml() 0 3 1
A DateTimeField.from_xml() 0 3 1
A BaseField.from_xml() 0 3 1
A FloatField.to_xml() 0 4 2
A FloatField.from_xml() 0 3 1
A ImageField.to_xml() 0 6 1
A DateTimeField.to_xml() 0 5 1
A VoiceField.to_xml() 0 6 1
A ArticlesField.from_xml() 0 8 1
B FieldDescriptor.__get__() 0 13 7
A BaseField.to_xml() 0 2 1
A VideoField.to_xml() 0 13 3
A Base64DecodeField.__base64_decode() 0 2 1
A BaseField.add_to_class() 0 4 1
A MusicField.to_xml() 0 19 5
A IntegerField.to_xml() 0 4 2
A FieldDescriptor.__init__() 0 3 1
A VideoField.from_xml() 0 8 3
A DateTimeField.__converter() 0 3 1
A FieldDescriptor.__set__() 0 2 1
A IntegerField.from_xml() 0 3 1
A StringField.__to_text() 0 2 1

How to fix   Complexity   

Complexity

Complex classes like wechatpy.fields 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
# -*- coding: utf-8 -*-
2 10
"""
3
    wechatpy.fields
4
    ~~~~~~~~~~~~~~~~
5
6
    This module defines some useful field types for parse WeChat messages
7
8
    :copyright: (c) 2014 by messense.
9
    :license: MIT, see LICENSE for more details.
10
"""
11 10
from __future__ import absolute_import, unicode_literals
12 10
import time
13 10
from datetime import datetime
14 10
import base64
15 10
import copy
16
17 10
import six
18
19 10
from wechatpy.utils import to_text, to_binary, ObjectDict, timezone
20
21
22 10
default_timezone = timezone('Asia/Shanghai')
23
24
25 10
class FieldDescriptor(object):
26
27 10
    def __init__(self, field):
28 10
        self.field = field
29 10
        self.attr_name = field.name
30
31 10
    def __get__(self, instance, instance_type=None):
32 10
        if instance is not None:
33 10
            value = instance._data.get(self.attr_name)
34 10
            if value is None:
35 10
                value = copy.deepcopy(self.field.default)
36 10
                instance._data[self.attr_name] = value
37 10
            if isinstance(value, dict):
38 10
                value = ObjectDict(value)
39 10
            if value and not isinstance(value, (dict, list, tuple)) and \
40
                    six.callable(self.field.converter):
41 10
                value = self.field.converter(value)
42 10
            return value
43
        return self.field
44
45 10
    def __set__(self, instance, value):
46 10
        instance._data[self.attr_name] = value
47
48
49 10
class BaseField(object):
50 10
    converter = None
51
52 10
    def __init__(self, name, default=None):
53 10
        self.name = name
54 10
        self.default = default
55
56 10
    def to_xml(self, value):
57
        raise NotImplementedError()
58
59 10
    @classmethod
60
    def from_xml(cls, value):
61
        raise NotImplementedError()
62
63
    def __repr__(self):
64
        _repr = '{klass}({name})'.format(
65
            klass=self.__class__.__name__,
66
            name=repr(self.name)
67
        )
68
        if six.PY2:
69
            return to_binary(_repr)
70
        else:
71
            return to_text(_repr)
72
73 10
    def add_to_class(self, klass, name):
74 10
        self.klass = klass
75 10
        klass._fields[name] = self
76 10
        setattr(klass, name, FieldDescriptor(self))
77
78
79 10
class StringField(BaseField):
80
81 10
    def __to_text(self, value):
82 10
        return to_text(value)
83
84 10
    converter = __to_text
85
86 10
    def to_xml(self, value):
87 10
        value = self.converter(value)
88 10
        tpl = '<{name}><![CDATA[{value}]]></{name}>'
89 10
        return tpl.format(name=self.name, value=value)
90
91 10
    @classmethod
92
    def from_xml(cls, value):
93 10
        return value
94
95
96 10
class IntegerField(BaseField):
97 10
    converter = int
98
99 10
    def to_xml(self, value):
100 10
        value = self.converter(value) if value is not None else self.default
101 10
        tpl = '<{name}>{value}</{name}>'
102 10
        return tpl.format(name=self.name, value=value)
103
104 10
    @classmethod
105
    def from_xml(cls, value):
106 10
        return cls.converter(value)
107
108
109 10
class DateTimeField(BaseField):
110 10
    def __converter(self, value):
111 10
        v = int(value)
112 10
        return datetime.fromtimestamp(v, tz=default_timezone)
113 10
    converter = __converter
114
115 10
    def to_xml(self, value):
116 10
        value = time.mktime(datetime.timetuple(value))
117 10
        value = int(value)
118 10
        tpl = '<{name}>{value}</{name}>'
119 10
        return tpl.format(name=self.name, value=value)
120
121 10
    @classmethod
122
    def from_xml(cls, value):
123
        return cls.converter(None, value)
124
125
126 10
class FloatField(BaseField):
127 10
    converter = float
128
129 10
    def to_xml(self, value):
130 10
        value = self.converter(value) if value is not None else self.default
131 10
        tpl = '<{name}>{value}</{name}>'
132 10
        return tpl.format(name=self.name, value=value)
133
134 10
    @classmethod
135
    def from_xml(cls, value):
136
        return cls.converter(value)
137
138
139 10
class ImageField(StringField):
140
141 10
    def to_xml(self, value):
142 10
        value = self.converter(value)
143 10
        tpl = """<Image>
144
        <MediaId><![CDATA[{value}]]></MediaId>
145
        </Image>"""
146 10
        return tpl.format(value=value)
147
148 10
    @classmethod
149
    def from_xml(cls, value):
150 10
        return value["MediaId"]
151
152
153 10
class VoiceField(StringField):
154
155 10
    def to_xml(self, value):
156 10
        value = self.converter(value)
157 10
        tpl = """<Voice>
158
        <MediaId><![CDATA[{value}]]></MediaId>
159
        </Voice>"""
160 10
        return tpl.format(value=value)
161
162 10
    @classmethod
163
    def from_xml(cls, value):
164 10
        return value["MediaId"]
165
166
167 10
class VideoField(StringField):
168
169 10
    def to_xml(self, value):
170 10
        kwargs = dict(media_id=self.converter(value['media_id']))
171 10
        content = '<MediaId><![CDATA[{media_id}]]></MediaId>'
172 10
        if 'title' in value:
173 10
            kwargs['title'] = self.converter(value['title'])
174 10
            content += '<Title><![CDATA[{title}]]></Title>'
175 10
        if 'description' in value:
176 10
            kwargs['description'] = self.converter(value['description'])
177 10
            content += '<Description><![CDATA[{description}]]></Description>'
178 10
        tpl = """<Video>
179
        {content}
180
        </Video>""".format(content=content)
181 10
        return tpl.format(**kwargs)
182
183 10
    @classmethod
184
    def from_xml(cls, value):
185 10
        rv = dict(media_id=value['MediaId'])
186 10
        if 'Title' in value:
187 10
            rv["title"] = value['Title']
188 10
        if 'Description' in value:
189 10
            rv['description'] = value['Description']
190 10
        return rv
191
192
193 10
class MusicField(StringField):
194
195 10
    def to_xml(self, value):
196 10
        kwargs = dict(thumb_media_id=self.converter(value['thumb_media_id']))
197 10
        content = '<ThumbMediaId><![CDATA[{thumb_media_id}]]></ThumbMediaId>'
198 10
        if 'title' in value:
199 10
            kwargs['title'] = self.converter(value['title'])
200 10
            content += '<Title><![CDATA[{title}]]></Title>'
201 10
        if 'description' in value:
202 10
            kwargs['description'] = self.converter(value['description'])
203 10
            content += '<Description><![CDATA[{description}]]></Description>'
204 10
        if 'music_url' in value:
205 10
            kwargs['music_url'] = self.converter(value['music_url'])
206 10
            content += '<MusicUrl><![CDATA[{music_url}]]></MusicUrl>'
207 10
        if 'hq_music_url' in value:
208 10
            kwargs['hq_music_url'] = self.converter(value['hq_music_url'])
209 10
            content += '<HQMusicUrl><![CDATA[{hq_music_url}]]></HQMusicUrl>'
210 10
        tpl = """<Music>
211
        {content}
212
        </Music>""".format(content=content)
213 10
        return tpl.format(**kwargs)
214
215 10
    @classmethod
216
    def from_xml(cls, value):
217 10
        rv = dict(thumb_media_id=value['ThumbMediaId'])
218 10
        if 'Title' in value:
219 10
            rv['title'] = value['Title']
220 10
        if 'Description' in value:
221 10
            rv['description'] = value['Description']
222 10
        if 'MusicUrl' in value:
223 10
            rv['music_url'] = value['MusicUrl']
224 10
        if 'HQMusicUrl' in value:
225 10
            rv['hq_music_url'] = value['HQMusicUrl']
226 10
        return rv
227
228
229 10
class ArticlesField(StringField):
230
231 10
    def to_xml(self, articles):
232 10
        article_count = len(articles)
233 10
        items = []
234 10
        for article in articles:
235 10
            title = self.converter(article.get('title', ''))
236 10
            description = self.converter(article.get('description', ''))
237 10
            image = self.converter(article.get('image', ''))
238 10
            url = self.converter(article.get('url', ''))
239 10
            item_tpl = """<item>
240
            <Title><![CDATA[{title}]]></Title>
241
            <Description><![CDATA[{description}]]></Description>
242
            <PicUrl><![CDATA[{image}]]></PicUrl>
243
            <Url><![CDATA[{url}]]></Url>
244
            </item>"""
245 10
            item = item_tpl.format(
246
                title=title,
247
                description=description,
248
                image=image,
249
                url=url
250
            )
251 10
            items.append(item)
252 10
        items_str = '\n'.join(items)
253 10
        tpl = """<ArticleCount>{article_count}</ArticleCount>
254
        <Articles>{items}</Articles>"""
255 10
        return tpl.format(
256
            article_count=article_count,
257
            items=items_str
258
        )
259
260 10
    @classmethod
261
    def from_xml(cls, value):
262 10
        return [dict(
263
            title=item["Title"],
264
            description=item["Description"],
265
            image=item["PicUrl"],
266
            url=item["Url"]
267
        ) for item in value["item"]]
268
269
270 10
class Base64EncodeField(StringField):
271
272 10
    def __base64_encode(self, text):
273 10
        return to_text(base64.b64encode(to_binary(text)))
274
275 10
    converter = __base64_encode
276
277
278 10
class Base64DecodeField(StringField):
279
280 10
    def __base64_decode(self, text):
281 10
        return to_text(base64.b64decode(to_binary(text)))
282
283 10
    converter = __base64_decode
284
285
286 10
class HardwareField(StringField):
287
288 10
    def to_xml(self, value=None):
289
        value = value or {'view': 'myrank', 'action': 'ranklist'}
290
        tpl = """<{name}>
291
        <MessageView><![CDATA[{view}]]></MessageView>
292
        <MessageAction><![CDATA[{action}]]></MessageAction>
293
        </{name}>"""
294
        return tpl.format(
295
            name=self.name,
296
            view=value.get('view'),
297
            action=value.get('action')
298
        )
299