Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(python-sdk): Introduce synchronous OpenFgaClient #225

Merged
merged 7 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ tag-client-python: test-client-python
.PHONY: build-client-python
build-client-python:
make build-client sdk_language=python tmpdir=${TMP_DIR} library="asyncio"
mv ${CLIENTS_OUTPUT_DIR}/fga-python-sdk/openfga_sdk/api/open_fga_api_sync.py ${CLIENTS_OUTPUT_DIR}/fga-python-sdk/openfga_sdk/sync/open_fga_api.py # TODO: Remove on OpenAPI generator v7.1 or higher
make run-in-docker sdk_language=python image=busybox:${BUSYBOX_DOCKER_TAG} command="/bin/sh -c 'patch -p1 /module/openfga_sdk/api/open_fga_api.py /config/clients/python/patches/open_fga_api.py.patch'"
make run-in-docker sdk_language=python image=busybox:${BUSYBOX_DOCKER_TAG} command="/bin/sh -c 'patch -p1 /module/openfga_sdk/sync/open_fga_api.py /config/clients/python/patches/open_fga_api_sync.py.patch'"
make run-in-docker sdk_language=python image=busybox:${BUSYBOX_DOCKER_TAG} command="/bin/sh -c 'patch -p1 /module/docs/OpenFgaApi.md /config/clients/python/patches/OpenFgaApi.md.patch'"
make run-in-docker sdk_language=python image=python:${PYTHON_DOCKER_TAG} command="/bin/sh -c 'python -m pip install pycodestyle==2.10.0 autopep8==2.0.2; autopep8 --in-place --ignore E402 --recursive openfga_sdk; autopep8 --in-place --recursive test'"
make run-in-docker sdk_language=python image=python:${PYTHON_DOCKER_TAG} command="/bin/sh -c 'pip install setuptools wheel && python setup.py sdist bdist_wheel'"
Expand Down Expand Up @@ -161,7 +163,7 @@ run-in-docker:
.EXPORT_ALL_VARIABLES:
.PHONY: build-client
build-client: build-openapi
SDK_LANGUAGE="${sdk_language}" TMP_DIR="${tmpdir}" LIBRARY_TEMPLATE="${library}" \
SDK_LANGUAGE="${sdk_language}" TMP_DIR="${tmpdir}" LIBRARY_TEMPLATE="${library}"\
./scripts/build_client.sh

.PHONY: build-openapi
Expand Down
6 changes: 6 additions & 0 deletions config/clients/python/.openapi-generator-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ test/*
!test/test_open_fga_api.py
!test/test_credentials.py
!test/test_client.py
!test/test_client_sync.py
!test/test_open_fga_api_sync.py
!test/test_validation.py
!test/test_oauth2.py
!test/test_oauth2_sync.py
.github/workflows/python.yml
.gitlab-ci.yml
.travis.yml
tox.ini
49 changes: 49 additions & 0 deletions config/clients/python/config.overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,55 @@
"test_validation.mustache": {
"destinationFilename": "test/test_validation.py",
"templateType": "SupportingFiles"
},
"__init__sync.mustache": {
"destinationFilename": "openfga_sdk/sync/__init__.py",
"templateType": "SupportingFiles"
},
"__init__sync_client.mustache": {
"destinationFilename": "openfga_sdk/sync/client/__init__.py",
"templateType": "SupportingFiles"
},
"rest_sync.mustache": {
"destinationFilename": "openfga_sdk/sync/rest.py",
"templateType": "SupportingFiles"
},
"api_client_sync.mustache": {
"destinationFilename": "openfga_sdk/sync/api_client.py",
"templateType": "SupportingFiles"
},
"api_sync.mustache": {
"folder": "openfga_sdk/sync",
"destinationFilename": "_sync.py",
"templateType": "API"
},
"api_test_sync.mustache": {
"destinationFilename": "test/test_open_fga_api_sync.py",
"templateType": "SupportingFiles"
},
"client/client_sync.mustache": {
"destinationFilename": "openfga_sdk/sync/client/client.py",
"templateType": "SupportingFiles"
},
"client/test_client_sync.mustache": {
"destinationFilename": "test/test_client_sync.py",
"templateType": "SupportingFiles"
},
"oauth2.mustache": {
"destinationFilename": "openfga_sdk/oauth2.py",
"templateType": "SupportingFiles"
},
"oauth2_test.mustache": {
"destinationFilename": "test/test_oauth2.py",
"templateType": "SupportingFiles"
},
"oauth2_sync.mustache": {
"destinationFilename": "openfga_sdk/sync/oauth2.py",
"templateType": "SupportingFiles"
},
"oauth2_test_sync.mustache": {
"destinationFilename": "test/test_oauth2_sync.py",
"templateType": "SupportingFiles"
}
}
}
4 changes: 2 additions & 2 deletions config/clients/python/patches/open_fga_api.py.patch
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
--- clients/fga-python-sdk/openfga_sdk/api/open_fga_api.py 2022-09-13 14:15:46.000000000 -0400
+++ open_fga_api.py 2022-09-13 14:14:01.000000000 -0400
@@ -193,13 +193,15 @@
collection_formats=collection_formats,
_request_auth=local_var_params.get('_request_auth')))
_request_auth=local_var_params.get('_request_auth'),
_oauth2_client=self._oauth2_client))

- async def create_store(self, **kwargs): # noqa: E501
+ async def create_store(self, body, **kwargs): # noqa: E501
Expand Down
68 changes: 68 additions & 0 deletions config/clients/python/patches/open_fga_api_sync.py.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
--- clients/fga-python-sdk/openfga_sdk/sync/open_fga_api.py 2022-09-13 14:15:46.000000000 -0400
+++ open_fga_api.py 2022-09-13 14:14:01.000000000 -0400
@@ -193,13 +193,15 @@
_request_auth=local_var_params.get('_request_auth'),
_oauth2_client=self._oauth2_client)

- def create_store(self, **kwargs): # noqa: E501
+ def create_store(self, body, **kwargs): # noqa: E501
"""Create a store # noqa: E501

Create a unique OpenFGA store which will be used to store authorization models and relationship tuples. # noqa: E501

- >>> thread = api.create_store()
+ >>> thread = api.create_store(body)

+ :param body: (required)
+ :type body: CreateStoreRequest
:param async_req: Whether to execute the request asynchronously.
:type async_req: bool, optional
:param _preload_content: if False, the urllib3.HTTPResponse object will
@@ -216,15 +218,17 @@
:rtype: CreateStoreResponse
"""
kwargs['_return_http_data_only'] = True
- return self.create_store_with_http_info(**kwargs) # noqa: E501
+ return self.create_store_with_http_info(body, **kwargs) # noqa: E501

- def create_store_with_http_info(self, **kwargs): # noqa: E501
+ def create_store_with_http_info(self, body, **kwargs): # noqa: E501
"""Create a store # noqa: E501

Create a unique OpenFGA store which will be used to store authorization models and relationship tuples. # noqa: E501

- >>> thread = api.create_store_with_http_info()
+ >>> thread = api.create_store_with_http_info(body)

+ :param body: (required)
+ :type body: CreateStoreRequest
:param async_req: Whether to execute the request asynchronously.
:type async_req: bool, optional
:param _return_http_data_only: response data without head status code
@@ -253,6 +257,8 @@

all_params = [

+ 'body'
+
]
all_params.extend(
[
@@ -312,7 +318,7 @@
}

return self.api_client.call_api(
- '/stores'.replace('{store_id}', store_id), 'POST',
+ '/stores', 'POST',
path_params,
query_params,
header_params,
@@ -998,7 +1004,7 @@
}

return self.api_client.call_api(
- '/stores'.replace('{store_id}', store_id), 'GET',
+ '/stores', 'GET',
path_params,
query_params,
header_params,
7 changes: 7 additions & 0 deletions config/clients/python/template/__init__sync.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# coding: utf-8

# flake8: noqa
{{>partial_header}}

from {{packageName}}.sync.client.client import OpenFgaClient
from {{packageName}}.sync.api_client import ApiClient
4 changes: 4 additions & 0 deletions config/clients/python/template/__init__sync_client.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# coding: utf-8

# flake8: noqa
{{>partial_header}}
28 changes: 23 additions & 5 deletions config/clients/python/template/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import re # noqa: F401
import six

from {{packageName}}.api_client import ApiClient
from {{packageName}}.oauth2 import OAuth2Client
from {{packageName}}.exceptions import ( # noqa: F401
FgaValidationException,
ApiValueError
Expand All @@ -28,7 +29,13 @@ class {{classname}}(object):
api_client = ApiClient()
self.api_client = api_client

{{#asyncio}}
self._oauth2_client = None
if api_client.configuration is not None:
credentials = api_client.configuration.credentials
if credentials is not None and credentials.method == 'client_credentials':
self._oauth2_client = OAuth2Client(credentials)

{{#asyncio}}
async def __aenter__(self):
return self

Expand All @@ -37,7 +44,17 @@ class {{classname}}(object):

async def close(self):
await self.api_client.close()
{{/asyncio}}
{{/asyncio}}
{{^asyncio}}
def __enter__(self):
return self

def __exit__(self):
self.close()

def close(self):
self.api_client.close()
{{/asyncio}}

{{#operation}}

Expand All @@ -49,10 +66,10 @@ class {{classname}}(object):
{{/notes}}

{{#sortParamsByRequiredFlag}}
>>> thread = await api.{{operationId}}({{#allParams}}{{#required}}{{^-first}}{{paramName}}{{^-last}}, {{/-last}}{{/-first}}{{/required}}{{/allParams}})
>>> thread = {{#asyncio}}await {{/asyncio}}api.{{operationId}}({{#allParams}}{{#required}}{{^-first}}{{paramName}}{{^-last}}, {{/-last}}{{/-first}}{{/required}}{{/allParams}})
{{/sortParamsByRequiredFlag}}
{{^sortParamsByRequiredFlag}}
>>> thread = await api.{{operationId}}({{#allParams}}{{#required}}{{^-first}}{{paramName}}={{paramName}}_value{{^-last}}, {{/-last}} {{/-first}}{{/required}}{{/allParams}})
>>> thread = {{#asyncio}}await {{/asyncio}}api.{{operationId}}({{#allParams}}{{#required}}{{^-first}}{{paramName}}={{paramName}}_value{{^-last}}, {{/-last}} {{/-first}}{{/required}}{{/allParams}})
{{/sortParamsByRequiredFlag}}

{{#requiredParams}}
Expand Down Expand Up @@ -326,6 +343,7 @@ class {{classname}}(object):
_host=local_var_host,
{{/servers.0}}
collection_formats=collection_formats,
_request_auth=local_var_params.get('_request_auth')){{#asyncio}}){{/asyncio}}
_request_auth=local_var_params.get('_request_auth'),
_oauth2_client=self._oauth2_client){{#asyncio}}){{/asyncio}}
{{/operation}}
{{/operations}}
39 changes: 27 additions & 12 deletions config/clients/python/template/api_client.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
{{#asyncio}}
import asyncio
{{/asyncio}}
{{^asyncio}}
import time
{{/asyncio}}
import atexit
import datetime
from dateutil.parser import parse
Expand All @@ -25,7 +28,7 @@ import tornado.gen

from {{packageName}}.configuration import Configuration
import {{modelPackage}}
from {{packageName}} import rest
from {{packageName}} import rest, oauth2
from {{packageName}}.exceptions import ApiValueError, ApiException, FgaValidationException, RateLimitExceededError


Expand Down Expand Up @@ -149,7 +152,7 @@ class ApiClient(object):
response_types_map=None, auth_settings=None,
_return_http_data_only=None, collection_formats=None,
_preload_content=True, _request_timeout=None, _host=None,
_request_auth=None, _retry_params=None):
_request_auth=None, _retry_params=None, _oauth2_client=None):

self.configuration.is_valid()
config = self.configuration
Expand Down Expand Up @@ -192,7 +195,7 @@ class ApiClient(object):
# auth setting
{{#asyncio}}await {{/asyncio}}self.update_params_for_auth(
header_params, query_params, auth_settings,
request_auth=_request_auth)
request_auth=_request_auth, oauth2_client=_oauth2_client)

# body
if body:
Expand Down Expand Up @@ -222,7 +225,8 @@ class ApiClient(object):
_request_timeout=_request_timeout)
except RateLimitExceededError as e:
if x < max_retry:
{{#asyncio}}await asyncio{{/asyncio}}.sleep(random_time(x, min_wait_in_ms))
{{#asyncio}}await asyncio.sleep(random_time(x, min_wait_in_ms)){{/asyncio}}
{{^asyncio}}time.sleep(random_time(x, min_wait_in_ms)){{/asyncio}}
continue
e.body = e.body.decode('utf-8') if six.PY3 else e.body
response_type = response_types_map.get(e.status, None)
Expand Down Expand Up @@ -389,7 +393,7 @@ class ApiClient(object):
async_req=None, _return_http_data_only=None,
collection_formats=None,_preload_content=True,
_request_timeout=None, _host=None, _request_auth=None,
_retry_params=None):
_retry_params=None, _oauth2_client=None):
"""Makes the HTTP request (synchronous) and returns deserialized data.

To make an async_req request, set the async_req parameter.
Expand Down Expand Up @@ -437,7 +441,7 @@ class ApiClient(object):
response_types_map, auth_settings,
_return_http_data_only, collection_formats,
_preload_content, _request_timeout, _host,
_request_auth, _retry_params){{#asyncio}}){{/asyncio}}
_request_auth, _retry_params, _oauth2_client){{#asyncio}}){{/asyncio}}

return self.pool.apply_async(self.__call_api, (resource_path,
method, path_params,
Expand All @@ -450,7 +454,9 @@ class ApiClient(object):
collection_formats,
_preload_content,
_request_timeout,
_host, _request_auth, _retry_params))
_host, _request_auth,
_retry_params,
_oauth2_client))

{{#asyncio}}async {{/asyncio}}def request(self, method, url, query_params=None, headers=None,
post_params=None, body=None, _preload_content=True,
Expand Down Expand Up @@ -581,19 +587,28 @@ class ApiClient(object):
return content_types[0]

{{#asyncio}}async {{/asyncio}}def update_params_for_auth(self, headers, queries, auth_settings,
request_auth=None):
request_auth=None, oauth2_client=None):
"""Updates header and query params based on authentication setting.

:param headers: Header parameters dict to be updated.
:param queries: Query parameters tuple list to be updated.
:param auth_settings: Authentication setting identifiers list.
:param request_auth: if set, the provided settings will
override the token in the configuration.
:param oauth2_client: if set, will be used for credential exchange.
"""
if self.configuration.credentials is not None:
added_headers = {{#asyncio}}await {{/asyncio}}self.configuration.credentials.get_authentication_header(self.rest_client)
for key, value in added_headers.items():
headers[key] = value
credentials = self.configuration.credentials
if credentials is not None:
if credentials.method == 'none':
pass
if credentials.method == 'api_token':
headers['Authorization'] = 'Bearer {}'.format(credentials.configuration.api_token)
if credentials.method == 'client_credentials':
if oauth2_client is None:
oauth2_client = oauth2.OAuth2Client(credentials)
oauth2_headers = await oauth2_client.get_authentication_header(self.rest_client)
for key, value in oauth2_headers.items():
headers[key] = value

if not auth_settings:
return
Expand Down
Loading
Loading