Passed
Push — beta ( 72a57d...7d0ef0 )
by Dean
03:02
created

Mapper._build_request()   F

Complexity

Conditions 14

Size

Total Lines 67

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 67
ccs 0
cts 36
cp 0
rs 2.6141
cc 14
crap 210

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 Mapper._build_request() 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
from plugin.core.environment import Environment
2
from plugin.core.helpers.variable import try_convert
3
from plugin.modules.core.base import Module
4
5
from oem import OemClient, AbsoluteNumberRequiredError
6
from oem.media.movie import MovieMatch
7
from oem.media.show import EpisodeIdentifier, EpisodeMatch
8
from oem.providers import IncrementalReleaseProvider
9
from oem_storage_codernitydb.main import CodernityDbStorage
10
from plex_metadata import Guid
11
import logging
12
import os
13
14
log = logging.getLogger(__name__)
15
16
17
class Mapper(Module):
18
    __key__ = 'mapper'
19
20
    services = {
21
        'anidb': ['imdb', 'tvdb']  # Try match against imdb database first
22
    }
23
24
    def __init__(self):
25
        self._client = None
26
27
    def start(self):
28
        # Construct oem client
29
        self._client = OemClient(
30
            provider=IncrementalReleaseProvider(
31
                fmt='minimize+msgpack',
32
                storage=CodernityDbStorage(os.path.join(
33
                    Environment.path.plugin_caches,
34
                    'oem'
35
                ))
36
            )
37
        )
38
39
    #
40
    # Movie
41
    #
42
43
    def map_movie(self, guid, movie):
44
        # Ensure guid has been parsed
45
        if type(guid) is str:
46
            guid = Guid.parse(guid)
47
48
        # Try match movie against database
49
        return self.map(guid.service, guid.id)
50
51
    def request_movie(self, guid, movie):
52
        # Try match movie against database
53
        supported, match = self.map_movie(guid, movie)
54
55
        if not match:
56
            return supported, None
57
58
        # Build request for Trakt.tv
59
        return supported, self._build_request(match, movie)
60
61
    #
62
    # Shows
63
    #
64
65
    def map_episode(self, guid, season_num, episode_num):
66
        # Ensure guid has been parsed
67
        if type(guid) is str:
68
            guid = Guid.parse(guid)
69
70
        # Build episode identifier
71
        identifier = EpisodeIdentifier(
72
            season_num=season_num,
73
            episode_num=episode_num
74
        )
75
76
        # Try match episode against database
77
        return self.map(guid.service, guid.id, identifier)
78
79
    def request_episode(self, guid, episode):
80
        # Try match episode against database
81
        supported, match = self.map_episode(guid, episode.season.index, episode.index)
82
83
        if not match:
84
            return supported, None
85
86
        # Build request for Trakt.tv
87
        return supported, self._build_request(match, episode.show, episode)
88
89
    #
90
    # Helper methods
91
    #
92
93
    def map(self, source, key, identifier=None):
94
        if source not in self.services:
95
            return False, None
96
97
        for target, service in self._iter_services(source):
98
            try:
99
                match = service.map(key, identifier)
100
            except AbsoluteNumberRequiredError:
101
                log.info('Unable to retrieve mapping for %r (%s -> %s) - Absolute mappings are not supported yet', key, source, target)
102
                continue
103
            except Exception, ex:
104
                log.warn('Unable to retrieve mapping for %r (%s -> %s) - %s', key, source, target, ex, exc_info=True)
105
                continue
106
107
            if match:
108
                return True, match
109
110
        return True, None
111
112
    def match(self, source, key):
113
        if source not in self.services:
114
            return False, None
115
116
        for target, service in self._iter_services(source):
117
            try:
118
                result = service.get(key)
119
            except Exception, ex:
120
                log.warn('Unable to retrieve item for %r (%s -> %s) - %s', key, source, target, ex, exc_info=True)
121
                continue
122
123
            if result:
124
                return True, result
125
126
        log.warn('Unable to find item for %s: %r' % (source, key), extra={
127
            'event': {
128
                'module': __name__,
129
                'name': 'match.missing_item',
130
                'key': (source, key)
131
            }
132
        })
133
        return True, None
134
135
    def _build_request(self, match, item, episode=None):
136
        if not match:
137
            log.warn('Invalid value provided for "match" parameter')
138
            return None
139
140
        if not item:
141
            log.warn('Invalid value provided for "item" parameter')
142
            return None
143
144
        # Retrieve identifier
145
        service = match.identifiers.keys()[0]
146
        key = try_convert(match.identifiers[service], int, match.identifiers[service])
147
148
        if type(key) not in [int, str]:
149
            log.info('Unsupported key: %r', key)
150
            return None
151
152
        # Determine media type
153
        if isinstance(match, MovieMatch):
154
            media = 'movie'
155
        elif isinstance(match, EpisodeMatch):
156
            media = 'show'
157
        else:
158
            log.warn('Unknown match: %r', match)
159
            return None
160
161
        # Build request
162
        request = {
163
            media: {
164
                'title': item.title,
165
166
                'ids': {
167
                    service: key
168
                }
169
            }
170
        }
171
172
        if item.year:
173
            request[media]['year'] = item.year
174
        elif episode and episode.year:
175
            request[media]['year'] = episode.year
176
        else:
177
            log.warn('Missing "year" parameter on %r', item)
178
179
        # Add episode parameters
180
        if isinstance(match, EpisodeMatch):
181
            if not episode:
182
                log.warn('Missing "episode" parameter')
183
                return None
184
185
            if match.absolute_num is not None:
186
                # TODO support for absolute episode scrobbling
187
                log.info('Absolute mappings are not supported yet')
188
                return None
189
190
            if match.season_num is None or match.episode_num is None:
191
                log.warn('Missing season or episode number in %r', match)
192
                return None
193
194
            request['episode'] = {
195
                'title': episode.title,
196
197
                'season': match.season_num,
198
                'number': match.episode_num
199
            }
200
201
        return request
202
203
    def _iter_services(self, source):
204
        if source not in self.services:
205
            return
206
207
        for target in self.services[source]:
208
            try:
209
                service = self._client[source].to(target)
210
            except KeyError:
211
                log.warn('Unable to find service: %s -> %s', source, target)
212
                continue
213
            except Exception, ex:
214
                log.warn('Unable to retrieve service: %s -> %s - %s', source, target, ex, exc_info=True)
215
                continue
216
217
            yield target, service
218