Passed
Pull Request — master (#74)
by macartur
01:37
created

NAppsAPI   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 214
Duplicated Lines 0 %

Importance

Changes 7
Bugs 1 Features 0
Metric Value
c 7
b 1
f 0
dl 0
loc 214
rs 8.3999
wmc 46

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