Repository: dlubal-software/RFEM_Python_Client (language: python)

Issue:
REQUEST: Installing Without Downgrading Setuptools 
**Is your feature request related to a problem? Please describe.**
The RFEM Python client depends on suds_requests, which also depends on 'use_2to3' from setuptools for it's installation. This was removed from setuptools in 58 - https://setuptools.pypa.io/en/latest/history.html#v58-0-0
[github/suds_requests#12](https://github.com/armooo/suds_requests/issues/12)

**Describe the solution you'd like**
In the short term, downgrading setuptools and installing is sufficient, but a cleaner long-term solution would be preferred. The desired solution would ideally work with the latest versions of setup-tools, and work "out of the box" without any tinkering with environments. I understand this might be tricky however, many of these packages are out of Dlubal's control..

REQUEST: Installing Without Downgrading Setuptools 
**Is your feature request related to a problem? Please describe.**
The RFEM Python client depends on suds_requests, which also depends on 'use_2to3' from setuptools for it's installation. This was removed from setuptools in 58 - https://setuptools.pypa.io/en/latest/history.html#v58-0-0
[github/suds_requests#12](https://github.com/armooo/suds_requests/issues/12)

**Describe the solution you'd like**
In the short term, downgrading setuptools and installing is sufficient, but a cleaner long-term solution would be preferred. The desired solution would ideally work with the latest versions of setup-tools, and work "out of the box" without any tinkering with environments. I understand this might be tricky however, many of these packages are out of Dlubal's control..



Discussion / hints:
Hi @cslotboom,
thank you for your interest in our Python Client. If you inspect the module you will find that the author uses `setuptools` only for non-functional file `setup.py`. We can omit it by transferring the function code to our Client. This module is under the MIT license.
Hi @cslotboom,
thank you for your interest in our Python Client. If you inspect the module you will find that the author uses `setuptools` only for non-functional file `setup.py`. We can omit it by transferring the function code to our Client. This module is under the MIT license.

Fix patch:
diff --git a/RFEM/dependencies.py b/RFEM/dependencies.py
new file mode 100644
index 00000000..759439af
--- /dev/null
+++ b/RFEM/dependencies.py
@@ -0,0 +1,36 @@
+import sys
+
+# Check Python version
+if not sys.version_info[0] == 3:
+    print('Must be using Python 3!')
+    input('Press Enter to exit...')
+    sys.exit()
+
+# Check all dependencies
+try:
+    import requests
+    import six
+    import xmltodict
+    import pytest
+    import mock
+    import suds.transport
+    import suds.client # suds-py3
+except:
+    print('One of the required modules is not installed in your Python env.')
+    instSUDS = input('\nDo you want to install all dependencies and check all their versions (y/n)? ')
+    instSUDS = instSUDS.lower()
+    if instSUDS == 'y':
+        # Subprocess will be opened in cmd and closed automaticaly after installation.
+        # Prevents invoking pip by an old script wrapper (https://github.com/pypa/pip/issues/5599)
+        import subprocess
+        try:
+            subprocess.call('python -m pip install --upgrade pip')
+            subprocess.call('python -m pip install requests six suds-py3 xmltodict pytest mock --user')
+        except:
+            print('WARNING: Installation of modules failed!')
+            print('Please use command "python -m pip install requests six suds-py3 xmltodict pytest mock --user" in your Command Prompt.')
+            input('Press Enter to exit...')
+            sys.exit()
+    else:
+        input('Press Enter to exit...')
+        sys.exit()
\ No newline at end of file
diff --git a/RFEM/initModel.py b/RFEM/initModel.py
index 5c07825b..9165ec58 100644
--- a/RFEM/initModel.py
+++ b/RFEM/initModel.py
@@ -1,102 +1,9 @@
 import sys
+import RFEM.dependencies
+import requests
+from suds.client import Client
 from RFEM.enums import ObjectTypes, ModelType, AddOn
-
-# Import SUDS module
-try:
-    sys.version_info[0] == 3
-except:
-    print('Must be using Python 3!')
-    input('Press Enter to exit...')
-    sys.exit()
-
-try:
-    from suds.client import Client
-except:
-    print('SUDS library is not installed in your Python env.')
-    instSUDS = input('Do you want to install it (y/n)? ')
-    instSUDS = instSUDS.lower()
-    if instSUDS == 'y':
-        # Subprocess will be opened in cmd and closed automaticaly after installation.
-        # Prevents invoking pip by an old script wrapper (https://github.com/pypa/pip/issues/5599)
-        import subprocess
-        try:
-            subprocess.call('python -m pip install --upgrade pip')
-            subprocess.call('python -m pip install suds-py3 --user')
-            from suds.client import Client
-        except:
-            print('WARNING: Installation of SUDS library failed!')
-            print('Please use command "pip install suds-py3 --user" in your Command Prompt.')
-            input('Press Enter to exit...')
-            sys.exit()
-    else:
-        input('Press Enter to exit...')
-        sys.exit()
-
-try:
-    import requests
-except:
-    print('requests library is not installed in your Python env.')
-    instSUDS = input('Do you want to install it (y/n)? ')
-    instSUDS = instSUDS.lower()
-    if instSUDS == 'y':
-        # Subprocess will be opened in cmd and closed automaticaly after installation.
-        # Prevents invoking pip by an old script wrapper (https://github.com/pypa/pip/issues/5599)
-        import subprocess
-        try:
-            subprocess.call('python -m pip install requests --user')
-            import requests
-        except:
-            print('WARNING: Installation of requests library failed!')
-            print('Please use command "pip install requests --user" in your Command Prompt.')
-            input('Press Enter to exit...')
-            sys.exit()
-    else:
-        input('Press Enter to exit...')
-        sys.exit()
-
-try:
-    import suds_requests
-except:
-    print('suds_requests library is not installed in your Python env.')
-    instSUDS = input('Do you want to install it (y/n)? ')
-    instSUDS = instSUDS.lower()
-    if instSUDS == 'y':
-        # Subprocess will be opened in cmd and closed automaticaly after installation.
-        # Prevents invoking pip by an old script wrapper (https://github.com/pypa/pip/issues/5599)
-        import subprocess
-        try:
-            subprocess.call('python -m pip install suds_requests --user')
-            import suds_requests
-        except:
-            print('WARNING: Installation of suds_requests library failed!')
-            print('Please use command "pip install suds_requests --user" in your Command Prompt.')
-            input('Press Enter to exit...')
-            sys.exit()
-    else:
-        input('Press Enter to exit...')
-        sys.exit()
-
-try:
-    import xmltodict
-except:
-    print('xmltodict library is not installed in your Python env.')
-    instXML = input('Do you want to install it (y/n)? ')
-    instXML = instXML.lower()
-    if instXML == 'y':
-        # Subprocess will be opened in cmd and closed automaticaly after installation.
-        # Prevents invoking pip by an old script wrapper (https://github.com/pypa/pip/issues/5599)
-        import subprocess
-        try:
-            subprocess.call('python -m pip install xmltodict --user')
-            import xmltodict
-        except:
-            print('WARNING: Installation of xmltodict library failed!')
-            print('Please use command "pip install xmltodict --user" in your Command Prompt.')
-            input('Press Enter to exit...')
-            sys.exit()
-    else:
-        input('Press Enter to exit...')
-        sys.exit()
+from RFEM.suds_requests import RequestsTransport
 
 # Connect to server
 # Check server port range set in "Program Options & Settings"
@@ -128,7 +35,7 @@
 session = requests.Session()
 adapter = requests.adapters.HTTPAdapter(pool_connections=1, pool_maxsize=1)
 session.mount('http://', adapter)
-trans = suds_requests.RequestsTransport(session)
+trans = RequestsTransport(session)
 
 class Model():
     clientModel = None
diff --git a/RFEM/suds_requests.py b/RFEM/suds_requests.py
new file mode 100644
index 00000000..47bc7464
--- /dev/null
+++ b/RFEM/suds_requests.py
@@ -0,0 +1,73 @@
+#######################################################################
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# Curtesy of https://github.com/armooo/suds_requests
+# author: Jason Michalski
+# email: armooo@armooo.net
+# License: MIT
+#######################################################################
+
+import functools
+import requests
+import suds.transport as transport
+import traceback
+from six import BytesIO
+
+def handle_errors(f):
+    @functools.wraps(f)
+    def wrapper(*args, **kwargs):
+        try:
+            return f(*args, **kwargs)
+        except requests.HTTPError as e:
+            buf = BytesIO(e.response.content)
+            raise transport.TransportError(
+                'Error in requests\n' + traceback.format_exc(),
+                e.response.status_code,
+                buf,
+            )
+        except requests.RequestException:
+            buf = BytesIO(traceback.format_exc().encode('utf-8'))
+            raise transport.TransportError(
+                'Error in requests\n' + traceback.format_exc(),
+                000,
+                buf,
+            )
+    return wrapper
+
+
+class RequestsTransport(transport.Transport):
+    def __init__(self, session=None):
+        transport.Transport.__init__(self)
+        self._session = session or requests.Session()
+
+    @handle_errors
+    def open(self, request):
+        resp = self._session.get(request.url)
+        resp.raise_for_status()
+        return BytesIO(resp.content)
+
+    @handle_errors
+    def send(self, request):
+        resp = self._session.post(
+            request.url,
+            data=request.message,
+            headers=request.headers,
+        )
+        if resp.headers.get('content-type') not in ('text/xml',
+                                                    'application/soap+xml'):
+            resp.raise_for_status()
+        return transport.Reply(
+            resp.status_code,
+            resp.headers,
+            resp.content,
+        )


Test patch:
diff --git a/UnitTests/test_AddOns.py b/UnitTests/test_AddOns.py
index 65902dd7..d3825f2c 100644
--- a/UnitTests/test_AddOns.py
+++ b/UnitTests/test_AddOns.py
@@ -1,5 +1,5 @@
 import sys
-import pytest
+import RFEM.dependencies
 import os
 PROJECT_ROOT = os.path.abspath(os.path.join(
                   os.path.dirname(__file__),
diff --git a/UnitTests/test_suds_requests.py b/UnitTests/test_suds_requests.py
new file mode 100644
index 00000000..39707292
--- /dev/null
+++ b/UnitTests/test_suds_requests.py
@@ -0,0 +1,109 @@
+#######################################################################
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# Curtesy of https://github.com/armooo/suds_requests
+# author: Jason Michalski
+# email: armooo@armooo.net
+# License: MIT
+#######################################################################
+
+import os
+import sys
+import mock
+import pytest
+import requests
+import suds.transport
+PROJECT_ROOT = os.path.abspath(os.path.join(
+                  os.path.dirname(__file__),
+                  os.pardir)
+)
+sys.path.append(PROJECT_ROOT)
+import RFEM.suds_requests as suds_requests
+
+
+def test_no_errors():
+    m = mock.Mock(__name__='m')
+    f = suds_requests.handle_errors(m)
+    assert f() == m.return_value
+
+
+def test_HTTPError():
+    resp = mock.Mock(status_code=404,
+                     content=b'File not found')
+    m = mock.Mock(
+        side_effect=requests.HTTPError(response=resp),
+        __name__='m',
+    )
+    f = suds_requests.handle_errors(m)
+    with pytest.raises(suds.transport.TransportError) as excinfo:
+        f()
+    assert excinfo.value.httpcode == 404
+    assert excinfo.value.fp.read() == b'File not found'
+
+
+def test_RequestException():
+    m = mock.Mock(
+        side_effect=requests.RequestException(),
+        __name__='m',
+    )
+    f = suds_requests.handle_errors(m)
+    with pytest.raises(suds.transport.TransportError) as excinfo:
+        f()
+    assert excinfo.value.httpcode == 000
+    assert excinfo.value.fp.read().startswith(b'Traceback')
+
+def test_open():
+    session = mock.Mock()
+    session.get.return_value.content = b'abc123'
+    transport = suds_requests.RequestsTransport(session)
+    request = suds.transport.Request('http://url')
+
+    response = transport.open(request)
+
+    assert response.read() == b'abc123'
+
+
+def test_send():
+    session = mock.Mock()
+    session.post.return_value.content = b'abc123'
+    session.post.return_value.headers = {
+        1: 'A',
+        2: 'B',
+    }
+    session.post.return_value.status_code = 200
+    transport = suds_requests.RequestsTransport(session)
+    request = suds.transport.Request(
+        'http://url',
+        'I AM SOAP! WHY AM I NOT CLEAN!!!',
+    )
+    request.headers = {
+        'A': 1,
+        'B': 2,
+    }
+
+    reply = transport.send(request)
+
+    session.post.assert_called_with(
+        'http://url',
+        data='I AM SOAP! WHY AM I NOT CLEAN!!!',
+        headers={
+            'A': 1,
+            'B': 2,
+        },
+    )
+    assert reply.code == 200
+    assert reply.headers == {
+        1: 'A',
+        2: 'B',
+    }
+    assert reply.message == b'abc123'
\ No newline at end of file