Repository: mindsdb/mindsdb

Issue:
Installing mssql dependency error
We already have few users reporting the same issue on Windows machines but without actual error shown in the backend. Finally, we got a log that can help us debug the issue. By looking at the log it looks like we are not able to create datasource because of the missing `mssql` but the user reported that this issue happened when installing `mssql from gui`

https://aws1.discourse-cdn.com/standard11/uploads/mindsdb/original/1X/27f0b3bffdbd34da105f1d7ae38fd13e95d56f63.png

```
[2021-03-30 11:13:05,759] ERROR in app: Exception on /api/datasources/testsource [PUT]
Traceback (most recent call last):
File “C:\Program Files\MindsDB\python\lib\site-packages\flask\app.py”, line 1950, in full_dispatch_request
rv = self.dispatch_request()
File “C:\Program Files\MindsDB\python\lib\site-packages\flask\app.py”, line 1936, in dispatch_request
return self.view_functionsrule.endpoint
File “C:\Program Files\MindsDB\python\lib\site-packages\flask_restx\api.py”, line 375, in wrapper
resp = resource(*args, **kwargs)
File “C:\Program Files\MindsDB\python\lib\site-packages\flask\views.py”, line 89, in view
return self.dispatch_request(*args, **kwargs)
File “C:\Program Files\MindsDB\python\lib\site-packages\flask_restx\resource.py”, line 44, in dispatch_request
resp = meth(*args, **kwargs)
File “C:\Program Files\MindsDB\python\lib\site-packages\flask_restx\marshalling.py”, line 248, in wrapper
resp = f(*args, **kwargs)
File “C:\Program Files\MindsDB\python\lib\site-packages\mindsdb\api\http\namespaces\datasource.py”, line 141, in put
ca.default_store.save_datasource(name, source_type, data)
File “C:\Program Files\MindsDB\python\lib\site-packages\mindsdb\interfaces\datastore\datastore.py”, line 151, in save_datasource
‘class’: dsClass.name,
AttributeError: ‘NoneType’ object has no attribute ‘name’
ERROR:mindsdb.api.http.initialize:Exception on /api/datasources/testsource [PUT]
Traceback (most recent call last):
File “C:\Program Files\MindsDB\python\lib\site-packages\flask\app.py”, line 1950, in full_dispatch_request
rv = self.dispatch_request()
File “C:\Program Files\MindsDB\python\lib\site-packages\flask\app.py”, line 1936, in dispatch_request
return self.view_functionsrule.endpoint
File “C:\Program Files\MindsDB\python\lib\site-packages\flask_restx\api.py”, line 375, in wrapper
resp = resource(*args, **kwargs)
File “C:\Program Files\MindsDB\python\lib\site-packages\flask\views.py”, line 89, in view
return self.dispatch_request(*args, **kwargs)
File “C:\Program Files\MindsDB\python\lib\site-packages\flask_restx\resource.py”, line 44, in dispatch_request
resp = meth(*args, **kwargs)
File “C:\Program Files\MindsDB\python\lib\site-packages\flask_restx\marshalling.py”, line 248, in wrapper
resp = f(*args, **kwargs)
File “C:\Program Files\MindsDB\python\lib\site-packages\mindsdb\api\http\namespaces\datasource.py”, line 141, in put
ca.default_store.save_datasource(name, source_type, data)
File “C:\Program Files\MindsDB\python\lib\site-packages\mindsdb\interfaces\datastore\datastore.py”, line 151, in save_datasource
```


Discussion:

Resolving patch:
diff --git a/mindsdb/api/http/namespaces/config.py b/mindsdb/api/http/namespaces/config.py
--- a/mindsdb/api/http/namespaces/config.py
+++ b/mindsdb/api/http/namespaces/config.py
@@ -1,9 +1,8 @@
 import copy
-import traceback
-import datetime
 from dateutil.parser import parse as parse_datetime
 import subprocess
 import os
+import sys
 
 from flask import request
 from flask_restx import Resource, abort
@@ -14,6 +13,8 @@
 from mindsdb.utilities.functions import get_all_models_meta_data
 from mindsdb.utilities.log import get_logs
 from mindsdb.integrations import CHECKERS
+from mindsdb.api.http.utils import http_error
+
 
 def get_integration(name):
     integrations = ca.config_obj.get('integrations', {})
@@ -35,6 +36,7 @@ def get(self):
         logs = get_logs(min_timestamp, max_timestamp, context, level, log_from, limit)
         return {'data': logs}
 
+
 @ns_conf.route('/integrations')
 @ns_conf.param('name', 'List all database integration')
 class ListIntegration(Resource):
@@ -162,6 +164,7 @@ def get(self, flag):
         else:
             return 'Disabled telemetry', 200
 
+
 @ns_conf.route('/vars')
 class Vars(Resource):
     def get(self):
@@ -176,11 +179,11 @@ def get(self):
 
         cloud = ca.config_obj.get('cloud', False)
 
-
         return {'mongo': mongo, 'telemtry': telemtry, 'cloud': cloud}
 
+
 @ns_conf.param('flag', 'Turn telemtry on or off')
-class ToggleTelemetry(Resource):
+class ToggleTelemetry2(Resource):
     @ns_conf.doc('check')
     def get(self, flag):
         if flag in ["True", "true", "t"]:
@@ -193,7 +196,8 @@ def get(self, flag):
 @ns_conf.param('dependency_list', 'Install dependencies')
 class InstallDependenciesList(Resource):
     def get(self):
-        return {'dependencies':['snowflake','athena','google','s3','lightgbm_gpu','mssql']}
+        return {'dependencies': ['snowflake', 'athena', 'google', 's3', 'lightgbm_gpu', 'mssql']}
+
 
 @ns_conf.route('/install/<dependency>')
 @ns_conf.param('dependency', 'Install dependencies')
@@ -214,14 +218,27 @@ def get(self, dependency):
         else:
             return f'Unkown dependency: {dependency}', 400
 
+        outs = b''
+        errs = b''
         try:
-            sp = subprocess.Popen(['pip3', 'install', *dependency])
-            sp.wait()
-        except Exception:
-            try:
-                sp = subprocess.Popen(['pip', 'install', *dependency])
-                sp.wait()
-            except Exception:
-                return 'Failed to install', 400
+            sp = subprocess.Popen(
+                [sys.executable, '-m', 'pip', 'install', *dependency],
+                stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE
+            )
+            code = sp.wait()
+            outs, errs = sp.communicate(timeout=1)
+        except Exception as e:
+            return http_error(500, 'Failed to install dependency', str(e))
+
+        if code != 0:
+            output = ''
+            if isinstance(outs, bytes) and len(outs) > 0:
+                output = output + 'Output: ' + outs.decode()
+            if isinstance(errs, bytes) and len(errs) > 0:
+                if len(output) > 0:
+                    output = output + '\n'
+                output = output + 'Errors: ' + errs.decode()
+            return http_error(500, 'Failed to install dependency', output)
 
         return 'Installed', 200
diff --git a/mindsdb/api/http/utils.py b/mindsdb/api/http/utils.py
new file mode 100644
--- /dev/null
+++ b/mindsdb/api/http/utils.py
@@ -0,0 +1,24 @@
+import json
+
+from flask import Response
+
+
+def http_error(status_code, title, detail=''):
+    ''' Wrapper for error responce acoording with RFC 7807 (https://tools.ietf.org/html/rfc7807)
+
+        :param status_code: int - http status code for response
+        :param title: str
+        :param detail: str
+
+        :return: flask Response object
+    '''
+    return Response(
+        response=json.dumps({
+            'title': title,
+            'detail': detail
+        }),
+        status=status_code,
+        headers={
+            'Content-Type': 'application/problem+json'
+        }
+    )


Test patch: