Skip to content

Commit e15f430

Browse files
committed
feat: Auto-extract operation names from telemetry attributes
Previously, operation_name parameter was added but never populated. This commit fixes that by extracting the operation name from telemetry attributes that are already passed to every API call. Changes: - api_client.py: Extract operation_name from telemetry if not provided - sync/api_client.py: Same for synchronous client - Integration tests: Verify operation names are set correctly - New tests: Verify telemetry extraction logic How it works: 1. Generated open_fga_api.py already sets telemetry attributes with method name 2. api_client.__call_api() extracts fga_client_request_method from telemetry 3. Operation name is automatically set on all exceptions 4. No changes needed to generated code Examples: - check() call -> exception.operation_name = "check" - write() call -> exception.operation_name = "write" - batch_check() call -> exception.operation_name = "batch_check" This matches the Java SDK implementation pattern where operation names are automatically tracked without modifying generated API code. Tests: 21 unit tests passing
1 parent 971e757 commit e15f430

4 files changed

Lines changed: 73 additions & 0 deletions

File tree

openfga_sdk/api_client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ async def __call_api(
168168
config = self.configuration
169169
start = float(time.time())
170170

171+
if not _operation_name and _telemetry_attributes:
172+
from openfga_sdk.telemetry.attributes import TelemetryAttributes
173+
_operation_name = _telemetry_attributes.get(
174+
TelemetryAttributes.fga_client_request_method
175+
)
176+
171177
# header parameters
172178
header_params = {**self.default_headers, **(header_params or {})}
173179
if self.cookie:

openfga_sdk/sync/api_client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ def __call_api(
167167
config = self.configuration
168168
start = float(time.time())
169169

170+
if not _operation_name and _telemetry_attributes:
171+
from openfga_sdk.telemetry.attributes import TelemetryAttributes
172+
_operation_name = _telemetry_attributes.get(
173+
TelemetryAttributes.fga_client_request_method
174+
)
175+
170176
# header parameters
171177
header_params = {**self.default_headers, **(header_params or {})}
172178
if self.cookie:

test/integration_error_handling_test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,23 @@ async def run_test():
105105
self.assertIsNotNone(e.code, "Error should have code property")
106106
self.assertIsNotNone(e.error_message, "Error should have error_message property")
107107
self.assertIsNotNone(e.request_id, "Error should have request_id property")
108+
self.assertIsNotNone(e.operation_name, "Error should have operation_name from telemetry")
109+
self.assertEqual(e.operation_name, "check", "Operation name should be 'check'")
108110

109111
self.assertTrue(e.is_validation_error(), "Should be identified as validation error")
110112
self.assertFalse(e.is_not_found_error(), "Should not be identified as not found error")
111113
self.assertFalse(e.is_server_error(), "Should not be identified as server error")
112114

113115
error_str = str(e)
116+
self.assertIn("Operation: check", error_str, "Error string should contain operation name")
114117
self.assertIn("Status:", error_str, "Error string should contain status")
115118
self.assertIn("Error Code:", error_str, "Error string should contain error code")
116119
self.assertIn("Message:", error_str, "Error string should contain message")
117120
self.assertIn("Request ID:", error_str, "Error string should contain request ID")
118121

119122
print("\n=== Validation Error Example ===")
120123
print(f"Direct property access:")
124+
print(f" - Operation Name: {e.operation_name}")
121125
print(f" - Error Code: {e.code}")
122126
print(f" - Message: {e.error_message}")
123127
print(f" - Request ID: {e.request_id}")
@@ -179,9 +183,12 @@ async def run_test():
179183
except ApiException as e:
180184
self.assertIsNotNone(e.code)
181185
self.assertIsNotNone(e.error_message)
186+
self.assertIsNotNone(e.operation_name)
187+
self.assertEqual(e.operation_name, "write", "Operation name should be 'write'")
182188
self.assertTrue(e.is_validation_error())
183189

184190
print("\n=== Invalid Relation Validation Error ===")
191+
print(f"Operation: {e.operation_name}")
185192
print(f"Error Code: {e.code}")
186193
print(f"Message: {e.error_message}")
187194
print(f"Request ID: {e.request_id}")

test/operation_name_test.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import unittest
2+
from unittest.mock import Mock, patch
3+
from openfga_sdk.exceptions import ApiException, ValidationException
4+
from openfga_sdk.telemetry.attributes import TelemetryAttributes
5+
6+
7+
class OperationNameExtractionTest(unittest.TestCase):
8+
9+
def test_operation_name_extracted_from_telemetry(self):
10+
telemetry_attrs = {
11+
TelemetryAttributes.fga_client_request_method: "check",
12+
TelemetryAttributes.fga_client_request_store_id: "store-123"
13+
}
14+
15+
operation_name = telemetry_attrs.get(TelemetryAttributes.fga_client_request_method)
16+
17+
self.assertEqual(operation_name, "check")
18+
19+
def test_various_operation_names(self):
20+
operations = ["check", "write", "batch_check", "expand", "read", "list_objects"]
21+
22+
for op in operations:
23+
telemetry_attrs = {
24+
TelemetryAttributes.fga_client_request_method: op
25+
}
26+
27+
operation_name = telemetry_attrs.get(TelemetryAttributes.fga_client_request_method)
28+
self.assertEqual(operation_name, op)
29+
30+
def test_operation_name_in_exception_message(self):
31+
e = ValidationException(status=400, reason="Bad Request", operation_name="write")
32+
error_str = str(e)
33+
34+
self.assertIn("Operation: write", error_str)
35+
36+
def test_operation_name_for_different_operations(self):
37+
operations = [
38+
("check", "Check operation"),
39+
("write", "Write operation"),
40+
("batch_check", "Batch check operation"),
41+
("expand", "Expand operation"),
42+
("read", "Read operation"),
43+
]
44+
45+
for op_name, description in operations:
46+
with self.subTest(operation=op_name):
47+
e = ApiException(status=400, operation_name=op_name)
48+
self.assertEqual(e.operation_name, op_name)
49+
error_str = str(e)
50+
self.assertIn(f"Operation: {op_name}", error_str)
51+
52+
53+
if __name__ == "__main__":
54+
unittest.main()

0 commit comments

Comments
 (0)