From fbb3b5d3b229697bbd70a00eb19bedae059d7dc7 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 25 Mar 2025 09:48:37 -0500 Subject: [PATCH] release(python): v0.9.2 --- config/clients/python/CHANGELOG.md.mustache | 5 +++ config/clients/python/config.overrides.json | 2 +- .../template/src/api_client.py.mustache | 3 ++ .../src/client/configuration.py.mustache | 34 ++++++++++++++++++- .../template/src/sync/api_client.py.mustache | 3 ++ .../src/telemetry/attributes.py.mustache | 6 ++++ .../src/telemetry/metrics.py.mustache | 9 ++--- .../template/test/rest_test.py.mustache | 11 ++++-- .../telemetry/attributes_test.py.mustache | 14 ++++++-- 9 files changed, 76 insertions(+), 11 deletions(-) diff --git a/config/clients/python/CHANGELOG.md.mustache b/config/clients/python/CHANGELOG.md.mustache index cba24f39..442c7304 100644 --- a/config/clients/python/CHANGELOG.md.mustache +++ b/config/clients/python/CHANGELOG.md.mustache @@ -2,6 +2,11 @@ ## [Unreleased](https://github.com/openfga/python-sdk/compare/v{{packageVersion}}...HEAD) +### [0.9.2](https://github.com/openfga/python-sdk/compare/v0.9.1...v0.9.2) (2025-03-25) + +- fix(telemetry): fixes for telemetry attributes and metrics tracking +- fix: REST client should not close after `stream` request + ## v0.9.1 ### [0.9.1](https://github.com/openfga/python-sdk/compare/v0.9.0...v0.9.1) (2025-01-23) diff --git a/config/clients/python/config.overrides.json b/config/clients/python/config.overrides.json index 76dbfcb8..9bcb925b 100644 --- a/config/clients/python/config.overrides.json +++ b/config/clients/python/config.overrides.json @@ -2,7 +2,7 @@ "sdkId": "python", "gitRepoId": "python-sdk", "packageName": "openfga_sdk", - "packageVersion": "0.9.1", + "packageVersion": "0.9.2", "packageDescription": "Python SDK for OpenFGA", "packageDetailedDescription": "This is an autogenerated python SDK for OpenFGA. It provides a wrapper around the [OpenFGA API definition](https://openfga.dev/api).", "fossaComplianceNoticeId": "2f8a8629-b46c-435e-b8cd-1174a674fb4b", diff --git a/config/clients/python/template/src/api_client.py.mustache b/config/clients/python/template/src/api_client.py.mustache index 35405f98..c8723513 100644 --- a/config/clients/python/template/src/api_client.py.mustache +++ b/config/clients/python/template/src/api_client.py.mustache @@ -278,6 +278,7 @@ class ApiClient: response=e.body.decode("utf-8"), credentials=self.configuration.credentials, attributes=_telemetry_attributes, + start=start, ) self._telemetry.metrics.request( @@ -307,6 +308,7 @@ class ApiClient: response=e, credentials=self.configuration.credentials, attributes=_telemetry_attributes, + start=start, ) self._telemetry.metrics.request( @@ -333,6 +335,7 @@ class ApiClient: response=response_data, credentials=self.configuration.credentials, attributes=_telemetry_attributes, + start=start, ) self._telemetry.metrics.request( diff --git a/config/clients/python/template/src/client/configuration.py.mustache b/config/clients/python/template/src/client/configuration.py.mustache index eea054c9..b9825ebc 100644 --- a/config/clients/python/template/src/client/configuration.py.mustache +++ b/config/clients/python/template/src/client/configuration.py.mustache @@ -2,6 +2,14 @@ from {{packageName}}.configuration import Configuration from {{packageName}}.exceptions import FgaValidationException +from {{packageName}}.telemetry.attributes import TelemetryAttribute +from {{packageName}}.telemetry.configuration import ( + TelemetryConfigurationType, + TelemetryMetricConfiguration, + TelemetryMetricsConfiguration, +) +from {{packageName}}.telemetry.counters import TelemetryCounter +from {{packageName}}.telemetry.histograms import TelemetryHistogram from {{packageName}}.validation import is_well_formed_ulid_string class ClientConfiguration(Configuration): @@ -20,8 +28,32 @@ class ClientConfiguration(Configuration): ssl_ca_cert=None, api_url=None, # TODO: restructure when removing api_scheme/api_host timeout_millisec: int | None = None, + telemetry: ( + dict[ + TelemetryConfigurationType | str, + TelemetryMetricsConfiguration + | dict[ + TelemetryHistogram | TelemetryCounter | str, + TelemetryMetricConfiguration + | dict[TelemetryAttribute | str, bool] + | None, + ] + | None, + ] + | None + ) = None, ): - super().__init__(api_scheme, api_host, store_id, credentials, retry_params, ssl_ca_cert=ssl_ca_cert, api_url=api_url, timeout_millisec=timeout_millisec) + super().__init__( + api_scheme, + api_host, + store_id, + credentials, + retry_params, + ssl_ca_cert=ssl_ca_cert, + api_url=api_url, + timeout_millisec=timeout_millisec, + telemetry=telemetry, + ) self._authorization_model_id = authorization_model_id def is_valid(self): diff --git a/config/clients/python/template/src/sync/api_client.py.mustache b/config/clients/python/template/src/sync/api_client.py.mustache index 3edd1e61..8199d486 100644 --- a/config/clients/python/template/src/sync/api_client.py.mustache +++ b/config/clients/python/template/src/sync/api_client.py.mustache @@ -264,6 +264,7 @@ class ApiClient: response=e.body.decode("utf-8"), credentials=self.configuration.credentials, attributes=_telemetry_attributes, + start=start, ) self._telemetry.metrics.request( @@ -291,6 +292,7 @@ class ApiClient: response=e, credentials=self.configuration.credentials, attributes=_telemetry_attributes, + start=start, ) self._telemetry.metrics.request( @@ -317,6 +319,7 @@ class ApiClient: response=response_data, credentials=self.configuration.credentials, attributes=_telemetry_attributes, + start=start, ) self._telemetry.metrics.request( diff --git a/config/clients/python/template/src/telemetry/attributes.py.mustache b/config/clients/python/template/src/telemetry/attributes.py.mustache index 2aec69a4..3ee7e4c1 100644 --- a/config/clients/python/template/src/telemetry/attributes.py.mustache +++ b/config/clients/python/template/src/telemetry/attributes.py.mustache @@ -278,6 +278,7 @@ class TelemetryAttributes: ) = None, credentials: Credentials | None = None, attributes: dict[TelemetryAttribute, str | bool | int | float] | None = None, + start: float | None = None, ) -> dict[TelemetryAttribute, str | bool | int | float]: response_model_id = None response_query_duration = None @@ -286,6 +287,11 @@ class TelemetryAttributes: if attributes is not None: _attributes = attributes + if start is not None and start > 0: + _attributes[TelemetryAttributes.http_client_request_duration] = int( + (time.time() - start) * 1000 + ) + if isinstance(response, ApiException): if response.status is not None: _attributes[TelemetryAttributes.http_response_status_code] = int( diff --git a/config/clients/python/template/src/telemetry/metrics.py.mustache b/config/clients/python/template/src/telemetry/metrics.py.mustache index 5b34fb66..482fe2a7 100644 --- a/config/clients/python/template/src/telemetry/metrics.py.mustache +++ b/config/clients/python/template/src/telemetry/metrics.py.mustache @@ -9,6 +9,7 @@ from {{packageName}}.telemetry.attributes import ( from {{packageName}}.telemetry.configuration import ( TelemetryConfiguration, TelemetryMetricConfiguration, + TelemetryMetricsConfiguration, isMetricEnabled, ) from {{packageName}}.telemetry.counters import TelemetryCounter, TelemetryCounters @@ -80,7 +81,7 @@ class TelemetryMetrics: if ( isinstance(configuration, TelemetryConfiguration) - and isinstance(configuration.metrics, TelemetryMetricConfiguration) + and isinstance(configuration.metrics, TelemetryMetricsConfiguration) and isinstance( configuration.metrics.fga_client_request, TelemetryMetricConfiguration, @@ -117,7 +118,7 @@ class TelemetryMetrics: if ( isinstance(configuration, TelemetryConfiguration) - and isinstance(configuration.metrics, TelemetryMetricConfiguration) + and isinstance(configuration.metrics, TelemetryMetricsConfiguration) and isinstance( configuration.metrics.fga_client_credentials_request, TelemetryMetricConfiguration, @@ -168,7 +169,7 @@ class TelemetryMetrics: if ( isinstance(configuration, TelemetryConfiguration) - and isinstance(configuration.metrics, TelemetryMetricConfiguration) + and isinstance(configuration.metrics, TelemetryMetricsConfiguration) and isinstance( configuration.metrics.fga_client_request_duration, TelemetryMetricConfiguration, @@ -217,7 +218,7 @@ class TelemetryMetrics: if ( isinstance(configuration, TelemetryConfiguration) - and isinstance(configuration.metrics, TelemetryMetricConfiguration) + and isinstance(configuration.metrics, TelemetryMetricsConfiguration) and isinstance( configuration.metrics.fga_client_query_duration, TelemetryMetricConfiguration, diff --git a/config/clients/python/template/test/rest_test.py.mustache b/config/clients/python/template/test/rest_test.py.mustache index 33dab323..52491c6f 100644 --- a/config/clients/python/template/test/rest_test.py.mustache +++ b/config/clients/python/template/test/rest_test.py.mustache @@ -328,7 +328,9 @@ async def test_stream_exception_in_chunks(): class FakeContent: async def iter_chunks(self): - raise ValueError("Boom!") + if True: # This ensures the coroutine is actually created and awaited + raise ValueError("Boom!") + yield (b"", None) # This line is never reached mock_response = MagicMock() mock_response.status = 200 @@ -346,8 +348,11 @@ async def test_stream_exception_in_chunks(): client.close = AsyncMock() results = [] - async for item in client.stream("GET", "http://example.com"): - results.append(item) + try: + async for item in client.stream("GET", "http://example.com"): + results.append(item) + except ValueError: + pass assert results == [] client.handle_response_exception.assert_awaited_once() diff --git a/config/clients/python/template/test/telemetry/attributes_test.py.mustache b/config/clients/python/template/test/telemetry/attributes_test.py.mustache index 9a4783e7..05f72e2f 100644 --- a/config/clients/python/template/test/telemetry/attributes_test.py.mustache +++ b/config/clients/python/template/test/telemetry/attributes_test.py.mustache @@ -112,6 +112,7 @@ def test_from_request_without_optional_params(telemetry_attributes): def test_from_response_with_http_response(telemetry_attributes): + start_time = time.time() - 5 response = MagicMock(spec=HTTPResponse) response.status = 200 response.getheader.side_effect = lambda header: { @@ -124,16 +125,21 @@ def test_from_response_with_http_response(telemetry_attributes): configuration=CredentialConfiguration(client_id="client_123"), ) attributes = telemetry_attributes.fromResponse( - response=response, credentials=credentials + response=response, + credentials=credentials, + start=start_time, ) assert attributes[TelemetryAttributes.http_response_status_code] == 200 assert attributes[TelemetryAttributes.fga_client_response_model_id] == "model_123" assert attributes[TelemetryAttributes.http_server_request_duration] == "50" assert attributes[TelemetryAttributes.fga_client_request_client_id] == "client_123" + assert TelemetryAttributes.http_client_request_duration in attributes + assert attributes[TelemetryAttributes.http_client_request_duration] > 0 def test_from_response_with_rest_response(telemetry_attributes): + start_time = time.time() - 5 response = MagicMock(spec=RESTResponse) response.status = 404 response.headers = { @@ -148,13 +154,17 @@ def test_from_response_with_rest_response(telemetry_attributes): configuration=CredentialConfiguration(client_id="client_456"), ) attributes = telemetry_attributes.fromResponse( - response=response, credentials=credentials + response=response, + credentials=credentials, + start=start_time, ) assert attributes[TelemetryAttributes.http_response_status_code] == 404 assert attributes[TelemetryAttributes.fga_client_response_model_id] == "model_404" assert attributes[TelemetryAttributes.http_server_request_duration] == "100" assert attributes[TelemetryAttributes.fga_client_request_client_id] == "client_456" + assert TelemetryAttributes.http_client_request_duration in attributes + assert attributes[TelemetryAttributes.http_client_request_duration] > 0 def test_from_body_with_batch_check(telemetry_attributes): body = MagicMock(spec=BatchCheckRequest)