copy_from_local()   A
last analyzed

Complexity

Conditions 5

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 24.2584

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 19
ccs 1
cts 12
cp 0.0833
rs 9.3333
c 0
b 0
f 0
cc 5
nop 3
crap 24.2584
1 1
import logging
2 1
import os
3 1
import shutil
4 1
from re import compile as _compile
5 1
from uuid import uuid4 as random_uuid
6
7 1
from tornado import gen
8
9 1
from tabpy.tabpy_server.app.app_parameters import SettingsParameters
10 1
from tabpy.tabpy_server.handlers import BaseHandler
11 1
from tabpy.tabpy_server.handlers.base_handler import STAGING_THREAD
12 1
from tabpy.tabpy_server.management.state import get_query_object_path
13 1
from tabpy.tabpy_server.psws.callbacks import on_state_change
14
15
16 1
def copy_from_local(localpath, remotepath, is_dir=False):
17
    if is_dir:
18
        if not os.path.exists(remotepath):
19
            # remote folder does not exist
20
            shutil.copytree(localpath, remotepath)
21
        else:
22
            # remote folder exists, copy each file
23
            src_files = os.listdir(localpath)
24
            for file_name in src_files:
25
                full_file_name = os.path.join(localpath, file_name)
26
                if os.path.isdir(full_file_name):
27
                    # copy folder recursively
28
                    full_remote_path = os.path.join(remotepath, file_name)
29
                    shutil.copytree(full_file_name, full_remote_path)
30
                else:
31
                    # copy each file
32
                    shutil.copy(full_file_name, remotepath)
33
    else:
34
        shutil.copy(localpath, remotepath)
35
36
37 1
class ManagementHandler(BaseHandler):
38 1
    def initialize(self, app):
39 1
        super(ManagementHandler, self).initialize(app)
40 1
        self.port = self.settings[SettingsParameters.Port]
41
42 1
    def _get_protocol(self):
43
        return "http://"
44
45 1
    @gen.coroutine
46 1
    def _add_or_update_endpoint(self, action, name, version, request_data):
47
        """
48
        Add or update an endpoint
49
        """
50
        self.logger.log(logging.DEBUG, f"Adding/updating model {name}...")
51
52
        if not isinstance(name, str):
53
            msg = "Endpoint name must be a string"
54
            self.logger.log(logging.CRITICAL, msg)
55
            raise TypeError(msg)
56
57
        name_checker = _compile(r"^[a-zA-Z0-9-_\s]+$")
58
        if not name_checker.match(name):
59
            raise gen.Return(
60
                "endpoint name can only contain: a-z, A-Z, 0-9,"
61
                " underscore, hyphens and spaces."
62
            )
63
64
        if self.settings.get("add_or_updating_endpoint"):
65
            msg = (
66
                "Another endpoint update is already in progress"
67
                ", please wait a while and try again"
68
            )
69
            self.logger.log(logging.CRITICAL, msg)
70
            raise RuntimeError(msg)
71
72
        self.settings["add_or_updating_endpoint"] = random_uuid()
73
        try:
74
            docstring = None
75
            if "docstring" in request_data:
76
                docstring = str(
77
                    bytes(request_data["docstring"], "utf-8").decode("unicode_escape")
78
                )
79
80
            description = request_data.get("description", None)
81
            endpoint_type = request_data.get("type", None)
82
            methods = request_data.get("methods", [])
83
            dependencies = request_data.get("dependencies", None)
84
            target = request_data.get("target", None)
85
            schema = request_data.get("schema", None)
86
            src_path = request_data.get("src_path", None)
87
            is_public = request_data.get("is_public", None)
88
            target_path = get_query_object_path(
89
                self.settings[SettingsParameters.StateFilePath], name, version
90
            )
91
92
            path_checker = _compile(r"^[\\\:a-zA-Z0-9-_~\s/\.\(\)]+$")
93
            # copy from staging
94
            if src_path:
95
                if not isinstance(src_path, str):
96
                    raise gen.Return("src_path must be a string.")
97
                if not path_checker.match(src_path):
98
                    raise gen.Return(f"Invalid source path for endpoint {name}")
99
100
                yield self._copy_po_future(src_path, target_path)
101
            elif endpoint_type != "alias":
102
                raise gen.Return("src_path is required to add/update an endpoint.")
103
            else:
104
                # alias special logic:
105
                if not target:
106
                    raise gen.Return("Target is required for alias endpoint.")
107
                dependencies = [target]
108
109
            # update local config
110
            try:
111
                if action == "add":
112
                    self.tabpy_state.add_endpoint(
113
                        name=name,
114
                        description=description,
115
                        docstring=docstring,
116
                        endpoint_type=endpoint_type,
117
                        methods=methods,
118
                        dependencies=dependencies,
119
                        target=target,
120
                        schema=schema,
121
                        is_public=is_public,
122
                    )
123
                else:
124
                    self.tabpy_state.update_endpoint(
125
                        name=name,
126
                        description=description,
127
                        docstring=docstring,
128
                        endpoint_type=endpoint_type,
129
                        methods=methods,
130
                        dependencies=dependencies,
131
                        target=target,
132
                        schema=schema,
133
                        version=version,
134
                        is_public=is_public,
135
                    )
136
137
            except Exception as e:
138
                raise gen.Return(f"Error when changing TabPy state: {e}")
139
140
            on_state_change(
141
                self.settings, self.tabpy_state, self.python_service, self.logger
142
            )
143
144
        finally:
145
            self.settings["add_or_updating_endpoint"] = None
146
147 1
    @gen.coroutine
148 1
    def _copy_po_future(self, src_path, target_path):
149
        future = STAGING_THREAD.submit(
150
            copy_from_local, src_path, target_path, is_dir=True
151
        )
152
        ret = yield future
153
        raise gen.Return(ret)
154