From a6af40a05567c56638232062bdc1b0c0c5767551 Mon Sep 17 00:00:00 2001 From: Chiraag Date: Mon, 14 Jul 2025 10:26:15 -0400 Subject: [PATCH 1/9] Add api key as resource attribute --- quotientai/_constants.py | 2 ++ quotientai/tracing/core.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/quotientai/_constants.py b/quotientai/_constants.py index 195f8db..6071dee 100644 --- a/quotientai/_constants.py +++ b/quotientai/_constants.py @@ -1,3 +1,5 @@ TRACER_NAME = "quotient.sdk.python" DEFAULT_TRACING_ENDPOINT = "https://otel.quotientai.co/v1/traces" +# DEFAULT_TRACING_ENDPOINT = "http://localhost:4318/v1/traces" + diff --git a/quotientai/tracing/core.py b/quotientai/tracing/core.py index cc53e57..dca66f6 100644 --- a/quotientai/tracing/core.py +++ b/quotientai/tracing/core.py @@ -122,6 +122,14 @@ def _create_otlp_exporter(self, endpoint: str, headers: dict): Can be overridden or patched for testing. """ return OTLPSpanExporter(endpoint=endpoint, headers=headers) + + def _get_trace_api_key(self): + """ + Get API key for trace identification. + Can be overridden to hash/truncate the key for security. + """ + return self._client.api_key + @functools.lru_cache() def _setup_auto_collector(self, app_name: str, environment: str, instruments: Optional[tuple] = None, detections: Optional[str] = None): @@ -139,6 +147,7 @@ def _setup_auto_collector(self, app_name: str, environment: str, instruments: Op resource_attributes = { QuotientAttributes.app_name: app_name, QuotientAttributes.environment: environment, + "quotient.api_key": self._get_trace_api_key(), } if detections is not None: From 749803ff1d1673d3aae1e78f6ee78cf386a88acb Mon Sep 17 00:00:00 2001 From: Chiraag Date: Mon, 14 Jul 2025 22:47:58 -0400 Subject: [PATCH 2/9] jwt auth --- quotientai/tracing/core.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/quotientai/tracing/core.py b/quotientai/tracing/core.py index dca66f6..696d8c3 100644 --- a/quotientai/tracing/core.py +++ b/quotientai/tracing/core.py @@ -123,12 +123,24 @@ def _create_otlp_exporter(self, endpoint: str, headers: dict): """ return OTLPSpanExporter(endpoint=endpoint, headers=headers) - def _get_trace_api_key(self): + def _get_jwt(self): """ - Get API key for trace identification. - Can be overridden to hash/truncate the key for security. + Get JWT token from client. + Returns the JWT token or falls back to API key if not found. """ - return self._client.api_key + # First check if client already has a valid token loaded + if hasattr(self._client, '_is_token_valid') and self._client._is_token_valid(): + return self._client.token + + try: + with open(self._client._token_path, 'r') as f: + data = json.load(f) + jwt = data.get('token') + if jwt: + return jwt + return None + except Exception as e: + return None @functools.lru_cache() @@ -147,7 +159,7 @@ def _setup_auto_collector(self, app_name: str, environment: str, instruments: Op resource_attributes = { QuotientAttributes.app_name: app_name, QuotientAttributes.environment: environment, - "quotient.api_key": self._get_trace_api_key(), + "quotient.jwt": self._get_jwt(), # Use JWT instead of API key } if detections is not None: @@ -163,10 +175,10 @@ def _setup_auto_collector(self, app_name: str, environment: str, instruments: Op "OTEL_EXPORTER_OTLP_ENDPOINT", DEFAULT_TRACING_ENDPOINT, ) - + # Parse headers from environment or use default headers = { - "Authorization": f"Bearer {self._client.api_key}", + "Authorization": f"Bearer {self._client.token}", "Content-Type": "application/x-protobuf", } if "OTEL_EXPORTER_OTLP_HEADERS" in os.environ: From 5dcf3d50df2a5bdfc7ca48441be616ccd8550a3b Mon Sep 17 00:00:00 2001 From: Chiraag Date: Mon, 14 Jul 2025 22:49:07 -0400 Subject: [PATCH 3/9] api key --- quotientai/tracing/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quotientai/tracing/core.py b/quotientai/tracing/core.py index 696d8c3..d4fae5f 100644 --- a/quotientai/tracing/core.py +++ b/quotientai/tracing/core.py @@ -178,7 +178,7 @@ def _setup_auto_collector(self, app_name: str, environment: str, instruments: Op # Parse headers from environment or use default headers = { - "Authorization": f"Bearer {self._client.token}", + "Authorization": f"Bearer {self._client.api_key}", "Content-Type": "application/x-protobuf", } if "OTEL_EXPORTER_OTLP_HEADERS" in os.environ: From 557e8e09888c01f0a49009fd40ff2eb93b181a0d Mon Sep 17 00:00:00 2001 From: Chiraag Date: Wed, 16 Jul 2025 09:50:52 -0400 Subject: [PATCH 4/9] user_id instead of jwt --- quotientai/async_client.py | 1 + quotientai/client.py | 1 + quotientai/resources/auth.py | 10 ++++++++++ quotientai/tracing/core.py | 27 +++++++++------------------ 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/quotientai/async_client.py b/quotientai/async_client.py index f0b6556..1de887d 100644 --- a/quotientai/async_client.py +++ b/quotientai/async_client.py @@ -30,6 +30,7 @@ def __init__(self, api_key: str): self.token = None self.token_expiry = 0 self.token_api_key = None + self.user_id = None self._token_path = ( token_dir / ".quotient" diff --git a/quotientai/client.py b/quotientai/client.py index 5a354eb..0c2e127 100644 --- a/quotientai/client.py +++ b/quotientai/client.py @@ -32,6 +32,7 @@ def __init__(self, api_key: str): self.token = None self.token_expiry = 0 self.token_api_key = None + self.user_id = None self._token_path = ( token_dir / ".quotient" diff --git a/quotientai/resources/auth.py b/quotientai/resources/auth.py index 6e459e8..fbf09b3 100644 --- a/quotientai/resources/auth.py +++ b/quotientai/resources/auth.py @@ -8,6 +8,11 @@ def authenticate(self): A call to GET /auth/profile to initially authenticate the user. """ response = self._client._get("/auth/profile") + + # Set the user_id if successful + if response and isinstance(response, dict) and 'user_id' in response: + self._client.user_id = response['user_id'] + return response class AsyncAuthResource: @@ -25,6 +30,11 @@ def authenticate(self): task = loop.create_task(self._client._get("/auth/profile")) # Run the task to completion result = loop.run_until_complete(task) + + # Set the user_id if successful + if result and isinstance(result, dict) and 'user_id' in result: + self._client.user_id = result['user_id'] + return result finally: loop.close() diff --git a/quotientai/tracing/core.py b/quotientai/tracing/core.py index d4fae5f..e73c3b8 100644 --- a/quotientai/tracing/core.py +++ b/quotientai/tracing/core.py @@ -122,25 +122,16 @@ def _create_otlp_exporter(self, endpoint: str, headers: dict): Can be overridden or patched for testing. """ return OTLPSpanExporter(endpoint=endpoint, headers=headers) - - def _get_jwt(self): + + + def _get_user_id(self): """ - Get JWT token from client. - Returns the JWT token or falls back to API key if not found. + Get user_id from client. + Returns the user_id or None if not found. """ - # First check if client already has a valid token loaded - if hasattr(self._client, '_is_token_valid') and self._client._is_token_valid(): - return self._client.token - - try: - with open(self._client._token_path, 'r') as f: - data = json.load(f) - jwt = data.get('token') - if jwt: - return jwt - return None - except Exception as e: - return None + if hasattr(self._client, 'user_id'): + return self._client.user_id + return None @functools.lru_cache() @@ -159,7 +150,7 @@ def _setup_auto_collector(self, app_name: str, environment: str, instruments: Op resource_attributes = { QuotientAttributes.app_name: app_name, QuotientAttributes.environment: environment, - "quotient.jwt": self._get_jwt(), # Use JWT instead of API key + "quotient.user_id": self._get_user_id(), } if detections is not None: From c8f433185e70a97f7b9e329be218f4aeb8c349d7 Mon Sep 17 00:00:00 2001 From: Chiraag Date: Wed, 16 Jul 2025 09:52:10 -0400 Subject: [PATCH 5/9] clean --- quotientai/tracing/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quotientai/tracing/core.py b/quotientai/tracing/core.py index e73c3b8..790719a 100644 --- a/quotientai/tracing/core.py +++ b/quotientai/tracing/core.py @@ -166,7 +166,7 @@ def _setup_auto_collector(self, app_name: str, environment: str, instruments: Op "OTEL_EXPORTER_OTLP_ENDPOINT", DEFAULT_TRACING_ENDPOINT, ) - + # Parse headers from environment or use default headers = { "Authorization": f"Bearer {self._client.api_key}", From 027f7c988e78370b5bf41e686ba895744aea7681 Mon Sep 17 00:00:00 2001 From: Chiraag Rekhari Date: Wed, 16 Jul 2025 10:34:48 -0400 Subject: [PATCH 6/9] Update quotientai/tracing/core.py Co-authored-by: Freddie Vargus --- quotientai/tracing/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quotientai/tracing/core.py b/quotientai/tracing/core.py index 790719a..0c7748a 100644 --- a/quotientai/tracing/core.py +++ b/quotientai/tracing/core.py @@ -124,13 +124,13 @@ def _create_otlp_exporter(self, endpoint: str, headers: dict): return OTLPSpanExporter(endpoint=endpoint, headers=headers) - def _get_user_id(self): + def _get_user(self): """ Get user_id from client. Returns the user_id or None if not found. """ - if hasattr(self._client, 'user_id'): - return self._client.user_id + if hasattr(self._client, '_QuotientAI__user'): + return self._client._QuotientAI__user return None From a0f21f36d71a17e3c5b129fbe3b53aabed8b6134 Mon Sep 17 00:00:00 2001 From: Chiraag Date: Wed, 16 Jul 2025 10:41:00 -0400 Subject: [PATCH 7/9] fixes from review --- quotientai/_constants.py | 3 --- quotientai/async_client.py | 2 +- quotientai/client.py | 2 +- quotientai/resources/auth.py | 4 ++-- quotientai/tracing/core.py | 8 ++++---- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/quotientai/_constants.py b/quotientai/_constants.py index 6071dee..6f30377 100644 --- a/quotientai/_constants.py +++ b/quotientai/_constants.py @@ -1,5 +1,2 @@ TRACER_NAME = "quotient.sdk.python" DEFAULT_TRACING_ENDPOINT = "https://otel.quotientai.co/v1/traces" -# DEFAULT_TRACING_ENDPOINT = "http://localhost:4318/v1/traces" - - diff --git a/quotientai/async_client.py b/quotientai/async_client.py index 1de887d..7e0b95f 100644 --- a/quotientai/async_client.py +++ b/quotientai/async_client.py @@ -30,7 +30,7 @@ def __init__(self, api_key: str): self.token = None self.token_expiry = 0 self.token_api_key = None - self.user_id = None + self._user = None self._token_path = ( token_dir / ".quotient" diff --git a/quotientai/client.py b/quotientai/client.py index 0c2e127..0248044 100644 --- a/quotientai/client.py +++ b/quotientai/client.py @@ -32,7 +32,7 @@ def __init__(self, api_key: str): self.token = None self.token_expiry = 0 self.token_api_key = None - self.user_id = None + self._user = None self._token_path = ( token_dir / ".quotient" diff --git a/quotientai/resources/auth.py b/quotientai/resources/auth.py index fbf09b3..3b94a19 100644 --- a/quotientai/resources/auth.py +++ b/quotientai/resources/auth.py @@ -11,7 +11,7 @@ def authenticate(self): # Set the user_id if successful if response and isinstance(response, dict) and 'user_id' in response: - self._client.user_id = response['user_id'] + self._client._user = response['user_id'] return response @@ -33,7 +33,7 @@ def authenticate(self): # Set the user_id if successful if result and isinstance(result, dict) and 'user_id' in result: - self._client.user_id = result['user_id'] + self._client._user = result['user_id'] return result finally: diff --git a/quotientai/tracing/core.py b/quotientai/tracing/core.py index 0c7748a..758ed29 100644 --- a/quotientai/tracing/core.py +++ b/quotientai/tracing/core.py @@ -129,9 +129,9 @@ def _get_user(self): Get user_id from client. Returns the user_id or None if not found. """ - if hasattr(self._client, '_QuotientAI__user'): - return self._client._QuotientAI__user - return None + if hasattr(self._client, '_user'): + return self._client._user + return "None" @functools.lru_cache() @@ -150,7 +150,7 @@ def _setup_auto_collector(self, app_name: str, environment: str, instruments: Op resource_attributes = { QuotientAttributes.app_name: app_name, QuotientAttributes.environment: environment, - "quotient.user_id": self._get_user_id(), + "quotient.user": self._get_user(), } if detections is not None: From 659ec96af6eee496afd7be695b2e60783e84ef8e Mon Sep 17 00:00:00 2001 From: Chiraag Date: Wed, 16 Jul 2025 10:41:46 -0400 Subject: [PATCH 8/9] newline --- quotientai/_constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quotientai/_constants.py b/quotientai/_constants.py index 6f30377..195f8db 100644 --- a/quotientai/_constants.py +++ b/quotientai/_constants.py @@ -1,2 +1,3 @@ TRACER_NAME = "quotient.sdk.python" DEFAULT_TRACING_ENDPOINT = "https://otel.quotientai.co/v1/traces" + From ebc475d8872d42cf790001cefd6ae0277c681436 Mon Sep 17 00:00:00 2001 From: Chiraag Date: Wed, 16 Jul 2025 10:46:08 -0400 Subject: [PATCH 9/9] quotientattr --- quotientai/tracing/core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quotientai/tracing/core.py b/quotientai/tracing/core.py index 758ed29..298a903 100644 --- a/quotientai/tracing/core.py +++ b/quotientai/tracing/core.py @@ -42,6 +42,7 @@ class QuotientAttributes(str, Enum): app_name = "app.name" environment = "app.environment" detections = "quotient.detections" + user = "quotient.user" class TracingResource: @@ -150,7 +151,7 @@ def _setup_auto_collector(self, app_name: str, environment: str, instruments: Op resource_attributes = { QuotientAttributes.app_name: app_name, QuotientAttributes.environment: environment, - "quotient.user": self._get_user(), + QuotientAttributes.user: self._get_user(), } if detections is not None: @@ -166,7 +167,7 @@ def _setup_auto_collector(self, app_name: str, environment: str, instruments: Op "OTEL_EXPORTER_OTLP_ENDPOINT", DEFAULT_TRACING_ENDPOINT, ) - + # Parse headers from environment or use default headers = { "Authorization": f"Bearer {self._client.api_key}",