diff --git a/config/clients/python/template/src/client/configuration.py.mustache b/config/clients/python/template/src/client/configuration.py.mustache index b60efa29..ba26ca5f 100644 --- a/config/clients/python/template/src/client/configuration.py.mustache +++ b/config/clients/python/template/src/client/configuration.py.mustache @@ -19,8 +19,9 @@ class ClientConfiguration(Configuration): authorization_model_id=None, ssl_ca_cert=None, api_url=None, # TODO: restructure when removing api_scheme/api_host + timeout_millisec: int | None = None, ): - super().__init__(api_scheme, api_host, store_id, credentials, retry_params, ssl_ca_cert=ssl_ca_cert, api_url=api_url) + 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) self._authorization_model_id = authorization_model_id def is_valid(self): diff --git a/config/clients/python/template/src/configuration.py.mustache b/config/clients/python/template/src/configuration.py.mustache index 5dd1d4b1..e6ffd03c 100644 --- a/config/clients/python/template/src/configuration.py.mustache +++ b/config/clients/python/template/src/configuration.py.mustache @@ -147,6 +147,7 @@ class Configuration: :param ssl_ca_cert: str - the path to a file of concatenated CA certificates in PEM format :param api_url: str - the URL of the FGA server + :param timeout_millis: int | None - the default timeout in milliseconds for requests """ _default = None @@ -163,7 +164,12 @@ class Configuration: server_operation_index=None, server_operation_variables=None, 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, TelemetryMetricConfiguration | dict[TelemetryAttribute, bool] | None] | None] | None = None): + """Constructor """ self._url = api_url @@ -176,6 +182,8 @@ class Configuration: else: # use the default parameters self._retry_params = RetryParams() + + self._timeout_millisec = timeout_millisec or 5000 * 60 """Default Base url """ self.server_index = 0 @@ -624,6 +632,16 @@ class Configuration: if self._credentials is not None: self._credentials.validate_credentials_config() + if self._timeout_millisec is not None: + if not isinstance(self._timeout_millisec, int): + raise FgaValidationException(f"timeout_millisec unexpected type {self._timeout_millisec}") + + ten_minutes = 10000 * 60 + if self._timeout_millisec < 0 or self._timeout_millisec > ten_minutes: + raise FgaValidationException(f"timeout_millisec not within reasonable range (0,60000), {self._timeout_millisec}") + + + @property def api_scheme(self): """Return connection is https or http.""" @@ -695,6 +713,20 @@ class Configuration: """ self._retry_params = value + @property + def timeout_millisec(self): + """ + Return timeout milliseconds + """ + return self._timeout_millisec + + @timeout_millisec.setter + def timeout_millisec(self, value): + """ + Update timeout milliseconds + """ + self._timeout_millisec = value + @property def disabled_client_side_validations(self): """Return disable_client_side_validations.""" diff --git a/config/clients/python/template/src/rest.py.mustache b/config/clients/python/template/src/rest.py.mustache index 55a910b4..5cdbe8d4 100644 --- a/config/clients/python/template/src/rest.py.mustache +++ b/config/clients/python/template/src/rest.py.mustache @@ -55,6 +55,7 @@ class RESTClientObject: self.proxy = configuration.proxy self.proxy_headers = configuration.proxy_headers + self._timeout_millisec = configuration.timeout_millisec # https pool manager self.pool_manager = aiohttp.ClientSession( @@ -96,7 +97,7 @@ class RESTClientObject: post_params = post_params or {} headers = headers or {} - timeout = _request_timeout or 5 * 60 + timeout = _request_timeout or self._timeout_millisec / 1000 if 'Content-Type' not in headers: headers['Content-Type'] = 'application/json' diff --git a/config/clients/python/template/src/sync/rest.py.mustache b/config/clients/python/template/src/sync/rest.py.mustache index 698db6df..b28edafa 100644 --- a/config/clients/python/template/src/sync/rest.py.mustache +++ b/config/clients/python/template/src/sync/rest.py.mustache @@ -62,6 +62,8 @@ class RESTClientObject: else: maxsize = 4 + self._timeout_millisec = configuration.timeout_millisec + # https pool manager if configuration.proxy: self.pool_manager = urllib3.ProxyManager( @@ -122,7 +124,7 @@ class RESTClientObject: post_params = post_params or {} headers = headers or {} - timeout = None + timeout = urllib3.Timeout(total=self._timeout_millisec / 1000) if _request_timeout: if isinstance(_request_timeout, (float, int)): timeout = urllib3.Timeout(total=_request_timeout) diff --git a/config/clients/python/template/test/api_test.py.mustache b/config/clients/python/template/test/api_test.py.mustache index 5b956649..d77852c1 100644 --- a/config/clients/python/template/test/api_test.py.mustache +++ b/config/clients/python/template/test/api_test.py.mustache @@ -1043,6 +1043,17 @@ class {{#operations}}Test{{classname}}(IsolatedAsyncioTestCase): self.assertEqual(configuration.api_url, 'http://localhost:8080') configuration.is_valid() # Should not throw and complain about scheme being invalid + def test_timeout_millisec(self): + """ + Ensure that timeout_seconds is set and validated + """ + configuration = {{packageName}}.Configuration( + api_url='http://localhost:8080', + timeout_millisec=10000, + ) + self.assertEqual(configuration.timeout_millisec, 10000) + configuration.is_valid() + async def test_bad_configuration_read_authorization_model(self): """ Test whether FgaValidationException is raised for API (reading authorization models) diff --git a/config/clients/python/template/test/configuration_test.py.mustache b/config/clients/python/template/test/configuration_test.py.mustache index 9f745788..d8e4abd6 100644 --- a/config/clients/python/template/test/configuration_test.py.mustache +++ b/config/clients/python/template/test/configuration_test.py.mustache @@ -125,6 +125,7 @@ class TestConfigurationSetDefaultAndGetDefaultCopy: } default_config.ssl_ca_cert = "/path/to/ca_cert.pem" default_config.api_url = "https://{{sampleApiDomain}}/api" + default_config.timeout_millisec = 10000 Configuration.set_default(default_config) assert Configuration._default.api_scheme == "https" @@ -172,6 +173,7 @@ class TestConfigurationSetDefaultAndGetDefaultCopy: } assert Configuration._default.ssl_ca_cert == "/path/to/ca_cert.pem" assert Configuration._default.api_url == "https://{{sampleApiDomain}}/api" + assert Configuration._default.timeout_millisec == 10000 def test_configuration_get_default_copy(self, configuration): default_config = Configuration() @@ -201,6 +203,7 @@ class TestConfigurationSetDefaultAndGetDefaultCopy: } default_config.ssl_ca_cert = "/path/to/ca_cert.pem" default_config.api_url = "https://{{sampleApiDomain}}/api" + default_config.timeout_millisec = 10000 Configuration.set_default(default_config) copied_config = Configuration.get_default_copy() @@ -218,6 +221,7 @@ class TestConfigurationSetDefaultAndGetDefaultCopy: assert copied_config.credentials._api_audience == "audience123" assert copied_config.credentials._api_issuer == "issuer123" assert copied_config.credentials._api_token == "token123" + assert Configuration._default.timeout_millisec == 10000 class TestConfigurationValidityChecks: @@ -350,6 +354,7 @@ class TestConfigurationMiscellaneous: }, ssl_ca_cert="/path/to/ca_cert.pem", api_url="https://{{sampleApiDomain}}/api", + timeout_millisec=10000, ) # Perform deep copy @@ -383,3 +388,4 @@ class TestConfigurationMiscellaneous: ) assert copied_config.ssl_ca_cert == config.ssl_ca_cert assert copied_config.api_url == config.api_url + assert copied_config.timeout_millisec == config.timeout_millisec diff --git a/config/clients/python/template/test/sync/api_test.py.mustache b/config/clients/python/template/test/sync/api_test.py.mustache index 89202e3e..ef7b0f00 100644 --- a/config/clients/python/template/test/sync/api_test.py.mustache +++ b/config/clients/python/template/test/sync/api_test.py.mustache @@ -1049,6 +1049,17 @@ class TestOpenFgaApiSync(IsolatedAsyncioTestCase): self.assertEqual(configuration.api_url, 'http://localhost:8080') configuration.is_valid() # Should not throw and complain about scheme being invalid + def test_timeout_millisec(self): + """ + Ensure that timeout_millisec is set and validated + """ + configuration = Configuration( + api_url='http://localhost:8080', + timeout_millisec=10000, + ) + self.assertEqual(configuration.timeout_millisec, 10000) + configuration.is_valid() + async def test_bad_configuration_read_authorization_model(self): """ Test whether FgaValidationException is raised for API (reading authorization models)