Test Failed
Push — master ( 3f9872...314d6d )
by Beraldo
45s
created

NAppsAPI   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Importance

Changes 7
Bugs 1 Features 0
Metric Value
c 7
b 1
f 0
dl 0
loc 226
rs 8.4864
wmc 48

15 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 4 1
A enable_napp() 0 10 3
A disable_napp() 0 7 2
A enable() 0 14 3
A disable() 0 14 3
A upload() 0 10 2
A uninstall() 0 17 4
A _print_napps() 0 14 4
A delete() 0 16 4
C print_napps() 0 24 7
A install_napps() 0 19 4
A search() 0 15 2
A install() 0 4 1
A install_napp() 0 19 4
A list() 0 17 4

How to fix   Complexity   

Complex Class

Complex classes like NAppsAPI 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
"""Translate cli commands to non-cli code."""
2
import json
3
import logging
4
import os
5
import re
6
from urllib.error import HTTPError
7
8
import requests
9
from kytos.utils.napps import NAppsManager
10
11
log = logging.getLogger(__name__)
12
13
14
class NAppsAPI:
15
    """An API for the command-line interface.
16
17
    Use the config file only for required options. Static methods are called
18
    by the parser and they instantiate an object of this class to fulfill the
19
    request.
20
    """
21
22
    @classmethod
23
    def disable(cls, args):
24
        """Disable subcommand."""
25
        mgr = NAppsManager()
26
27
        if args['all']:
28
            napps = mgr.get_enabled()
29
        else:
30
            napps = args['<napp>']
31
32
        for napp in napps:
33
            mgr.set_napp(*napp)
34
            log.info('NApp %s:', mgr.napp_id)
35
            cls.disable_napp(mgr)
36
37
    @staticmethod
38
    def disable_napp(mgr):
39
        """Disable a NApp."""
40
        if mgr.is_enabled():
41
            log.info('  Disabling...')
42
            mgr.disable()
43
        log.info('  Disabled.')
44
45
    @classmethod
46
    def enable(cls, args):
47
        """Enable subcommand."""
48
        mgr = NAppsManager()
49
50
        if args['all']:
51
            napps = mgr.get_disabled()
52
        else:
53
            napps = args['<napp>']
54
55
        for napp in napps:
56
            mgr.set_napp(*napp)
57
            log.info('NApp %s:', mgr.napp_id)
58
            cls.enable_napp(mgr)
59
60
    @staticmethod
61
    def enable_napp(mgr):
62
        """Install one NApp using NAppManager object."""
63
        try:
64
            if not mgr.is_enabled():
65
                log.info('  Enabling...')
66
                mgr.enable()
67
            log.info('  Enabled.')
68
        except (FileNotFoundError, PermissionError) as e:
69
            log.error('  %s', e)
70
71
    @classmethod
72
    def create(cls, args):
73
        """Bootstrap a basic NApp structure on the current folder."""
74
        NAppsManager.create_napp()
75
76
    @classmethod
77
    def upload(cls, args):
78
        """Upload the NApp to the NApps server.
79
80
        Create the NApp package and upload it to the NApp server.
81
        """
82
        try:
83
            NAppsManager().upload()
84
        except FileNotFoundError:
85
            log.error("Couldn't find kytos.json in current directory.")
86
87
    @classmethod
88
    def uninstall(cls, args):
89
        """Uninstall and delete NApps.
90
91
        For local installations, do not delete code outside install_path and
92
        enabled_path.
93
        """
94
        mgr = NAppsManager()
95
        for napp in args['<napp>']:
96
            mgr.set_napp(*napp)
97
            log.info('NApp %s:', mgr.napp_id)
98
            if mgr.is_installed():
99
                if mgr.is_enabled():
100
                    cls.disable_napp(mgr)
101
                log.info('  Uninstalling...')
102
                mgr.uninstall()
103
            log.info('  Uninstalled.')
104
105
    @classmethod
106
    def install(cls, args):
107
        """Install local or remote NApps."""
108
        cls.install_napps(args['<napp>'])
109
110
    @classmethod
111
    def install_napps(cls, napps):
112
        """Install local or remote NApps.
113
114
        This method is recursive, it will install each napps and your
115
        dependencies.
116
        """
117
        mgr = NAppsManager()
118
        for napp in napps:
119
            mgr.set_napp(*napp)
120
            log.info('NApp %s:', mgr.napp_id)
121
            if not mgr.is_installed():
122
                cls.install_napp(mgr)
123
            else:
124
                log.info('  Installed.')
125
            napp_dependencies = mgr.dependencies()
126
            if napp_dependencies:
127
                log.info('Installing Dependencies:')
128
                cls.install_napps(napp_dependencies)
129
130
    @classmethod
131
    def install_napp(cls, mgr):
132
        """Install a NApp."""
133
        try:
134
            log.info('  Searching local NApp...')
135
            mgr.install_local()
136
            log.info('  Found and installed.')
137
            cls.enable_napp(mgr)
138
        except FileNotFoundError:
139
            log.info('  Not found. Downloading from NApps Server...')
140
            try:
141
                mgr.install_remote()
142
                log.info('  Downloaded and installed.')
143
                cls.enable_napp(mgr)
144
            except HTTPError as e:
145
                if e.code == 404:
146
                    log.error('  NApp not found.')
147
                else:
148
                    log.error('  NApps Server error: %s', e)
149
150
    @classmethod
151
    def search(cls, args):
152
        """Search for NApps in NApps server matching a pattern."""
153
        safe_shell_pat = re.escape(args['<pattern>']).replace(r'\*', '.*')
154
        pat_str = '.*{}.*'.format(safe_shell_pat)
155
        pattern = re.compile(pat_str, re.IGNORECASE)
156
        remote_json = NAppsManager.search(pattern)
157
        remote = set()
158
        for napp in remote_json:
159
            # WARNING: This will be changed in future versions, when 'author'
160
            # will be removed.
161
            username = napp.get('username', napp.get('author'))
162
            remote.add(((username, napp.get('name')), napp.get('description')))
163
164
        cls._print_napps(remote)
165
166
    @classmethod
167
    def _print_napps(cls, napp_list):
168
        """Format the NApp list to be printed."""
169
        mgr = NAppsManager()
170
        enabled = mgr.get_enabled()
171
        installed = mgr.get_installed()
172
        napps = []
173
        for napp, desc in sorted(napp_list):
174
            status = 'i' if napp in installed else '-'
175
            status += 'e' if napp in enabled else '-'
176
            status = '[{}]'.format(status)
177
            name = '{}/{}'.format(*napp)
178
            napps.append((status, name, desc))
179
        cls.print_napps(napps)
180
181
    @classmethod
182
    def list(cls, args):
183
        """List all installed NApps and inform whether they are enabled."""
184
        mgr = NAppsManager()
185
186
        # Add status
187
        napps = [napp + ('[ie]',) for napp in mgr.get_enabled()]
188
        napps += [napp + ('[i-]',) for napp in mgr.get_disabled()]
189
190
        # Sort, add description and reorder coloumns
191
        napps.sort()
192
        napps_ordered = []
193
        for user, name, status in napps:
194
            napps_ordered.append((status, '{}/{}'.format(user, name),
195
                                  mgr.get_description(user, name)))
196
197
        cls.print_napps(napps_ordered)
198
199
    @staticmethod
200
    def print_napps(napps):
201
        """Print status, name and description."""
202
        if not napps:
203
            print('No NApps found.')
204
            return
205
206
        stat_w = 6  # We already know the size of Status col
207
        name_w = max(len(n[1]) for n in napps)
208
        desc_w = max(len(n[2]) for n in napps)
209
        term_w = os.popen('stty size', 'r').read().split()[1]
210
        remaining = int(term_w) - stat_w - name_w - 6
211
        desc_w = min(desc_w, remaining)
212
        widths = (stat_w, name_w, desc_w)
213
214
        header = '\n{:^%d} | {:^%d} | {:^%d}' % widths
215
        row = '{:^%d} | {:<%d} | {:<%d}' % widths
216
        print(header.format('Status', 'NApp ID', 'Description'))
217
        print('=+='.join('=' * w for w in widths))
218
        for user, name, desc in napps:
219
            desc = (desc[:desc_w-3] + '...') if len(desc) > desc_w else desc
220
            print(row.format(user, name, desc))
221
222
        print('\nStatus: (i)nstalled, (e)nabled\n')
223
224
    @staticmethod
225
    def delete(args):
226
        """Delete NApps from server."""
227
        mgr = NAppsManager()
228
        for napp in args['<napp>']:
229
            mgr.set_napp(*napp)
230
            log.info('Deleting NApp %s from server...', mgr.napp_id)
231
            try:
232
                mgr.delete()
233
                log.info('  Deleted.')
234
            except requests.HTTPError as e:
235
                if e.response.status_code == 405:
236
                    log.error('Delete Napp is not allowed yet.')
237
                else:
238
                    msg = json.loads(e.response.content)
239
                    log.error('  Server error: %s - ', msg['error'])
240