AccountMigration   B
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 262
Duplicated Lines 0 %

Test Coverage

Coverage 12.12%

Importance

Changes 0
Metric Value
wmc 40
dl 0
loc 262
ccs 16
cts 132
cp 0.1212
rs 8.2608
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
B create_plex_basic_credential() 0 33 5
A create_server_account() 0 8 2
A create_trakt_basic_credential() 0 17 3
A run() 0 17 2
A create_rules() 0 4 1
C refresh_account() 0 31 7
A create_plex_account() 0 9 2
B create_administrator_account() 0 42 4
A create_trakt_account() 0 10 2
B get_token() 0 42 5
A create_trakt_oauth_credential() 0 17 4
A get_trakt_username() 0 9 3

How to fix   Complexity   

Complex Class

Complex classes like AccountMigration 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 1
from plugin.core.environment import Environment
2 1
from plugin.models import (
3
    Account, ClientRule, UserRule,
4
    PlexAccount, PlexBasicCredential,
5
    TraktAccount, TraktBasicCredential, TraktOAuthCredential
6
)
7 1
from plugin.modules.migrations.core.base import Migration
8
9 1
from exception_wrappers.libraries import apsw
10 1
import logging
11 1
import os
12 1
import peewee
13 1
import requests
14
15 1
log = logging.getLogger(__name__)
16
17
18 1
class AccountMigration(Migration):
19 1
    def run(self, token_plex=None):
0 ignored issues
show
Bug introduced by
Arguments number differs from overridden 'run' method
Loading history...
20
        # Ensure server `Account` exists
21
        self.create_server_account()
22
23
        # Ensure administrator `Account` exists
24
        self.create_administrator_account(token_plex=token_plex)
25
26
        # Refresh extra accounts
27
        accounts = Account.select().where(
28
            Account.id > 1,
0 ignored issues
show
Bug introduced by
The Class Account does not seem to have a member named id.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
29
            Account.deleted == False
30
        )
31
32
        for account in accounts:
33
            self.refresh_account(account)
34
35
        return True
36
37 1
    @classmethod
38
    def create_server_account(cls):
39
        try:
40
            Account.get(Account.id == 0)
0 ignored issues
show
Bug introduced by
The Class Account does not seem to have a member named id.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
41
        except Account.DoesNotExist:
0 ignored issues
show
Bug introduced by
The Class Account does not seem to have a member named DoesNotExist.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
42
            Account.create(
43
                id=0,
44
                name=''
45
            )
46
47 1
    @classmethod
48 1
    def create_administrator_account(cls, token_plex=None):
49
        username = cls.get_trakt_username()
50
51
        try:
52
            account = Account.get(Account.id == 1)
0 ignored issues
show
Bug introduced by
The Class Account does not seem to have a member named id.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
53
        except Account.DoesNotExist:
0 ignored issues
show
Bug introduced by
The Class Account does not seem to have a member named DoesNotExist.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
54
            account = Account.create(
55
                id=1,
56
                name=username
57
            )
58
59
            # Create default rules for account
60
            cls.create_rules(account)
61
62
        # Ensure plex account details exist
63
        p_created, p_account = cls.create_plex_account(account)
64
65
        cls.create_plex_basic_credential(p_account, token_plex=token_plex)
66
67
        # Refresh plex account details
68
        try:
69
            p_refreshed = p_account.refresh(force=p_created)
70
        except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
71
            log.warn('Unable to refresh plex account (not authenticated?)', exc_info=True)
72
            p_refreshed = False
73
74
        # Ensure trakt account details exist
75
        t_created, t_account = cls.create_trakt_account(account, username)
76
77
        cls.create_trakt_basic_credential(t_account)
78
        cls.create_trakt_oauth_credential(t_account)
79
80
        # Refresh trakt account details
81
        try:
82
            t_refreshed = t_account.refresh(force=t_created)
83
        except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
84
            log.warn('Unable to refresh trakt account (not authenticated?)', exc_info=True)
85
            t_refreshed = False
86
87
        # Refresh account
88
        account.refresh(force=p_refreshed or t_refreshed)
89
90 1
    @classmethod
91
    def refresh_account(cls, account):
92
        if not account or account.deleted:
93
            return
94
95
        log.debug('Refreshing account: %r', account)
96
97
        # Refresh plex account details
98
        p_account = account.plex
99
        p_refreshed = False
100
101
        if p_account:
102
            try:
103
                p_refreshed = p_account.refresh()
104
            except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
105
                log.info('Unable to refresh plex account (not authenticated?)', exc_info=True)
106
                p_refreshed = False
107
108
        # Refresh trakt account details
109
        t_account = account.trakt
110
        t_refreshed = False
111
112
        if t_account:
113
            try:
114
                t_refreshed = t_account.refresh()
115
            except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
116
                log.info('Unable to refresh trakt account (not authenticated?)', exc_info=True)
117
                t_refreshed = False
118
119
        # Refresh account
120
        account.refresh(force=p_refreshed or t_refreshed)
121
122 1
    @classmethod
123
    def create_rules(cls, account):
124
        ClientRule.create(account=account, priority=1)
125
        UserRule.create(account=account, priority=1)
126
127
    #
128
    # Plex
129
    #
130
131 1
    @classmethod
132
    def create_plex_account(cls, account):
133
        try:
134
            return True, PlexAccount.create(
135
                account=account
136
            )
137
        except (apsw.ConstraintError, peewee.IntegrityError):
138
            return False, PlexAccount.get(
139
                account=account
140
            )
141
142 1
    @classmethod
143 1
    def create_plex_basic_credential(cls, plex_account, token_plex=None):
144
        if token_plex is None:
145
            token_plex = cls.get_token()
146
147
        if not token_plex:
148
            log.warn('No plex token available, unable to authenticate plex account')
149
            return False
150
151
        try:
152
            PlexBasicCredential.create(
153
                account=plex_account,
154
155
                token_plex=token_plex
156
            )
157
        except (apsw.ConstraintError, peewee.IntegrityError) as ex:
158
            # Ensure basic credential has a token
159
            rows_updated = PlexBasicCredential.update(
160
                token_plex=token_plex,
161
                token_server=None
162
            ).where(
163
                PlexBasicCredential.account == plex_account,
164
                PlexBasicCredential.token_plex != token_plex
165
            ).execute()
166
167
            # Check if basic credential was updated
168
            if rows_updated:
169
                return True
170
171
            log.debug('Ignoring basic credential update for %r, already exists (%s)', plex_account, ex)
172
            return False
173
174
        return True
175
176
    #
177
    # Trakt
178
    #
179
180 1
    @classmethod
181
    def create_trakt_account(cls, account, username):
182
        try:
183
            return True, TraktAccount.create(
184
                account=account,
185
                username=username
186
            )
187
        except (apsw.ConstraintError, peewee.IntegrityError):
188
            return False, TraktAccount.get(
189
                account=account
190
            )
191
192 1
    @classmethod
193
    def create_trakt_basic_credential(cls, trakt_account):
194
        if not Environment.dict['trakt.token']:
195
            return False
196
197
        try:
198
            TraktBasicCredential.create(
199
                account=trakt_account,
200
                password=Environment.get_pref('password'),
201
202
                token=Environment.dict['trakt.token']
203
            )
204
        except (apsw.ConstraintError, peewee.IntegrityError) as ex:
205
            log.debug('Ignoring basic credential update for %r, already exists (%s)', trakt_account, ex)
206
            return False
207
208
        return True
209
210 1
    @classmethod
211
    def create_trakt_oauth_credential(cls, trakt_account):
212
        if not Environment.dict['trakt.pin.code'] or not Environment.dict['trakt.pin.authorization']:
213
            return False
214
215
        try:
216
            TraktOAuthCredential.create(
217
                account=trakt_account,
218
                code=Environment.dict['trakt.pin.code'],
219
220
                **Environment.dict['trakt.pin.authorization']
221
            )
222
        except (apsw.ConstraintError, peewee.IntegrityError) as ex:
223
            log.debug('Ignoring oauth credential update for %r, already exists (%s)', trakt_account, ex)
224
            return False
225
226
        return True
227
228 1
    @classmethod
229
    def get_trakt_username(cls):
230
        if Environment.get_pref('username'):
231
            return Environment.get_pref('username')
232
233
        if Environment.dict['trakt.username']:
234
            return Environment.dict['trakt.username']
235
236
        return None
237
238 1
    @classmethod
239 1
    def get_token(cls, request_headers=None):
240
        # Environment token
241
        env_token = os.environ.get('PLEXTOKEN')
242
243
        if env_token:
244
            log.info('Plex Token: environment')
245
            return env_token
246
247
        # Check if anonymous access is available
248
        server = requests.get('http://localhost:32400')
249
250
        if server.status_code == 200:
251
            log.info('Plex Token: anonymous')
252
            return 'anonymous'
253
254
        # No token available
255
        if request_headers is None:
256
            log.error('Plex Token: not available')
257
            return None
258
259
        # Try retrieve token from request
260
        req_token = request_headers.get('X-Plex-Token')
261
262
        if req_token:
263
            log.info('Plex Token: request')
264
            return req_token
265
266
        # No token available in request
267
        data = {
268
            'Client': {
269
                'User-Agent': request_headers.get('User-Agent'),
270
                'X-Plex-Product': request_headers.get('X-Plex-Product'),
271
            },
272
            'Headers': request_headers.keys()
273
        }
274
275
        log.debug('Request details: %r', data)
276
        log.error('Plex Token: not available', extra={
277
            'data': data
278
        })
279
        return None
280