Repository: Vantiq/vantiq-extension-sources (language: python)

Issue:
pythonConnector: Add ability to enhance connections via keyword args
Depends on #528 


Discussion / hints:

Fix patch:
diff --git a/pythonExecSource/README.md b/pythonExecSource/README.md
index b15da8c9..56364c48 100644
--- a/pythonExecSource/README.md
+++ b/pythonExecSource/README.md
@@ -43,6 +43,25 @@ authToken = _cDWBfZLNO9FkXd-twjwKnVIBZSGwns35nF4nQFV_ps=
 sources = pythonSource
 ```
 
+As outlined in the vantiqconnectorsdk, there is also the option of providing extra keyword arguments that will be used
+to make connections to Vantiq.  These will be used both for the vantiqconnectorsdk connection as well any "regular"
+Vantiq connections used.  These are use, for example, for reading documents containing Python
+code.
+
+To make use of this capability, include the `connectKWArgs` property in your config file.
+
+A special case is use for disabling SSL verification (often used in development environments). To do this, use the
+argument `disableSslVerification`.  This sets up both the connector's websocket connection and the auxiliary 
+Vantiq connection to skip SSL verification.  Doing so, internally, requires some Python code not directly
+representable in JSON.  The following example shows how this is done.
+
+``
+targetServer = https://dev.vantiq.com
+authToken = _cDWBfZLNO9FkXd-twjwKnVIBZSGwns35nF4nQFV_ps=
+sources = pythonSource
+connectKWArgs={ "disableSslVerification": true }
+```
+
 For users who may not want to write the `authToken` property to a file because of its sensitive nature, set the environment variable `CONNECTOR_AUTH_TOKEN` to its value. If the `authToken` is specified in the `server.config` document, that value will take precedence.
 Otherwise, if the `authToken` is not set in the configuration file, the system will retrieve whatever value is provided in the environment variable.
 
diff --git a/pythonExecSource/build.gradle b/pythonExecSource/build.gradle
index 51627dc7..4bd8ba8e 100644
--- a/pythonExecSource/build.gradle
+++ b/pythonExecSource/build.gradle
@@ -12,11 +12,11 @@ ext {
     }
 
     if (!project.rootProject.hasProperty('pyExecSrcVersion')) {
-        pyExecSrcVersion = '1.2.12'
+        pyExecSrcVersion = '1.3.0'
     }
     // If these change, update requirements-conn.in as well -- as that's used to generate the requirements.txt file
-    vantiqPythonSdkVersion = '1.3.5'
-    vantiqConnectorSdkVersion = '1.2.12'
+    vantiqPythonSdkVersion = '1.4.0'
+    vantiqConnectorSdkVersion = '1.3.3'
 }
 
 python {
diff --git a/pythonExecSource/requirements-conn.in b/pythonExecSource/requirements-conn.in
index dba04b97..a6191e38 100644
--- a/pythonExecSource/requirements-conn.in
+++ b/pythonExecSource/requirements-conn.in
@@ -1,5 +1,5 @@
 websockets>=12.0
 aiohttp>=3.10.2
-vantiqconnectorsdk>=1.2.12
-vantiqsdk>=1.3.5
+vantiqconnectorsdk>=1.3.3
+vantiqsdk>=1.4.0
 requests>=2.32.3
diff --git a/pythonExecSource/requirements.txt b/pythonExecSource/requirements.txt
index 5596253a..eddf7f5e 100644
--- a/pythonExecSource/requirements.txt
+++ b/pythonExecSource/requirements.txt
@@ -140,9 +140,9 @@ urllib3==2.2.2
     # via
     #   requests
     #   twine
-vantiqconnectorsdk==1.2.12
+vantiqconnectorsdk==1.3.3
     # via -r requirements-conn.in
-vantiqsdk==1.3.5
+vantiqsdk==1.4.0
     # via -r requirements-conn.in
 websockets==12.0
     # via
diff --git a/pythonExecSource/src/main/python/pyExecConnector.py b/pythonExecSource/src/main/python/pyExecConnector.py
index 1d91548f..91d4df91 100644
--- a/pythonExecSource/src/main/python/pyExecConnector.py
+++ b/pythonExecSource/src/main/python/pyExecConnector.py
@@ -398,7 +398,10 @@ async def fetch_script(self, script_to_fetch: str):
                 url = config[VantiqConnector.TARGET_SERVER]
                 # We may need to sanitize the URL here.
                 url = self.sanitize_url(url)
-                self.vantiq_client = Vantiq(url)
+                if isinstance(self.connection.connect_kw_args, dict) and self.connection.connect_kw_args:
+                    self.vantiq_client = Vantiq(url, "1", **self.connection.connect_kw_args)
+                else:
+                    self.vantiq_client = Vantiq(url)
                 await self.vantiq_client.set_access_token(config[VantiqConnector.AUTH_TOKEN])
         except VantiqException as ve:
             if self.vantiq_client is not None:


Test patch:
diff --git a/pythonExecSource/src/test/python/test_PyExecConnectorLive.py b/pythonExecSource/src/test/python/test_PyExecConnectorLive.py
index e8727cde..9563d0f5 100644
--- a/pythonExecSource/src/test/python/test_PyExecConnectorLive.py
+++ b/pythonExecSource/src/test/python/test_PyExecConnectorLive.py
@@ -514,11 +514,16 @@ async def do_connector_test_raw_code(self, use_conn_info: bool, qry_count: int,
         await self.do_connector_test(use_conn_info, qry_count, at_once, TEST_SIMPLE_CODE, None, None)
 
     async def do_connector_test_documents(self, use_conn_info: bool, qry_count: int, at_once: int):
-        await self.do_connector_test(use_conn_info, qry_count, at_once, TEST_SIMPLE_DOC, None, None)
+        await self.do_connector_test(use_conn_info, qry_count, at_once, TEST_SIMPLE_DOC, None,
+                                     None)
+
+    async def do_connector_test_documents_connect_args(self, use_conn_info: bool, qry_count: int, at_once: int):
+        await self.do_connector_test(use_conn_info, qry_count, at_once, TEST_SIMPLE_DOC, None,
+                                     None, '{ "disableSslVerification": true }')
 
     async def do_connector_test(self, use_conn_info: bool, qry_count: int, at_once: int,
                                 proc_to_run: str, expected_error: Union[dict, None],
-                                expected_result: Union[dict, None]):
+                                expected_result: Union[dict, None], connect_args: str = None):
         # Construct a server config file to use...
         assert at_once == 0 or qry_count % at_once == 0  # Test config failure -- at once must be multiples
         cf = f'''
@@ -526,8 +531,17 @@ async def do_connector_test(self, use_conn_info: bool, qry_count: int, at_once:
         authToken={_access_token}
         sources={PY_EXEC_SOURCE_NAME}
         '''
-
-        self.vantiq_connection = Vantiq(_server_url)
+        if connect_args is not None:
+            cf = cf + f'''
+            {VantiqConnector.FAIL_ON_CONNECTION_ERROR}=true
+            connectKWArgs={connect_args}
+            '''
+
+        if connect_args:
+            # Here, we'll assume that we're disabling SSL, so we will, too.
+            self.vantiq_connection = Vantiq(_server_url, "1", ssl=False)
+        else:
+            self.vantiq_connection = Vantiq(_server_url)
         await self.vantiq_connection.set_access_token(_access_token)
         await self.setup_test_env(self.vantiq_connection, use_conn_info)
 
@@ -570,7 +584,7 @@ def check_test_conditions():
             pytest.skip('Need access to Vantiq server.')
 
     @pytest.mark.asyncio
-    @pytest.mark.timeout(10)
+    @pytest.mark.timeout(25)
     async def test_syntax_error(self):
         self.check_test_conditions()
         await self.do_connector_test(False, 1, 1, TEST_SYNTAX_ERROR,
@@ -578,7 +592,7 @@ async def test_syntax_error(self):
                                       VantiqConnector.ERROR_TEMPLATE: 'SyntaxError'}, None)
 
     @pytest.mark.asyncio
-    @pytest.mark.timeout(10)
+    @pytest.mark.timeout(25)
     async def test_import_error(self):
         self.check_test_conditions()
         # Import errors (also subclass ModuleNotFound errors) are found at execution time, generally, rather
@@ -596,7 +610,7 @@ async def test_timeout_error(self):
         await self.do_connector_test(False, 1, 1, TEST_TIMEOUT, None, {'i_am_alive': 'Because I think'})
 
     @pytest.mark.asyncio
-    @pytest.mark.timeout(10)
+    @pytest.mark.timeout(25)
     async def test_indent_error(self):
         self.check_test_conditions()
         await self.do_connector_test(False, 1, 1, TEST_INDENT_ERROR,
@@ -604,37 +618,45 @@ async def test_indent_error(self):
                                       VantiqConnector.ERROR_TEMPLATE: 'IndentationError'}, None)
 
     @pytest.mark.asyncio
-    @pytest.mark.timeout(10)
+    @pytest.mark.timeout(25)
     async def test_simple_query(self):
         self.check_test_conditions()
         await self.do_connector_test_raw_code(True, QUERY_COUNT, 0)
 
     @pytest.mark.asyncio
-    @pytest.mark.timeout(10)
+    @pytest.mark.timeout(25)
     async def test_simple_query_noci(self):
         self.check_test_conditions()
         await self.do_connector_test_raw_code(False, QUERY_COUNT, 0)
 
     @pytest.mark.asyncio
-    @pytest.mark.timeout(10)
+    @pytest.mark.timeout(25)
     async def test_simple_query_multiple(self):
         self.check_test_conditions()
         await self.do_connector_test_raw_code(True, QUERY_COUNT, 5)
 
     @pytest.mark.asyncio
-    @pytest.mark.timeout(10)
+    @pytest.mark.timeout(25)
     async def test_simple_query_noci_multiple(self):
         self.check_test_conditions()
         await self.do_connector_test_raw_code(False, QUERY_COUNT, 5)
 
     @pytest.mark.asyncio
-    @pytest.mark.timeout(10)
+    @pytest.mark.timeout(25)
     async def test_doc_query(self):
         self.check_test_conditions()
         await self.do_connector_test_documents(True, QUERY_COUNT, 0)
 
     @pytest.mark.asyncio
-    @pytest.mark.timeout(10)
+    @pytest.mark.timeout(25)
+    async def test_doc_query_connect_args(self):
+        self.check_test_conditions()
+        if _server_url.startswith('ws:') or _server_url.startswith('http:'):
+            pytest.skip('This test requires an SSL-enabled server (https or wss).')
+        await self.do_connector_test_documents_connect_args(True, QUERY_COUNT, 0)
+
+    @pytest.mark.asyncio
+    @pytest.mark.timeout(25)
     async def test_doc_query_multiple(self):
         self.check_test_conditions()
         await self.do_connector_test_documents(True, QUERY_COUNT, 5)