From ea05286472995728034c6d9ce477d49dd629c967 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Tue, 13 Feb 2024 15:22:26 -0800 Subject: [PATCH 1/8] When a SDK client is instantiated, immediately check connectivity to verify API token and endpoint. --- src/groundlight/cli.py | 4 ++-- src/groundlight/client.py | 34 +++++++++++++++++++++++++++++++--- src/groundlight/config.py | 2 +- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/groundlight/cli.py b/src/groundlight/cli.py index 7ea962d5..059d34e6 100644 --- a/src/groundlight/cli.py +++ b/src/groundlight/cli.py @@ -6,7 +6,7 @@ from groundlight import Groundlight from groundlight.client import ApiTokenError -from groundlight.config import API_TOKEN_HELP_MESSAGE +from groundlight.config import API_TOKEN_MISSING_HELP_MESSAGE cli_app = typer.Typer( no_args_is_help=True, @@ -56,7 +56,7 @@ def groundlight(): cli_app.command()(cli_func) cli_app() except ApiTokenError: - print(API_TOKEN_HELP_MESSAGE) + print(API_TOKEN_MISSING_HELP_MESSAGE) if __name__ == "__main__": diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 244a4c0d..48652123 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -10,11 +10,12 @@ from openapi_client import Configuration from openapi_client.api.detectors_api import DetectorsApi from openapi_client.api.image_queries_api import ImageQueriesApi +from openapi_client.exceptions import UnauthorizedException from openapi_client.model.detector_creation_input import DetectorCreationInput from urllib3.exceptions import InsecureRequestWarning from groundlight.binary_labels import Label, convert_display_label_to_internal, convert_internal_label_to_display -from groundlight.config import API_TOKEN_HELP_MESSAGE, API_TOKEN_VARIABLE_NAME, DISABLE_TLS_VARIABLE_NAME +from groundlight.config import API_TOKEN_MISSING_HELP_MESSAGE, API_TOKEN_VARIABLE_NAME, DISABLE_TLS_VARIABLE_NAME from groundlight.encodings import url_encode_dict from groundlight.images import ByteStreamWrapper, parse_supported_image_types from groundlight.internalapi import ( @@ -29,7 +30,11 @@ logger = logging.getLogger("groundlight.sdk") -class ApiTokenError(Exception): +class GroundlightClientException(Exception): + pass + + +class ApiTokenError(GroundlightClientException): pass @@ -107,7 +112,8 @@ def __init__( # Retrieve the API token from environment variable api_token = os.environ[API_TOKEN_VARIABLE_NAME] except KeyError as e: - raise ApiTokenError(API_TOKEN_HELP_MESSAGE) from e + raise ApiTokenError(API_TOKEN_MISSING_HELP_MESSAGE) from e + self.api_token_prefix = api_token[:12] should_disable_tls_verification = disable_tls_verification @@ -129,6 +135,28 @@ def __init__( self.api_client = GroundlightApiClient(configuration) self.detectors_api = DetectorsApi(self.api_client) self.image_queries_api = ImageQueriesApi(self.api_client) + self._verify_connectivity() + + def _verify_connectivity(self) -> None: + """ + Verify that the client can connect to the Groundlight service, and raise a helpful + exception if it cannot. + """ + try: + # a simple query to confirm that the endpoint & API token are working + self.list_detectors(page=1, page_size=1) + except UnauthorizedException as e: + msg = ( + f"Invalid API token '{self.api_token_prefix}...' connecting to endpoint " + f"'{self.endpoint}'. Endpoint is responding, but API token is probably invalid." + ) + raise ApiTokenError(msg) from e + except Exception as e: + msg = ( + f"Error connecting to Groundlight using API token '{self.api_token_prefix}...'" + f" at endpoint '{self.endpoint}'. Endpoint might be invalid or offline?" + ) + raise GroundlightClientException(msg) from e @staticmethod def _fixup_image_query(iq: ImageQuery) -> ImageQuery: diff --git a/src/groundlight/config.py b/src/groundlight/config.py index a2f8893d..b70d4842 100644 --- a/src/groundlight/config.py +++ b/src/groundlight/config.py @@ -7,7 +7,7 @@ __all__ = ["API_TOKEN_WEB_URL", "API_TOKEN_VARIABLE_NAME", "DEFAULT_ENDPOINT", "DISABLE_TLS_VARIABLE_NAME"] -API_TOKEN_HELP_MESSAGE = ( +API_TOKEN_MISSING_HELP_MESSAGE = ( "No API token found. Please put your token in an environment variable " f'named "{API_TOKEN_VARIABLE_NAME}". If you don\'t have a token, you can ' f"create one at {API_TOKEN_WEB_URL}" From 0235111b85094b015a0fe06268b94920bc85395c Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Tue, 13 Feb 2024 15:25:06 -0800 Subject: [PATCH 2/8] Renaming exception to make linter happy. --- src/groundlight/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 48652123..87ca7d56 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -30,11 +30,11 @@ logger = logging.getLogger("groundlight.sdk") -class GroundlightClientException(Exception): +class GroundlightClientError(Exception): pass -class ApiTokenError(GroundlightClientException): +class ApiTokenError(GroundlightClientError): pass @@ -156,7 +156,7 @@ def _verify_connectivity(self) -> None: f"Error connecting to Groundlight using API token '{self.api_token_prefix}...'" f" at endpoint '{self.endpoint}'. Endpoint might be invalid or offline?" ) - raise GroundlightClientException(msg) from e + raise GroundlightClientError(msg) from e @staticmethod def _fixup_image_query(iq: ImageQuery) -> ImageQuery: From 0556b3043ced278f8930de747cf4071f728e1e11 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Tue, 13 Feb 2024 15:33:59 -0800 Subject: [PATCH 3/8] Better error messages in CLI. Catching another edge case with a helpful error. --- src/groundlight/cli.py | 4 ++-- src/groundlight/client.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/groundlight/cli.py b/src/groundlight/cli.py index 059d34e6..bf380bef 100644 --- a/src/groundlight/cli.py +++ b/src/groundlight/cli.py @@ -55,8 +55,8 @@ def groundlight(): cli_func = class_func_to_cli(method) cli_app.command()(cli_func) cli_app() - except ApiTokenError: - print(API_TOKEN_MISSING_HELP_MESSAGE) + except ApiTokenError as e: + print(e) if __name__ == "__main__": diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 87ca7d56..5bddb004 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -107,12 +107,14 @@ def __init__( self.endpoint = sanitize_endpoint_url(endpoint) configuration = Configuration(host=self.endpoint) - if api_token is None: + if not api_token: try: # Retrieve the API token from environment variable api_token = os.environ[API_TOKEN_VARIABLE_NAME] except KeyError as e: raise ApiTokenError(API_TOKEN_MISSING_HELP_MESSAGE) from e + if not api_token: + raise ApiTokenError("No API token found. GROUNDLIGHT_API_TOKEN environment variable is set but blank") self.api_token_prefix = api_token[:12] should_disable_tls_verification = disable_tls_verification From 71bd749b0ca724f5a8a944fdd40e09c525ad8605 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Tue, 13 Feb 2024 23:34:50 +0000 Subject: [PATCH 4/8] Automatically reformatting code --- src/groundlight/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/groundlight/cli.py b/src/groundlight/cli.py index bf380bef..c15c4035 100644 --- a/src/groundlight/cli.py +++ b/src/groundlight/cli.py @@ -6,7 +6,6 @@ from groundlight import Groundlight from groundlight.client import ApiTokenError -from groundlight.config import API_TOKEN_MISSING_HELP_MESSAGE cli_app = typer.Typer( no_args_is_help=True, From eb03a32dcef6eca44e37974ce980c7665bcd2643 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Tue, 13 Feb 2024 15:35:53 -0800 Subject: [PATCH 5/8] wordsmithing --- src/groundlight/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 5bddb004..919197fb 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -156,7 +156,7 @@ def _verify_connectivity(self) -> None: except Exception as e: msg = ( f"Error connecting to Groundlight using API token '{self.api_token_prefix}...'" - f" at endpoint '{self.endpoint}'. Endpoint might be invalid or offline?" + f" at endpoint '{self.endpoint}'. Endpoint might be invalid or unreachable?" ) raise GroundlightClientError(msg) from e From 59d4e61451a585cf4247ac797a1afa5fef965228 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Tue, 13 Feb 2024 15:37:30 -0800 Subject: [PATCH 6/8] fixing lint error --- src/groundlight/cli.py | 1 - src/groundlight/client.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/groundlight/cli.py b/src/groundlight/cli.py index bf380bef..c15c4035 100644 --- a/src/groundlight/cli.py +++ b/src/groundlight/cli.py @@ -6,7 +6,6 @@ from groundlight import Groundlight from groundlight.client import ApiTokenError -from groundlight.config import API_TOKEN_MISSING_HELP_MESSAGE cli_app = typer.Typer( no_args_is_help=True, diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 919197fb..47f0c28d 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -156,7 +156,8 @@ def _verify_connectivity(self) -> None: except Exception as e: msg = ( f"Error connecting to Groundlight using API token '{self.api_token_prefix}...'" - f" at endpoint '{self.endpoint}'. Endpoint might be invalid or unreachable?" + f" at endpoint '{self.endpoint}'. Endpoint might be invalid or unreachable? " + f"Check https://status.groundlight.ai/ for service status." ) raise GroundlightClientError(msg) from e From 2aea5902ccbddd686c90cc2ea52e3ebcd3cd0ad7 Mon Sep 17 00:00:00 2001 From: Auto-format Bot Date: Tue, 13 Feb 2024 23:38:27 +0000 Subject: [PATCH 7/8] Automatically reformatting code --- src/groundlight/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groundlight/client.py b/src/groundlight/client.py index 47f0c28d..d7a8fc69 100644 --- a/src/groundlight/client.py +++ b/src/groundlight/client.py @@ -157,7 +157,7 @@ def _verify_connectivity(self) -> None: msg = ( f"Error connecting to Groundlight using API token '{self.api_token_prefix}...'" f" at endpoint '{self.endpoint}'. Endpoint might be invalid or unreachable? " - f"Check https://status.groundlight.ai/ for service status." + "Check https://status.groundlight.ai/ for service status." ) raise GroundlightClientError(msg) from e From bfd263a5bfa2722778d1e4e186a498805ae805a3 Mon Sep 17 00:00:00 2001 From: Leo Dirac Date: Tue, 13 Feb 2024 17:20:31 -0800 Subject: [PATCH 8/8] Disabling testing on the edge endpoint docs. --- docs/docs/building-applications/7-edge.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/building-applications/7-edge.md b/docs/docs/building-applications/7-edge.md index 03f6a21f..873b81d3 100644 --- a/docs/docs/building-applications/7-edge.md +++ b/docs/docs/building-applications/7-edge.md @@ -21,7 +21,7 @@ image queries will not appear in the cloud dashboard. To configure the Groundlight SDK to use the edge endpoint, you can either pass the endpoint URL to the Groundlight constructor like: -```python +```python notest from groundlight import Groundlight gl = Groundlight(endpoint="http://localhost:6717") ```