From 9bcf44c05ed025ec8e0bc117474c19bb5d8b81a7 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 20 Nov 2024 11:08:22 -0800 Subject: [PATCH 01/21] Add docstring to Endpoint class. --- tabpy/tabpy_tools/rest_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tabpy/tabpy_tools/rest_client.py b/tabpy/tabpy_tools/rest_client.py index c3b7a526..5f1bb0a0 100644 --- a/tabpy/tabpy_tools/rest_client.py +++ b/tabpy/tabpy_tools/rest_client.py @@ -41,6 +41,7 @@ class Endpoint(RESTObject): version = RESTProperty(int) description = RESTProperty(str) dependencies = RESTProperty(list) + docstring = RESTProperty(str) methods = RESTProperty(list) creation_time = RESTProperty(datetime, from_epoch, to_epoch) last_modified_time = RESTProperty(datetime, from_epoch, to_epoch) @@ -64,6 +65,7 @@ def __eq__(self, other): and self.version == other.version and self.description == other.description and self.dependencies == other.dependencies + and self.docstring == other.docstring and self.methods == other.methods and self.evaluator == other.evaluator and self.schema_version == other.schema_version From d8207c0e073025b4f9dcf28da6a57d15cb2eba13 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 20 Nov 2024 11:08:50 -0800 Subject: [PATCH 02/21] Delete staging_path when removing endpoint. --- tabpy/tabpy_server/handlers/endpoint_handler.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tabpy/tabpy_server/handlers/endpoint_handler.py b/tabpy/tabpy_server/handlers/endpoint_handler.py index d7f45f43..64b964cf 100644 --- a/tabpy/tabpy_server/handlers/endpoint_handler.py +++ b/tabpy/tabpy_server/handlers/endpoint_handler.py @@ -115,11 +115,13 @@ def delete(self, name): # delete files if endpoint_info["type"] != "alias": - delete_path = get_query_object_path( + query_path = get_query_object_path( self.settings["state_file_path"], name, None ) + staging_path = query_path.replace("/query_objects/", "/staging/endpoints/") try: - yield self._delete_po_future(delete_path) + yield self._delete_po_future(query_path) + yield self._delete_po_future(staging_path) except Exception as e: self.error_out(400, f"Error while deleting: {e}") self.finish() From ff526b2fd6e1ab969c683e8c8a8a61b82957a223 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 20 Nov 2024 11:15:55 -0800 Subject: [PATCH 03/21] Add remote_server capability. --- tabpy/tabpy_tools/client.py | 83 ++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/tabpy/tabpy_tools/client.py b/tabpy/tabpy_tools/client.py index 684fdc0e..2fb09e54 100644 --- a/tabpy/tabpy_tools/client.py +++ b/tabpy/tabpy_tools/client.py @@ -1,4 +1,5 @@ import copy +import inspect from re import compile import time import requests @@ -49,7 +50,7 @@ def _check_endpoint_name(name): class Client: - def __init__(self, endpoint, query_timeout=1000): + def __init__(self, endpoint, query_timeout=1000, remote_server=False, localhost_endpoint=None): """ Connects to a running server. @@ -63,10 +64,19 @@ def __init__(self, endpoint, query_timeout=1000): query_timeout : float, optional The timeout for query operations. + + remote_server : bool, optional + Whether client is a remote TabPy server. + + localhost_endpoint : str, optional + The localhost endpoint with potentially different protocol and + port compared to the main endpoint parameter. """ _check_hostname(endpoint) self._endpoint = endpoint + self._remote_server = remote_server + self._localhost_endpoint = localhost_endpoint session = requests.session() session.verify = False @@ -232,6 +242,13 @@ def deploy(self, name, obj, description="", schema=None, override=False, is_publ -------- remove, get_endpoints """ + if self._remote_server: + self._remote_deploy( + name, obj, + description=description, schema=schema, override=override, is_public=is_public + ) + return + endpoint = self.get_endpoints().get(name) version = 1 if endpoint: @@ -379,6 +396,7 @@ def _gen_endpoint(self, name, obj, description, version=1, schema=None, is_publi description = obj.__doc__.strip() or "" if isinstance(obj.__doc__, str) else "" endpoint_object = CustomQueryObject(query=obj, description=description,) + docstring = inspect.getdoc(obj) or "-- no docstring found in query function --" return { "name": name, @@ -390,6 +408,7 @@ def _gen_endpoint(self, name, obj, description, version=1, schema=None, is_publi "methods": endpoint_object.get_methods(), "required_files": [], "required_packages": [], + "docstring": docstring, "schema": copy.copy(schema), "is_public": is_public, } @@ -419,6 +438,7 @@ def _wait_for_endpoint_deployment( logger.info( f"Waiting for endpoint {endpoint_name} to deploy to " f"version {version}" ) + time.sleep(interval) start = time.time() while True: ep_status = self.get_status() @@ -447,6 +467,67 @@ def _wait_for_endpoint_deployment( logger.info(f"Sleeping {interval}...") time.sleep(interval) + def _remote_deploy(self, name, obj, description="", schema=None, override=False, is_public=False): + """ + Remotely deploy a Python function using the /evaluate endpoint. Takes the same inputs + as deploy. + """ + remote_script = self._gen_remote_script() + remote_script += f"{inspect.getsource(obj)}\n" + + remote_script += ( + f"client.deploy(" + f"'{name}', {obj.__name__}, '{description}', " + f"override={override}, is_public={is_public}, schema={schema}" + f")" + ) + + self._evaluate_remote_script(remote_script) + + def _gen_remote_script(self): + """ + Generates a remote script for TabPy client connection with credential handling. + + Returns: + str: A Python script to establish a TabPy client connection + """ + remote_script = [ + "from tabpy.tabpy_tools.client import Client", + f"client = Client('{self._localhost_endpoint or self._endpoint}')" + ] + + remote_script.append( + f"client.set_credentials('{auth.username}', '{auth.password}')" + ) if (auth := self._service.service_client.network_wrapper.auth) else None + + return "\n".join(remote_script) + "\n" + + def _evaluate_remote_script(self, remote_script): + """ + Uses TabPy /evaluate endpoint to execute a remote TabPy client script. + + Parameters + ---------- + remote_script : str + The script to execute remotely. + """ + print(f"Remote script:\n{remote_script}") + url = f"{self._endpoint}evaluate" + headers = {"Content-Type": "application/json"} + payload = {"data": {}, "script": remote_script} + + response = requests.post( + url, + headers=headers, + auth=self._service.service_client.network_wrapper.auth, + json=payload + ) + + log_message = response.text.replace('null', 'success') + if "Ad-hoc scripts have been disabled" in log_message: + log_message += "\n[Connecting to this TabPy server with remote_server=True is not allowed.]" + print(f"\n{response.status_code} - {log_message}\n") + def set_credentials(self, username, password): """ Set credentials for all the TabPy client-server communication From ff3092fad6c0d41090032dd2a75d14219ed9ff4e Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 20 Nov 2024 11:26:26 -0800 Subject: [PATCH 04/21] Format client.py --- tabpy/tabpy_tools/client.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/tabpy/tabpy_tools/client.py b/tabpy/tabpy_tools/client.py index 2fb09e54..7b4360f8 100644 --- a/tabpy/tabpy_tools/client.py +++ b/tabpy/tabpy_tools/client.py @@ -50,7 +50,9 @@ def _check_endpoint_name(name): class Client: - def __init__(self, endpoint, query_timeout=1000, remote_server=False, localhost_endpoint=None): + def __init__( + self, endpoint, query_timeout=1000, remote_server=False, localhost_endpoint=None + ): """ Connects to a running server. @@ -64,13 +66,13 @@ def __init__(self, endpoint, query_timeout=1000, remote_server=False, localhost_ query_timeout : float, optional The timeout for query operations. - + remote_server : bool, optional Whether client is a remote TabPy server. - + localhost_endpoint : str, optional - The localhost endpoint with potentially different protocol and - port compared to the main endpoint parameter. + The localhost endpoint with potentially different protocol and + port compared to the main endpoint parameter. """ _check_hostname(endpoint) @@ -244,7 +246,7 @@ def deploy(self, name, obj, description="", schema=None, override=False, is_publ """ if self._remote_server: self._remote_deploy( - name, obj, + name, obj, description=description, schema=schema, override=override, is_public=is_public ) return @@ -467,7 +469,9 @@ def _wait_for_endpoint_deployment( logger.info(f"Sleeping {interval}...") time.sleep(interval) - def _remote_deploy(self, name, obj, description="", schema=None, override=False, is_public=False): + def _remote_deploy( + self, name, obj, description="", schema=None, override=False, is_public=False + ): """ Remotely deploy a Python function using the /evaluate endpoint. Takes the same inputs as deploy. @@ -499,9 +503,9 @@ def _gen_remote_script(self): remote_script.append( f"client.set_credentials('{auth.username}', '{auth.password}')" ) if (auth := self._service.service_client.network_wrapper.auth) else None - + return "\n".join(remote_script) + "\n" - + def _evaluate_remote_script(self, remote_script): """ Uses TabPy /evaluate endpoint to execute a remote TabPy client script. @@ -523,10 +527,10 @@ def _evaluate_remote_script(self, remote_script): json=payload ) - log_message = response.text.replace('null', 'success') - if "Ad-hoc scripts have been disabled" in log_message: - log_message += "\n[Connecting to this TabPy server with remote_server=True is not allowed.]" - print(f"\n{response.status_code} - {log_message}\n") + msg = response.text.replace('null', 'success') + if "Ad-hoc scripts have been disabled" in msg: + msg += "\n[Remote TabPy client not allowed.]" + print(f"\n{response.status_code} - {msg}\n") def set_credentials(self, username, password): """ From 4b82c94c18f8fc03f50036ac2d01792a52395a70 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 20 Nov 2024 13:37:57 -0800 Subject: [PATCH 05/21] Add unit tests. --- tests/unit/tools_tests/test_client.py | 31 ++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/tests/unit/tools_tests/test_client.py b/tests/unit/tools_tests/test_client.py index f5df51ff..fcf41ffe 100644 --- a/tests/unit/tools_tests/test_client.py +++ b/tests/unit/tools_tests/test_client.py @@ -12,17 +12,24 @@ def setUp(self): def test_init(self): client = Client("http://example.com:9004") - self.assertEqual(client._endpoint, "http://example.com:9004") + self.assertEqual(client._remote_server, False) client = Client("http://example.com/", 10.0) - self.assertEqual(client._endpoint, "http://example.com/") client = Client(endpoint="https://example.com/", query_timeout=-10.0) - self.assertEqual(client._endpoint, "https://example.com/") self.assertEqual(client.query_timeout, 0.0) + + client = Client( + "http://example.com:442/", + remote_server=True, + localhost_endpoint="http://localhost:9004/" + ) + self.assertEqual(client._endpoint, "http://example.com:442/") + self.assertEqual(client._remote_server, True) + self.assertEqual(client._localhost_endpoint, "http://localhost:9004/") # valid name tests with self.assertRaises(ValueError): @@ -90,3 +97,21 @@ def test_check_invalid_endpoint_name(self): f"endpoint name {endpoint_name } can only contain: " "a-z, A-Z, 0-9, underscore, hyphens and spaces.", ) + + def test_deploy_with_remote_server(self): + client = Client("http://example.com:9004/", remote_server=True) + mock_evaluate_remote_script = Mock() + client._evaluate_remote_script = mock_evaluate_remote_script + client.deploy('name', lambda: True, 'description') + mock_evaluate_remote_script.assert_called() + + def test_gen_remote_script(self): + client = Client("http://example.com:9004/", remote_server=True) + script = client._gen_remote_script() + self.assertTrue("from tabpy.tabpy_tools.client import Client" in script) + self.assertTrue("client = Client('http://example.com:9004/')" in script) + self.assertFalse("client.set_credentials" in script) + + client.set_credentials("username", "password") + script = client._gen_remote_script() + self.assertTrue("client.set_credentials('username', 'password')" in script) From 13da3e4353bae79e50ebf472aa8cf2d609a6a702 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 20 Nov 2024 13:38:37 -0800 Subject: [PATCH 06/21] Update gitignore. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 614ac726..92d74e4e 100644 --- a/.gitignore +++ b/.gitignore @@ -128,4 +128,4 @@ tabpy/tabpy_server/staging # etc setup.bat *~ -tabpy_log.log.1 +tabpy_log.log.* From 96e1d8c425e829b30ea3125965fce08826e356fb Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 20 Nov 2024 13:39:18 -0800 Subject: [PATCH 07/21] Bump version. --- tabpy/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tabpy/VERSION b/tabpy/VERSION index 3cf561c0..fb2c0766 100755 --- a/tabpy/VERSION +++ b/tabpy/VERSION @@ -1 +1 @@ -2.12.1 +2.13.0 From e90b479fa4b5a2fb8579610dc8cdbe419803ed18 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 20 Nov 2024 13:43:17 -0800 Subject: [PATCH 08/21] Update changelog. --- CHANGELOG | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 2e4a8c43..c6f3492c 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,12 @@ # Changelog +## v2.13.0 + +### Improvements + +- Add support for deploying functions to a remote TabPy server by setting + `remote_server=True` when creating the Client instance. + ## v2.12.0 ### Improvements From 9109735ed4e3c72e212d5a82dcf883c07fc208b8 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 20 Nov 2024 14:07:07 -0800 Subject: [PATCH 09/21] Update docs for remote_server. --- docs/tabpy-tools.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/tabpy-tools.md b/docs/tabpy-tools.md index fdc217e6..c417f81d 100755 --- a/docs/tabpy-tools.md +++ b/docs/tabpy-tools.md @@ -41,6 +41,16 @@ The URL and port are where the Tableau-Python-Server process has been started - more info can be found in the [Starting TabPy](server-install.md#starting-tabpy) section of the documentation. +When connecting to a remote TabPy server, configure the following parameters: + +- Set `remote_server` to `True` to indicate a remote connection +- Set `localhost_endpoint` to the specific localhost address used by the remote server + - **Note:** The protocol and port may differ from the main endpoint + +```python +client = Client('https://example.com:443/', remote_server=True, localhost_endpoint='http://localhost:9004/') +``` + ## Authentication When TabPy is configured with the authentication feature on, client code From 7b0c485fefa80c1b1a4805bfeb099640f04955c0 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 20 Nov 2024 14:29:19 -0800 Subject: [PATCH 10/21] Add delay to deploy_models. --- tests/integration/integ_test_base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/integ_test_base.py b/tests/integration/integ_test_base.py index d055d14e..7a2f51b7 100755 --- a/tests/integration/integ_test_base.py +++ b/tests/integration/integ_test_base.py @@ -308,6 +308,7 @@ def deploy_models(self, username: str, password: str): stdout=outfile, stderr=outfile, ) + time.sleep(2) def _get_process(self): return self.process From 2fd9874d02259bf636e0e3bd4729d8f61b69b455 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Wed, 20 Nov 2024 14:56:11 -0800 Subject: [PATCH 11/21] Fix integration test. --- tests/integration/integ_test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/integ_test_base.py b/tests/integration/integ_test_base.py index 7a2f51b7..8b312e18 100755 --- a/tests/integration/integ_test_base.py +++ b/tests/integration/integ_test_base.py @@ -308,7 +308,7 @@ def deploy_models(self, username: str, password: str): stdout=outfile, stderr=outfile, ) - time.sleep(2) + time.sleep(5) def _get_process(self): return self.process From 10ef0c56ea9b47ccb1765c8e54544e65b9860d60 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Thu, 21 Nov 2024 08:14:33 -0800 Subject: [PATCH 12/21] Fix integ tests. --- tests/integration/integ_test_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/integ_test_base.py b/tests/integration/integ_test_base.py index 8b312e18..9c214390 100755 --- a/tests/integration/integ_test_base.py +++ b/tests/integration/integ_test_base.py @@ -241,7 +241,7 @@ def setUp(self): ) # give the app some time to start up... - time.sleep(5) + time.sleep(10) def tearDown(self): # stop TabPy @@ -254,7 +254,7 @@ def tearDown(self): # after shutting down TabPy and before we start it again # for next test give it some time to terminate. - time.sleep(5) + time.sleep(10) # remove temporary files if self.delete_temp_folder: From 3360ec0f3fab31b360262d1a491803f801a03c2d Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Thu, 21 Nov 2024 08:25:49 -0800 Subject: [PATCH 13/21] Update return status. --- tabpy/tabpy_tools/client.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tabpy/tabpy_tools/client.py b/tabpy/tabpy_tools/client.py index 7b4360f8..e37d2dff 100644 --- a/tabpy/tabpy_tools/client.py +++ b/tabpy/tabpy_tools/client.py @@ -245,11 +245,10 @@ def deploy(self, name, obj, description="", schema=None, override=False, is_publ remove, get_endpoints """ if self._remote_server: - self._remote_deploy( + return self._remote_deploy( name, obj, description=description, schema=schema, override=override, is_public=is_public ) - return endpoint = self.get_endpoints().get(name) version = 1 @@ -486,7 +485,7 @@ def _remote_deploy( f")" ) - self._evaluate_remote_script(remote_script) + return self._evaluate_remote_script(remote_script) def _gen_remote_script(self): """ @@ -515,7 +514,7 @@ def _evaluate_remote_script(self, remote_script): remote_script : str The script to execute remotely. """ - print(f"Remote script:\n{remote_script}") + print(f"Remote script:\n{remote_script}\n") url = f"{self._endpoint}evaluate" headers = {"Content-Type": "application/json"} payload = {"data": {}, "script": remote_script} @@ -527,10 +526,13 @@ def _evaluate_remote_script(self, remote_script): json=payload ) - msg = response.text.replace('null', 'success') + msg = response.text.replace('null', 'Success') if "Ad-hoc scripts have been disabled" in msg: msg += "\n[Remote TabPy client not allowed.]" - print(f"\n{response.status_code} - {msg}\n") + + status_message = (f"{response.status_code} - {msg}\n") + print(status_message) + return status_message def set_credentials(self, username, password): """ From 27f6745c3c9f3748c4ed689f18f4390f9f709e11 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Thu, 21 Nov 2024 08:32:52 -0800 Subject: [PATCH 14/21] Format code. --- tabpy/tabpy_tools/client.py | 2 +- tests/unit/tools_tests/test_client.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tabpy/tabpy_tools/client.py b/tabpy/tabpy_tools/client.py index e37d2dff..8b959119 100644 --- a/tabpy/tabpy_tools/client.py +++ b/tabpy/tabpy_tools/client.py @@ -529,7 +529,7 @@ def _evaluate_remote_script(self, remote_script): msg = response.text.replace('null', 'Success') if "Ad-hoc scripts have been disabled" in msg: msg += "\n[Remote TabPy client not allowed.]" - + status_message = (f"{response.status_code} - {msg}\n") print(status_message) return status_message diff --git a/tests/unit/tools_tests/test_client.py b/tests/unit/tools_tests/test_client.py index fcf41ffe..d18043d0 100644 --- a/tests/unit/tools_tests/test_client.py +++ b/tests/unit/tools_tests/test_client.py @@ -21,10 +21,10 @@ def test_init(self): client = Client(endpoint="https://example.com/", query_timeout=-10.0) self.assertEqual(client._endpoint, "https://example.com/") self.assertEqual(client.query_timeout, 0.0) - + client = Client( - "http://example.com:442/", - remote_server=True, + "http://example.com:442/", + remote_server=True, localhost_endpoint="http://localhost:9004/" ) self.assertEqual(client._endpoint, "http://example.com:442/") @@ -104,14 +104,14 @@ def test_deploy_with_remote_server(self): client._evaluate_remote_script = mock_evaluate_remote_script client.deploy('name', lambda: True, 'description') mock_evaluate_remote_script.assert_called() - + def test_gen_remote_script(self): client = Client("http://example.com:9004/", remote_server=True) script = client._gen_remote_script() self.assertTrue("from tabpy.tabpy_tools.client import Client" in script) self.assertTrue("client = Client('http://example.com:9004/')" in script) self.assertFalse("client.set_credentials" in script) - + client.set_credentials("username", "password") script = client._gen_remote_script() self.assertTrue("client.set_credentials('username', 'password')" in script) From ba6c41e818737bca9fa56727f2effec889b15030 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Thu, 21 Nov 2024 08:33:59 -0800 Subject: [PATCH 15/21] Undo integ test changes. --- tests/integration/integ_test_base.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/integration/integ_test_base.py b/tests/integration/integ_test_base.py index 9c214390..d055d14e 100755 --- a/tests/integration/integ_test_base.py +++ b/tests/integration/integ_test_base.py @@ -241,7 +241,7 @@ def setUp(self): ) # give the app some time to start up... - time.sleep(10) + time.sleep(5) def tearDown(self): # stop TabPy @@ -254,7 +254,7 @@ def tearDown(self): # after shutting down TabPy and before we start it again # for next test give it some time to terminate. - time.sleep(10) + time.sleep(5) # remove temporary files if self.delete_temp_folder: @@ -308,7 +308,6 @@ def deploy_models(self, username: str, password: str): stdout=outfile, stderr=outfile, ) - time.sleep(5) def _get_process(self): return self.process From 741bf171baf6f15c8262fe08d6deecdcb9bbfd82 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Thu, 21 Nov 2024 09:19:40 -0800 Subject: [PATCH 16/21] Improve docstring retreival. --- tabpy/tabpy_tools/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tabpy/tabpy_tools/client.py b/tabpy/tabpy_tools/client.py index 8b959119..9bda2ac6 100644 --- a/tabpy/tabpy_tools/client.py +++ b/tabpy/tabpy_tools/client.py @@ -397,7 +397,6 @@ def _gen_endpoint(self, name, obj, description, version=1, schema=None, is_publi description = obj.__doc__.strip() or "" if isinstance(obj.__doc__, str) else "" endpoint_object = CustomQueryObject(query=obj, description=description,) - docstring = inspect.getdoc(obj) or "-- no docstring found in query function --" return { "name": name, @@ -409,7 +408,7 @@ def _gen_endpoint(self, name, obj, description, version=1, schema=None, is_publi "methods": endpoint_object.get_methods(), "required_files": [], "required_packages": [], - "docstring": docstring, + "docstring": endpoint_object.get_doc_string(), "schema": copy.copy(schema), "is_public": is_public, } From bebeab0751197d7bea8a76242cfeae8948fb0875 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Thu, 21 Nov 2024 10:33:42 -0800 Subject: [PATCH 17/21] check windows 32 bit --- tabpy/tabpy_tools/custom_query_object.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tabpy/tabpy_tools/custom_query_object.py b/tabpy/tabpy_tools/custom_query_object.py index 18a149b8..7651d6bd 100644 --- a/tabpy/tabpy_tools/custom_query_object.py +++ b/tabpy/tabpy_tools/custom_query_object.py @@ -1,4 +1,7 @@ +import inspect import logging +import platform +import sys from .query_object import QueryObject as _QueryObject @@ -71,10 +74,11 @@ def query(self, *args, **kwargs): def get_doc_string(self): """Get doc string from customized query""" - if self.custom_query.__doc__ is not None: - return self.custom_query.__doc__ + default_docstring = "-- no docstring found in query function --" + if platform.system() == "Windows" and sys.maxsize <= 2**32: + return default_docstring else: - return "-- no docstring found in query function --" + return inspect.getdoc(self.custom_query) or default_docstring def get_methods(self): return [self.get_query_method()] From 091847ec79882e9c299f3d89a6b449a6d46ede89 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Thu, 21 Nov 2024 11:12:31 -0800 Subject: [PATCH 18/21] Fix get_doc_string. --- tabpy/tabpy_tools/custom_query_object.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tabpy/tabpy_tools/custom_query_object.py b/tabpy/tabpy_tools/custom_query_object.py index 7651d6bd..280e0c19 100644 --- a/tabpy/tabpy_tools/custom_query_object.py +++ b/tabpy/tabpy_tools/custom_query_object.py @@ -75,10 +75,13 @@ def query(self, *args, **kwargs): def get_doc_string(self): """Get doc string from customized query""" default_docstring = "-- no docstring found in query function --" - if platform.system() == "Windows" and sys.maxsize <= 2**32: + + # TODO: fix docstring parsing on Windows systems + if sys.platform == 'win32': return default_docstring - else: - return inspect.getdoc(self.custom_query) or default_docstring + + ds = getattr(self.custom_query, '__doc__', None) + return ds if ds and isinstance(ds, str) else default_docstring def get_methods(self): return [self.get_query_method()] From 779dd5a4798de3a4e902cc0a0a339439ab54a77b Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Fri, 22 Nov 2024 08:19:18 -0800 Subject: [PATCH 19/21] Remove unused import. --- tabpy/tabpy_tools/custom_query_object.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tabpy/tabpy_tools/custom_query_object.py b/tabpy/tabpy_tools/custom_query_object.py index 280e0c19..a2c0cdb7 100644 --- a/tabpy/tabpy_tools/custom_query_object.py +++ b/tabpy/tabpy_tools/custom_query_object.py @@ -1,4 +1,3 @@ -import inspect import logging import platform import sys From 2eae9703fa789d0da3ee64b8fe7ab060b4e08a26 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Fri, 22 Nov 2024 12:51:15 -0800 Subject: [PATCH 20/21] Correct doc_string to docstring. --- tabpy/tabpy_server/management/state.py | 4 ++-- tabpy/tabpy_tools/client.py | 2 +- tabpy/tabpy_tools/custom_query_object.py | 2 +- tabpy/tabpy_tools/query_object.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tabpy/tabpy_server/management/state.py b/tabpy/tabpy_server/management/state.py index d5aeae35..ecdf5974 100644 --- a/tabpy/tabpy_server/management/state.py +++ b/tabpy/tabpy_server/management/state.py @@ -215,7 +215,7 @@ def add_endpoint( Name of the endpoint description : str, optional Description of this endpoint - doc_string : str, optional + docstring : str, optional The doc string for this endpoint, if needed. endpoint_type : str The endpoint type (model, alias) @@ -309,7 +309,7 @@ def update_endpoint( Name of the endpoint description : str, optional Description of this endpoint - doc_string : str, optional + docstring : str, optional The doc string for this endpoint, if needed. endpoint_type : str, optional The endpoint type (model, alias) diff --git a/tabpy/tabpy_tools/client.py b/tabpy/tabpy_tools/client.py index 9bda2ac6..020c5d4e 100644 --- a/tabpy/tabpy_tools/client.py +++ b/tabpy/tabpy_tools/client.py @@ -408,7 +408,7 @@ def _gen_endpoint(self, name, obj, description, version=1, schema=None, is_publi "methods": endpoint_object.get_methods(), "required_files": [], "required_packages": [], - "docstring": endpoint_object.get_doc_string(), + "docstring": endpoint_object.get_docstring(), "schema": copy.copy(schema), "is_public": is_public, } diff --git a/tabpy/tabpy_tools/custom_query_object.py b/tabpy/tabpy_tools/custom_query_object.py index a2c0cdb7..597b0706 100644 --- a/tabpy/tabpy_tools/custom_query_object.py +++ b/tabpy/tabpy_tools/custom_query_object.py @@ -71,7 +71,7 @@ def query(self, *args, **kwargs): ) raise - def get_doc_string(self): + def get_docstring(self): """Get doc string from customized query""" default_docstring = "-- no docstring found in query function --" diff --git a/tabpy/tabpy_tools/query_object.py b/tabpy/tabpy_tools/query_object.py index 5ccbc109..94fe0d82 100644 --- a/tabpy/tabpy_tools/query_object.py +++ b/tabpy/tabpy_tools/query_object.py @@ -14,7 +14,7 @@ class QueryObject(abc.ABC): """ Derived class needs to implement the following interface: * query() -- given input, return query result - * get_doc_string() -- returns documentation for the Query Object + * get_docstring() -- returns documentation for the Query Object """ def __init__(self, description=""): @@ -30,7 +30,7 @@ def query(self, input): pass @abc.abstractmethod - def get_doc_string(self): + def get_docstring(self): """Returns documentation for the query object By default, this method returns the docstring for 'query' method From 3bb4d3797f8a553a11df8727a422bdfb3b8121e8 Mon Sep 17 00:00:00 2001 From: Jake Ichikawa Date: Mon, 25 Nov 2024 13:44:10 -0800 Subject: [PATCH 21/21] Improve not allowed string. --- tabpy/tabpy_tools/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tabpy/tabpy_tools/client.py b/tabpy/tabpy_tools/client.py index 020c5d4e..6582eefa 100644 --- a/tabpy/tabpy_tools/client.py +++ b/tabpy/tabpy_tools/client.py @@ -527,7 +527,7 @@ def _evaluate_remote_script(self, remote_script): msg = response.text.replace('null', 'Success') if "Ad-hoc scripts have been disabled" in msg: - msg += "\n[Remote TabPy client not allowed.]" + msg += "\n[Deployment to remote tabpy client not allowed.]" status_message = (f"{response.status_code} - {msg}\n") print(status_message)