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

LibrariesManager.test()   F

Complexity

Conditions 13

Size

Total Lines 67

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 17.1345

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 0
loc 67
ccs 22
cts 31
cp 0.7097
rs 2.5943
c 2
b 0
f 0
cc 13
crap 17.1345

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 LibrariesManager.test() 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.configuration import Configuration
0 ignored issues
show
Bug introduced by
The name configuration does not seem to exist in module plugin.core.
Loading history...
Configuration introduced by
Unable to import 'plugin.core.configuration' (invalid syntax (<string>, line 38))

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
2 1
from plugin.core.environment import Environment
0 ignored issues
show
Bug introduced by
The name environment does not seem to exist in module plugin.core.
Loading history...
Configuration introduced by
Unable to import 'plugin.core.environment' (invalid syntax (<string>, line 101))

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
3 1
from plugin.core.helpers.variable import merge
4 1
from plugin.core.libraries.cache import CacheManager
5 1
from plugin.core.libraries.constants import CONTENTS_PATH, NATIVE_DIRECTORIES, UNICODE_MAP
6 1
from plugin.core.libraries.helpers import PathHelper, StorageHelper, SystemHelper
7 1
from plugin.core.libraries.tests import LIBRARY_TESTS
8 1
from plugin.core.logger.handlers.error_reporter import RAVEN
0 ignored issues
show
Bug introduced by
The name error_reporter does not seem to exist in module plugin.core.logger.handlers.
Loading history...
Configuration introduced by
Unable to import 'plugin.core.logger.handlers.error_reporter' (invalid syntax (<string>, line 134))

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
9
10 1
import logging
11 1
import os
12 1
import platform
13 1
import sys
14
15 1
log = logging.getLogger(__name__)
16
17
18 1
class LibrariesManager(object):
19 1
    @classmethod
20 1
    def setup(cls, cache=False):
21
        """Setup native library directories
22
23
        :param cache: Enable native library caching
24
        :type cache: bool
25
        """
26
27
        # Use `cache` value from advanced configuration
28 1
        cache = Configuration.advanced['libraries'].get_boolean('cache', cache)
29
30
        # Retrieve libraries path (and cache libraries, if enabled)
31 1
        libraries_path = cls._libraries_path(cache)
32
33 1
        if not libraries_path:
34
            return
35
36 1
        log.info('Using native libraries at %r', StorageHelper.to_relative_path(libraries_path))
37
38
        # Remove current native library directories from `sys.path`
39 1
        cls.reset()
40
41
        # Insert platform specific library paths
42 1
        cls._insert_paths(libraries_path)
43
44
        # Display library paths in logfile
45 1
        for path in sys.path:
46 1
            path = os.path.abspath(path)
47
48 1
            if StorageHelper.is_framework_path(path):
49
                continue
50
51 1
            log.info('[PATH] %s', StorageHelper.to_relative_path(path))
52
53 1
    @staticmethod
54
    def test():
55
        """Test native libraries to ensure they can be correctly loaded"""
56 1
        log.info('Testing native library support...')
57
58
        # Retrieve library directories
59 1
        search_paths = []
60
61 1
        for path in sys.path:
62 1
            path_lower = path.lower()
63
64 1
            if 'trakttv.bundle' not in path_lower and 'com.plexapp.plugins.trakttv' not in path_lower:
65 1
                continue
66
67 1
            search_paths.append(path)
68
69
        # Run library tests
70 1
        metadata = {}
71
72 1
        for test in LIBRARY_TESTS:
73
            # Run tests
74 1
            result = test.run(search_paths)
0 ignored issues
show
Bug introduced by
The Class Apsw does not seem to have a member named run.

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...
Bug introduced by
The Class LList does not seem to have a member named run.

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...
Bug introduced by
The Class OpenSSL does not seem to have a member named run.

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...
Bug introduced by
The Class Cryptography does not seem to have a member named run.

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...
75
76 1
            if not result.get('success'):
77
                log_func = logging.warn if test.optional else logging.error
78
79
                # Write message to logfile
80
                if 'traceback' in result:
81
                    log_func('%s: unavailable - %s\n%%s' % (test.name, result.get('message')), result['traceback'])
82
                else:
83
                    log_func('%s: unavailable - %s' % (test.name, result.get('message')), exc_info=result.get('exc_info'))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (122/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
84
85
                if not test.optional:
86
                    return
87
88
                continue
89
90
            # Test successful
91 1
            t_metadata = result.get('metadata') or {}
92 1
            t_versions = t_metadata.get('versions')
93
94 1
            if t_versions:
95 1
                expanded = len(t_versions) > 1 or (
96
                    t_versions and t_versions.keys()[0] != test.name
97
                )
98
99 1
                if expanded:
100 1
                    log.info('%s: available (%s)', test.name, ', '.join([
101
                        '%s: %s' % (key, value)
102
                        for key, value in t_versions.items()
103
                    ]))
104
                else:
105
                    key = t_versions.keys()[0]
106
107
                    log.info('%s: available (%s)', test.name, t_versions[key])
108
            else:
109 1
                log.info('%s: available', test.name)
110
111
            # Merge result into `metadata`
112 1
            merge(metadata, t_metadata, recursive=True)
113
114
        # Include versions in error reports
115 1
        versions = metadata.get('versions') or {}
116
117 1
        RAVEN.tags.update(dict([
118
            ('%s.version' % key, value)
119
            for key, value in versions.items()
120
        ]))
121
122 1
    @classmethod
123
    def reset(cls):
124
        """Remove all the native library directives from `sys.path`"""
125
126 1
        for path in sys.path:
127 1
            path = os.path.abspath(path)
128
129 1
            if not path.lower().startswith(CONTENTS_PATH.lower()):
130 1
                continue
131
132
            # Convert to relative path
133 1
            path_rel = os.path.relpath(path, CONTENTS_PATH)
134
135
            # Take the first two fragments
136 1
            path_rel = os.path.sep.join(path_rel.split(os.path.sep)[:2])
137
138
            # Convert to unix-style separators (/)
139 1
            path_rel = path_rel.replace('\\', '/')
140
141
            # Ignore non-native library directories
142 1
            if path_rel not in NATIVE_DIRECTORIES:
143 1
                continue
144
145
            # Remove from `sys.path`
146
            PathHelper.remove(path)
147
148 1
    @classmethod
149 1
    def _libraries_path(cls, cache=False):
150
        """Retrieve the native libraries base directory (and cache the libraries if enabled)
151
152
        :param cache: Enable native library caching
153
        :type cache: bool
154
        """
155
156
        # Use specified libraries path (from "advanced.ini')
157 1
        libraries_path = Configuration.advanced['libraries'].get('libraries_path')
158
159 1
        if libraries_path and os.path.exists(libraries_path):
160
            log.info('Using libraries at %r', StorageHelper.to_relative_path(libraries_path))
161
            RAVEN.tags.update({'libraries.source': 'custom'})
162
            return libraries_path
163
164
        # Use system libraries (if bundled libraries have been disabled in "advanced.ini")
165 1
        if not Configuration.advanced['libraries'].get_boolean('bundled', True):
166
            log.info('Bundled libraries have been disabled, using system libraries')
167
            RAVEN.tags.update({'libraries.source': 'system'})
168
            return None
169
170
        # Cache libraries (if enabled)
171 1
        if cache:
172
            RAVEN.tags.update({'libraries.source': 'cache'})
173
            return cls._cache_libraries()
174
175 1
        RAVEN.tags.update({'libraries.source': 'bundle'})
176 1
        return Environment.path.libraries
177
178 1
    @classmethod
179
    def _cache_libraries(cls):
180
        cache_path = Configuration.advanced['libraries'].get('cache_path')
181
182
        # Try cache libraries to `cache_path`
183
        libraries_path = CacheManager.sync(cache_path)
184
185
        if not libraries_path:
186
            log.info('Unable to cache libraries, using bundled libraries directly')
187
            return Environment.path.libraries
188
189
        log.info('Cached libraries to %r', StorageHelper.to_relative_path(libraries_path))
190
        return libraries_path
191
192 1
    @classmethod
193
    def _insert_paths(cls, libraries_path):
194
        # Display platform details
195 1
        p_bits, _ = platform.architecture()
196 1
        p_machine = platform.machine()
197
198 1
        log.debug('Bits: %r, Machine: %r', p_bits, p_machine)
199
200
        # Retrieve system details
201 1
        system = SystemHelper.name()
202 1
        architecture = SystemHelper.architecture()
203
204 1
        if not architecture:
205
            return
206
207 1
        log.debug('System: %r, Architecture: %r', system, architecture)
208
209
        # Insert architecture specific libraries
210 1
        architectures = [architecture]
211
212 1
        if architecture == 'i686':
213
            # Fallback to i386
214
            architectures.append('i386')
215
216 1
        for arch in architectures + ['universal']:
217 1
            cls._insert_architecture_paths(libraries_path, system, arch)
218
219 1
    @classmethod
220
    def _insert_architecture_paths(cls, libraries_path, system, architecture):
221 1
        architecture_path = os.path.join(libraries_path, system, architecture)
222
223 1
        if not os.path.exists(architecture_path):
224 1
            return
225
226
        # Architecture libraries
227 1
        PathHelper.insert(libraries_path, system, architecture)
228
229
        # System libraries
230 1
        if system == 'Windows':
231
            # Windows libraries (VC++ specific)
232
            cls._insert_paths_windows(libraries_path, system, architecture)
233
        else:
234
            # Darwin/FreeBSD/Linux libraries
235 1
            cls._insert_paths_unix(libraries_path, system, architecture)
236
237 1
    @staticmethod
238
    def _insert_paths_unix(libraries_path, system, architecture):
239
        # UCS specific libraries
240 1
        ucs = UNICODE_MAP.get(sys.maxunicode)
241 1
        log.debug('UCS: %r', ucs)
242
243 1
        if ucs:
244 1
            PathHelper.insert(libraries_path, system, architecture, ucs)
245
246
        # CPU specific libraries
247 1
        cpu_type = SystemHelper.cpu_type()
248 1
        page_size = SystemHelper.page_size()
249
250 1
        log.debug('CPU Type: %r', cpu_type)
251 1
        log.debug('Page Size: %r', page_size)
252
253 1
        if cpu_type:
254
            PathHelper.insert(libraries_path, system, architecture, cpu_type)
255
256
            if page_size:
257
                PathHelper.insert(libraries_path, system, architecture, '%s_%s' % (cpu_type, page_size))
258
259
        # UCS + CPU specific libraries
260 1
        if cpu_type and ucs:
261
            PathHelper.insert(libraries_path, system, architecture, cpu_type, ucs)
262
263
            if page_size:
264
                PathHelper.insert(libraries_path, system, architecture, '%s_%s' % (cpu_type, page_size), ucs)
265
266
        # Include attributes in error reports
267 1
        RAVEN.tags.update({
268
            'cpu.type': cpu_type,
269
            'memory.page_size': page_size,
270
            'python.ucs': ucs
271
        })
272
273 1
    @staticmethod
274
    def _insert_paths_windows(libraries_path, system, architecture):
275
        vcr = SystemHelper.vcr_version() or 'vc12'  # Assume "vc12" if call fails
276
        ucs = UNICODE_MAP.get(sys.maxunicode)
277
278
        log.debug('VCR: %r, UCS: %r', vcr, ucs)
279
280
        # VC++ libraries
281
        PathHelper.insert(libraries_path, system, architecture, vcr)
282
283
        # UCS libraries
284
        if ucs:
285
            PathHelper.insert(libraries_path, system, architecture, vcr, ucs)
286
287
        # Include attributes in error reports
288
        RAVEN.tags.update({
289
            'python.ucs': ucs,
290
            'vcr.version': vcr
291
        })
292