diff --git a/README.md b/README.md index f306a03b..a52039a8 100644 --- a/README.md +++ b/README.md @@ -129,8 +129,7 @@ from openfga_sdk import ClientConfiguration, OpenFgaClient async def main(): configuration = ClientConfiguration( - api_scheme=FGA_API_SCHEME, # optional, defaults to "https" - api_host=FGA_API_HOST, # required, define without the scheme (e.g. api.fga.example instead of https://api.fga.example) + api_url=FGA_API_URL, # required store_id=FGA_STORE_ID, # optional, not needed when calling `CreateStore` or `ListStores` authorization_model_id=FGA_AUTHORIZATION_MODEL_ID, # Optional, can be overridden per request ) @@ -150,8 +149,7 @@ from openfga_sdk.credentials import CredentialConfiguration, Credentials async def main(): configuration = ClientConfiguration( - api_scheme=FGA_API_SCHEME, # optional, defaults to "https" - api_host=FGA_API_HOST, # required, define without the scheme (e.g. api.fga.example instead of https://api.fga.example) + api_url=FGA_API_URL, # required store_id=FGA_STORE_ID, # optional, not needed when calling `CreateStore` or `ListStores` authorization_model_id=FGA_AUTHORIZATION_MODEL_ID, # Optional, can be overridden per request credentials=Credentials( @@ -177,8 +175,7 @@ from openfga_sdk.credentials import Credentials, CredentialConfiguration async def main(): configuration = ClientConfiguration( - api_scheme=FGA_API_SCHEME, # optional, defaults to "https" - api_host=FGA_API_HOST, # required, define without the scheme (e.g. api.fga.example instead of https://api.fga.example) + api_url=FGA_API_URL, # required store_id=FGA_STORE_ID, # optional, not needed when calling `CreateStore` or `ListStores` authorization_model_id=FGA_AUTHORIZATION_MODEL_ID, # Optional, can be overridden per request credentials=Credentials( @@ -196,7 +193,6 @@ async def main(): api_response = await fga_client.read_authorization_models() await fga_client.close() return api_response - ``` #### Synchronous Client @@ -206,14 +202,12 @@ from `openfga_sdk.sync` that supports all the credential types and calls, without requiring async/await. ```python -from openfga_sdk import ClientConfiguration -from openfga_sdk.sync import OpenFgaClient +from openfga_sdk.sync import ClientConfiguration, OpenFgaClient def main(): configuration = ClientConfiguration( - api_scheme=FGA_API_SCHEME, # optional, defaults to "https" - api_host=FGA_API_HOST, # required, define without the scheme (e.g. api.fga.example instead of https://api.fga.example) + api_url=FGA_API_URL, # required store_id=FGA_STORE_ID, # optional, not needed when calling `CreateStore` or `ListStores` authorization_model_id=FGA_AUTHORIZATION_MODEL_ID, # optional, can be overridden per request ) @@ -221,7 +215,6 @@ def main(): with OpenFgaClient(configuration) as fga_client: api_response = fga_client.read_authorization_models() return api_response - ``` @@ -960,7 +953,6 @@ body = [ClientAssertion( )] response = await fga_client.write_assertions(body, options) - ``` diff --git a/example/example1/example1.py b/example/example1/example1.py index c06f3c5e..9d5b35ab 100644 --- a/example/example1/example1.py +++ b/example/example1/example1.py @@ -22,15 +22,14 @@ async def main(): ) ) - if os.getenv('FGA_API_HOST') is not None: + if os.getenv('FGA_API_URL') is not None: configuration = ClientConfiguration( - api_host=os.getenv('FGA_API_HOST'), + api_url=os.getenv('FGA_API_URL'), credentials=credentials ) else: configuration = ClientConfiguration( - api_scheme='http', - api_host='localhost:8080', + api_url='http://localhost:8080', credentials=credentials ) diff --git a/openfga_sdk/api_client.py b/openfga_sdk/api_client.py index 072500d3..6889d741 100644 --- a/openfga_sdk/api_client.py +++ b/openfga_sdk/api_client.py @@ -191,7 +191,10 @@ async def __call_api( # request url if _host is None: - url = self.configuration.api_scheme + '://' + self.configuration.api_host + resource_path + if self.configuration.api_url != None: + url = self.configuration.api_url + resource_path + else: + url = self.configuration.api_scheme + '://' + self.configuration.api_host + resource_path else: # use server/host defined in path or operation instead url = self.configuration.api_scheme + '://' + _host + resource_path diff --git a/openfga_sdk/client/configuration.py b/openfga_sdk/client/configuration.py index 72186d2f..3717b79f 100644 --- a/openfga_sdk/client/configuration.py +++ b/openfga_sdk/client/configuration.py @@ -25,13 +25,15 @@ def __init__( self, api_scheme="https", api_host=None, + api_url=None, store_id=None, credentials=None, retry_params=None, authorization_model_id=None, ssl_ca_cert=None, ): - super().__init__(api_scheme, api_host, store_id, credentials, retry_params, ssl_ca_cert=ssl_ca_cert) + super().__init__(api_scheme, api_host, api_url, store_id, + credentials, retry_params, ssl_ca_cert=ssl_ca_cert) self._authorization_model_id = authorization_model_id def is_valid(self): diff --git a/openfga_sdk/configuration.py b/openfga_sdk/configuration.py index 1e9df8b7..5527f311 100644 --- a/openfga_sdk/configuration.py +++ b/openfga_sdk/configuration.py @@ -84,7 +84,12 @@ class Configuration(object): Do not edit the class manually. :param api_scheme: Whether connection is 'https' or 'http'. Default as 'https' + .. deprecated:: 0.4.1 + Use `api_url` instead. :param api_host: Base url + .. deprecated:: 0.4.1 + Use `api_url` instead. + :param api_url: str - the URL of the FGA server :param store_id: ID of store for API :param credentials: Configuration for obtaining authentication credential :param retry_params: Retry parameters upon HTTP too many request @@ -136,7 +141,7 @@ class Configuration(object): _default = None - def __init__(self, api_scheme="https", api_host=None, + def __init__(self, api_scheme="https", api_host=None, api_url=None, store_id=None, credentials=None, retry_params=None, @@ -150,6 +155,7 @@ def __init__(self, api_scheme="https", api_host=None, ): """Constructor """ + self._url = api_url self._scheme = api_scheme self._base_path = api_host self._store_id = store_id @@ -499,28 +505,35 @@ def is_valid(self): Verify the configuration is valid. Note that we are only doing basic validation to ensure input is sane. """ - if self.api_host is None or self.api_host == '': - raise FgaValidationException('api_host is required but not configured.') - if self.api_scheme is None or self.api_scheme == '': - raise FgaValidationException('api_scheme is required but not configured.') - combined_url = self.api_scheme + '://' + self.api_host + combined_url = self.api_url + if self.api_url == None: + if self.api_host is None or self.api_host == '': + raise FgaValidationException('api_host is required but not configured.') + if self.api_scheme is None or self.api_scheme == '': + raise FgaValidationException('api_scheme is required but not configured.') + combined_url = self.api_scheme + '://' + self.api_host parsed_url = None try: parsed_url = urlparse(combined_url) except ValueError: - raise ApiValueError('Either api_scheme `{}` or api_host `{}` is invalid'.format( - self.api_scheme, self.api_host)) - if (parsed_url.scheme != 'http' and parsed_url.scheme != 'https'): - raise ApiValueError( - 'api_scheme `{}` must be either `http` or `https`'.format(self.api_scheme)) - if (parsed_url.netloc == ''): - raise ApiValueError('api_host `{}` is invalid'.format(self.api_host)) - if (parsed_url.path != ''): - raise ApiValueError( - 'api_host `{}` is not expected to have path specified'.format(self.api_scheme)) - if (parsed_url.query != ''): - raise ApiValueError( - 'api_host `{}` is not expected to have query specified'.format(self.api_scheme)) + if self.api_url == None: + raise ApiValueError('Either api_scheme `{}` or api_host `{}` is invalid'.format( + self.api_scheme, self.api_host)) + else: + raise ApiValueError('api_url `{}` is invalid'.format( + self.api_url)) + if self.api_url == None: + if (parsed_url.scheme != 'http' and parsed_url.scheme != 'https'): + raise ApiValueError( + 'api_scheme `{}` must be either `http` or `https`'.format(self.api_scheme)) + if (parsed_url.netloc == ''): + raise ApiValueError('api_host `{}` is invalid'.format(self.api_host)) + if (parsed_url.path != ''): + raise ApiValueError( + 'api_host `{}` is not expected to have path specified'.format(self.api_scheme)) + if (parsed_url.query != ''): + raise ApiValueError( + 'api_host `{}` is not expected to have query specified'.format(self.api_scheme)) if self.store_id is not None and self.store_id != "" and is_well_formed_ulid_string(self.store_id) is False: raise FgaValidationException( @@ -549,6 +562,16 @@ def api_host(self, value): """Update configured host""" self._base_path = value + @property + def api_url(self): + """Return api_url""" + return self._url + + @api_url.setter + def api_url(self, value): + """Update configured api_url""" + self._url = value + @property def store_id(self): """Return store id.""" diff --git a/openfga_sdk/sync/api_client.py b/openfga_sdk/sync/api_client.py index cdb59b1e..cf325705 100644 --- a/openfga_sdk/sync/api_client.py +++ b/openfga_sdk/sync/api_client.py @@ -190,7 +190,10 @@ def __call_api( # request url if _host is None: - url = self.configuration.api_scheme + '://' + self.configuration.api_host + resource_path + if self.configuration.api_url != None: + url = self.configuration.api_url + resource_path + else: + url = self.configuration.api_scheme + '://' + self.configuration.api_host + resource_path else: # use server/host defined in path or operation instead url = self.configuration.api_scheme + '://' + _host + resource_path diff --git a/test/test_client.py b/test/test_client.py index d286e305..ed177f3d 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -95,8 +95,7 @@ class TestOpenFgaClient(IsolatedAsyncioTestCase): def setUp(self): self.configuration = ClientConfiguration( - api_scheme='http', - api_host="api.fga.example", + api_url='http://api.fga.example', ) def tearDown(self): diff --git a/test/test_client_sync.py b/test/test_client_sync.py index babd1017..d751d5df 100644 --- a/test/test_client_sync.py +++ b/test/test_client_sync.py @@ -95,8 +95,7 @@ class TestOpenFgaClient(IsolatedAsyncioTestCase): def setUp(self): self.configuration = ClientConfiguration( - api_scheme='http', - api_host="api.fga.example", + api_url='http://api.fga.example', ) def tearDown(self): diff --git a/test/test_open_fga_api.py b/test/test_open_fga_api.py index eaebc2c1..b796e162 100644 --- a/test/test_open_fga_api.py +++ b/test/test_open_fga_api.py @@ -100,8 +100,7 @@ class TestOpenFgaApi(IsolatedAsyncioTestCase): def setUp(self): self.configuration = openfga_sdk.Configuration( - api_scheme='http', - api_host="api.fga.example", + api_url='http://api.fga.example', ) def tearDown(self): @@ -923,6 +922,28 @@ def test_configuration_store_id_invalid(self): ) self.assertRaises(FgaValidationException, configuration.is_valid) + def test_url(self): + """ + Ensure that api_url is set and validated + """ + configuration = openfga_sdk.Configuration( + api_url='http://localhost:8080' + ) + self.assertEqual(configuration.api_url, 'http://localhost:8080') + configuration.is_valid() + + def test_url_with_scheme_and_host(self): + """ + Ensure that api_url takes precedence over api_host and scheme + """ + configuration = openfga_sdk.Configuration( + api_url='http://localhost:8080', + api_host='localhost:8080', + api_scheme='foo' + ) + self.assertEqual(configuration.api_url, 'http://localhost:8080') + configuration.is_valid() # Should not throw and complain about scheme being invalid + async def test_bad_configuration_read_authorization_model(self): """ Test whether FgaValidationException is raised for API (reading authorization models) diff --git a/test/test_open_fga_api_sync.py b/test/test_open_fga_api_sync.py index 546a959d..a270c246 100644 --- a/test/test_open_fga_api_sync.py +++ b/test/test_open_fga_api_sync.py @@ -101,8 +101,7 @@ class TestOpenFgaApiSync(IsolatedAsyncioTestCase): def setUp(self): self.configuration = Configuration( - api_scheme='http', - api_host="api.fga.example", + api_url='http://api.fga.example', ) def tearDown(self): @@ -924,6 +923,28 @@ def test_configuration_store_id_invalid(self): ) self.assertRaises(FgaValidationException, configuration.is_valid) + def test_url(self): + """ + Ensure that api_url is set and validated + """ + configuration = Configuration( + api_url='http://localhost:8080' + ) + self.assertEqual(configuration.api_url, 'http://localhost:8080') + configuration.is_valid() + + def test_url_with_scheme_and_host(self): + """ + Ensure that api_url takes precedence over api_host and scheme + """ + configuration = Configuration( + api_url='http://localhost:8080', + api_host='localhost:8080', + api_scheme='foo' + ) + self.assertEqual(configuration.api_url, 'http://localhost:8080') + configuration.is_valid() # Should not throw and complain about scheme being invalid + async def test_bad_configuration_read_authorization_model(self): """ Test whether FgaValidationException is raised for API (reading authorization models)