Skip to content

Commit

Permalink
feat(python-sdk): add condition support
Browse files Browse the repository at this point in the history
  • Loading branch information
booniepepper committed Jan 4, 2024
1 parent a4efc76 commit 19eb501
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 72 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ jobs:

- name: Run All Tests
run: |-
make test-client-python OPEN_API_REF=0f1d73e766d26ac5c004383d741ee0f815c9b1e6 # TODO: Remove OPEN_API_REF after support for conditions
make test-client-python
- name: Check for SDK changes
run: ./scripts/commit_push_changes.sh
Expand Down
34 changes: 20 additions & 14 deletions config/clients/python/template/api_test.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ from {{packageName}}.models.create_store_request import CreateStoreRequest
from {{packageName}}.models.create_store_response import CreateStoreResponse
from {{packageName}}.models.error_code import ErrorCode
from {{packageName}}.models.expand_request import ExpandRequest
from {{packageName}}.models.expand_request_tuple_key import ExpandRequestTupleKey
from {{packageName}}.models.expand_response import ExpandResponse
from {{packageName}}.models.get_store_response import GetStoreResponse
from {{packageName}}.models.internal_error_code import InternalErrorCode
Expand All @@ -39,12 +40,15 @@ from {{packageName}}.models.read_assertions_response import ReadAssertionsRespon
from {{packageName}}.models.read_authorization_model_response import ReadAuthorizationModelResponse
from {{packageName}}.models.read_changes_response import ReadChangesResponse
from {{packageName}}.models.read_request import ReadRequest
from {{packageName}}.models.read_request_tuple_key import ReadRequestTupleKey
from {{packageName}}.models.read_response import ReadResponse
from {{packageName}}.models.store import Store
from {{packageName}}.models.tuple import Tuple
from {{packageName}}.models.tuple_change import TupleChange
from {{packageName}}.models.tuple_key import TupleKey
from {{packageName}}.models.tuple_keys import TupleKeys
from {{packageName}}.models.tuple_key_without_condition import TupleKeyWithoutCondition
from {{packageName}}.models.write_request_writes import WriteRequestWrites
from {{packageName}}.models.write_request_deletes import WriteRequestDeletes
from {{packageName}}.models.tuple_operation import TupleOperation
from {{packageName}}.models.type_definition import TypeDefinition
from {{packageName}}.models.users import Users
Expand Down Expand Up @@ -207,7 +211,7 @@ class {{#operations}}Test{{classname}}(IsolatedAsyncioTestCase):
{{#asyncio}}async {{/asyncio}}with {{packageName}}.ApiClient(configuration) as api_client:
api_instance = open_fga_api.OpenFgaApi(api_client)
body = ExpandRequest(
tuple_key=TupleKey(
tuple_key=ExpandRequestTupleKey(
object="document:budget",
relation="reader",
),
Expand All @@ -217,8 +221,8 @@ class {{#operations}}Test{{classname}}(IsolatedAsyncioTestCase):
body=body,
)
self.assertIsInstance(api_response, ExpandResponse)
curUsers = Users(users=["user:81684243-9356-4421-8fbf-a4f8d36aa31b"])
leaf = Leaf(users=curUsers)
cur_users = Users(users=["user:81684243-9356-4421-8fbf-a4f8d36aa31b"])
leaf = Leaf(users=cur_users)
node = Node(name="document:budget#reader", leaf=leaf)
userTree = UsersetTree(node)
expected_response = ExpandResponse(userTree)
Expand Down Expand Up @@ -393,7 +397,8 @@ class {{#operations}}Test{{classname}}(IsolatedAsyncioTestCase):
},
"timestamp": "2021-10-06T15:32:11.128Z"
}
]
],
"continuation_token": ""
}
'''
mock_request.return_value = mock_response(response_body, 200)
Expand All @@ -402,7 +407,7 @@ class {{#operations}}Test{{classname}}(IsolatedAsyncioTestCase):
{{#asyncio}}async {{/asyncio}}with {{packageName}}.ApiClient(configuration) as api_client:
api_instance = open_fga_api.OpenFgaApi(api_client)
body = ReadRequest(
tuple_key=TupleKey(
tuple_key=ReadRequestTupleKey(
object="document:2021-budget",
relation="reader",
user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",
Expand All @@ -416,7 +421,8 @@ class {{#operations}}Test{{classname}}(IsolatedAsyncioTestCase):
self.assertIsInstance(api_response, ReadResponse)
key = TupleKey(user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",relation="reader",object="document:2021-budget")
timestamp = datetime.fromisoformat("2021-10-06T15:32:11.128+00:00")
expected_data = ReadResponse(tuples=[Tuple(key=key, timestamp=timestamp)])
expected_data = ReadResponse(
tuples=[Tuple(key=key, timestamp=timestamp)], continuation_token='')
self.assertEqual(api_response, expected_data)
mock_request.assert_called_once_with(
'POST',
Expand Down Expand Up @@ -461,7 +467,7 @@ class {{#operations}}Test{{classname}}(IsolatedAsyncioTestCase):
self.assertIsInstance(api_response, ReadAssertionsResponse)
self.assertEqual(api_response.authorization_model_id, '01G5JAVJ41T49E9TT3SKVS7X1J')
assertion=Assertion(
tuple_key=TupleKey(
tuple_key=TupleKeyWithoutCondition(
object="document:2021-budget",
relation="reader",
user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",
Expand Down Expand Up @@ -635,7 +641,7 @@ class {{#operations}}Test{{classname}}(IsolatedAsyncioTestCase):
# example passing only required values which don't have defaults set

body = WriteRequest(
writes=TupleKeys(
writes=WriteRequestWrites(
tuple_keys=[
TupleKey(
object="document:2021-budget",
Expand Down Expand Up @@ -678,7 +684,7 @@ class {{#operations}}Test{{classname}}(IsolatedAsyncioTestCase):
# example passing only required values which don't have defaults set

body = WriteRequest(
deletes=TupleKeys(
deletes=WriteRequestDeletes(
tuple_keys=[
TupleKey(
object="document:2021-budget",
Expand Down Expand Up @@ -1137,11 +1143,11 @@ class {{#operations}}Test{{classname}}(IsolatedAsyncioTestCase):
self.assertIsInstance(api_response, CheckResponse)
self.assertTrue(api_response.allowed)
# Make sure the API was called with the right data
expectedHeader = urllib3.response.HTTPHeaderDict({'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk {{sdkId}}/{{packageVersion}}', 'Authorization': 'Bearer TOKEN1'})
expected_headers = urllib3.response.HTTPHeaderDict({'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk {{sdkId}}/{{packageVersion}}', 'Authorization': 'Bearer TOKEN1'})
mock_request.assert_called_once_with(
'POST',
'http://api.{{sampleApiDomain}}/stores/01H0H015178Y2V4CX10C2KGHF4/check',
headers=expectedHeader,
headers=expected_headers,
query_params=[],
post_params=[],
body={"tuple_key":{"object":"document:2021-budget","relation":"reader","user":"user:81684243-9356-4421-8fbf-a4f8d36aa31b"}},
Expand Down Expand Up @@ -1178,11 +1184,11 @@ class {{#operations}}Test{{classname}}(IsolatedAsyncioTestCase):
self.assertIsInstance(api_response, CheckResponse)
self.assertTrue(api_response.allowed)
# Make sure the API was called with the right data
expectedHeader = urllib3.response.HTTPHeaderDict({'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk {{sdkId}}/{{packageVersion}}', 'Custom Header': 'custom value'})
expected_headers = urllib3.response.HTTPHeaderDict({'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk {{sdkId}}/{{packageVersion}}', 'Custom Header': 'custom value'})
mock_request.assert_called_once_with(
'POST',
'http://api.{{sampleApiDomain}}/stores/01H0H015178Y2V4CX10C2KGHF4/check',
headers=expectedHeader,
headers=expected_headers,
query_params=[],
post_params=[],
body={"tuple_key":{"object":"document:2021-budget","relation":"reader","user":"user:81684243-9356-4421-8fbf-a4f8d36aa31b"}},
Expand Down
34 changes: 20 additions & 14 deletions config/clients/python/template/api_test_sync.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ from {{packageName}}.models.create_store_request import CreateStoreRequest
from {{packageName}}.models.create_store_response import CreateStoreResponse
from {{packageName}}.models.error_code import ErrorCode
from {{packageName}}.models.expand_request import ExpandRequest
from {{packageName}}.models.expand_request_tuple_key import ExpandRequestTupleKey
from {{packageName}}.models.expand_response import ExpandResponse
from {{packageName}}.models.get_store_response import GetStoreResponse
from {{packageName}}.models.internal_error_code import InternalErrorCode
Expand All @@ -40,12 +41,15 @@ from {{packageName}}.models.read_assertions_response import ReadAssertionsRespon
from {{packageName}}.models.read_authorization_model_response import ReadAuthorizationModelResponse
from {{packageName}}.models.read_changes_response import ReadChangesResponse
from {{packageName}}.models.read_request import ReadRequest
from {{packageName}}.models.read_request_tuple_key import ReadRequestTupleKey
from {{packageName}}.models.read_response import ReadResponse
from {{packageName}}.models.store import Store
from {{packageName}}.models.tuple import Tuple
from {{packageName}}.models.tuple_change import TupleChange
from {{packageName}}.models.tuple_key import TupleKey
from {{packageName}}.models.tuple_keys import TupleKeys
from {{packageName}}.models.tuple_key_without_condition import TupleKeyWithoutCondition
from {{packageName}}.models.write_request_writes import WriteRequestWrites
from {{packageName}}.models.write_request_deletes import WriteRequestDeletes
from {{packageName}}.models.tuple_operation import TupleOperation
from {{packageName}}.models.type_definition import TypeDefinition
from {{packageName}}.models.users import Users
Expand Down Expand Up @@ -208,7 +212,7 @@ class TestOpenFgaApiSync(IsolatedAsyncioTestCase):
with ApiClient(configuration) as api_client:
api_instance = open_fga_api.OpenFgaApi(api_client)
body = ExpandRequest(
tuple_key=TupleKey(
tuple_key=ExpandRequestTupleKey(
object="document:budget",
relation="reader",
),
Expand All @@ -218,8 +222,8 @@ class TestOpenFgaApiSync(IsolatedAsyncioTestCase):
body=body,
)
self.assertIsInstance(api_response, ExpandResponse)
curUsers = Users(users=["user:81684243-9356-4421-8fbf-a4f8d36aa31b"])
leaf = Leaf(users=curUsers)
cur_users = Users(users=["user:81684243-9356-4421-8fbf-a4f8d36aa31b"])
leaf = Leaf(users=cur_users)
node = Node(name="document:budget#reader", leaf=leaf)
userTree = UsersetTree(node)
expected_response = ExpandResponse(userTree)
Expand Down Expand Up @@ -394,7 +398,8 @@ class TestOpenFgaApiSync(IsolatedAsyncioTestCase):
},
"timestamp": "2021-10-06T15:32:11.128Z"
}
]
],
"continuation_token": ""
}
'''
mock_request.return_value = mock_response(response_body, 200)
Expand All @@ -403,7 +408,7 @@ class TestOpenFgaApiSync(IsolatedAsyncioTestCase):
with ApiClient(configuration) as api_client:
api_instance = open_fga_api.OpenFgaApi(api_client)
body = ReadRequest(
tuple_key=TupleKey(
tuple_key=ReadRequestTupleKey(
object="document:2021-budget",
relation="reader",
user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",
Expand All @@ -417,7 +422,8 @@ class TestOpenFgaApiSync(IsolatedAsyncioTestCase):
self.assertIsInstance(api_response, ReadResponse)
key = TupleKey(user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",relation="reader",object="document:2021-budget")
timestamp = datetime.fromisoformat("2021-10-06T15:32:11.128+00:00")
expected_data = ReadResponse(tuples=[Tuple(key=key, timestamp=timestamp)])
expected_data = ReadResponse(
tuples=[Tuple(key=key, timestamp=timestamp)], continuation_token='')
self.assertEqual(api_response, expected_data)
mock_request.assert_called_once_with(
'POST',
Expand Down Expand Up @@ -462,7 +468,7 @@ class TestOpenFgaApiSync(IsolatedAsyncioTestCase):
self.assertIsInstance(api_response, ReadAssertionsResponse)
self.assertEqual(api_response.authorization_model_id, '01G5JAVJ41T49E9TT3SKVS7X1J')
assertion=Assertion(
tuple_key=TupleKey(
tuple_key=TupleKeyWithoutCondition(
object="document:2021-budget",
relation="reader",
user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",
Expand Down Expand Up @@ -636,7 +642,7 @@ class TestOpenFgaApiSync(IsolatedAsyncioTestCase):
# example passing only required values which don't have defaults set

body = WriteRequest(
writes=TupleKeys(
writes=WriteRequestWrites(
tuple_keys=[
TupleKey(
object="document:2021-budget",
Expand Down Expand Up @@ -679,7 +685,7 @@ class TestOpenFgaApiSync(IsolatedAsyncioTestCase):
# example passing only required values which don't have defaults set

body = WriteRequest(
deletes=TupleKeys(
deletes=WriteRequestDeletes(
tuple_keys=[
TupleKey(
object="document:2021-budget",
Expand Down Expand Up @@ -1138,11 +1144,11 @@ class TestOpenFgaApiSync(IsolatedAsyncioTestCase):
self.assertIsInstance(api_response, CheckResponse)
self.assertTrue(api_response.allowed)
# Make sure the API was called with the right data
expectedHeader = urllib3.response.HTTPHeaderDict({'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk {{sdkId}}/{{packageVersion}}', 'Authorization': 'Bearer TOKEN1'})
expected_headers = urllib3.response.HTTPHeaderDict({'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk {{sdkId}}/{{packageVersion}}', 'Authorization': 'Bearer TOKEN1'})
mock_request.assert_called_once_with(
'POST',
'http://api.{{sampleApiDomain}}/stores/01H0H015178Y2V4CX10C2KGHF4/check',
headers=expectedHeader,
headers=expected_headers,
query_params=[],
post_params=[],
body={"tuple_key":{"object":"document:2021-budget","relation":"reader","user":"user:81684243-9356-4421-8fbf-a4f8d36aa31b"}},
Expand Down Expand Up @@ -1179,11 +1185,11 @@ class TestOpenFgaApiSync(IsolatedAsyncioTestCase):
self.assertIsInstance(api_response, CheckResponse)
self.assertTrue(api_response.allowed)
# Make sure the API was called with the right data
expectedHeader = urllib3.response.HTTPHeaderDict({'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk {{sdkId}}/{{packageVersion}}', 'Custom Header': 'custom value'})
expected_headers = urllib3.response.HTTPHeaderDict({'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk {{sdkId}}/{{packageVersion}}', 'Custom Header': 'custom value'})
mock_request.assert_called_once_with(
'POST',
'http://api.{{sampleApiDomain}}/stores/01H0H015178Y2V4CX10C2KGHF4/check',
headers=expectedHeader,
headers=expected_headers,
query_params=[],
post_params=[],
body={"tuple_key":{"object":"document:2021-budget","relation":"reader","user":"user:81684243-9356-4421-8fbf-a4f8d36aa31b"}},
Expand Down
18 changes: 10 additions & 8 deletions config/clients/python/template/client/client.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ from {{packageName}}.models.check_request import CheckRequest
from {{packageName}}.models.contextual_tuple_keys import ContextualTupleKeys
from {{packageName}}.models.create_store_request import CreateStoreRequest
from {{packageName}}.models.expand_request import ExpandRequest
from {{packageName}}.models.expand_request_tuple_key import ExpandRequestTupleKey
from {{packageName}}.models.list_objects_request import ListObjectsRequest
from {{packageName}}.models.read_authorization_model_response import ReadAuthorizationModelResponse
from {{packageName}}.models.read_request import ReadRequest
from {{packageName}}.models.read_request_tuple_key import ReadRequestTupleKey
from {{packageName}}.models.tuple_key import TupleKey
from {{packageName}}.models.write_assertions_request import WriteAssertionsRequest
from {{packageName}}.models.write_authorization_model_request import WriteAuthorizationModelRequest
Expand Down Expand Up @@ -67,7 +69,7 @@ def set_heading_if_not_set(options: dict[str, int|str], name: str, value: str):

def options_to_kwargs(options: dict[str, int|str] = None):
"""
Return kargs with continuation_token and page_size
Return kwargs with continuation_token and page_size
"""
kwargs = {}
if options is not None:
Expand Down Expand Up @@ -127,7 +129,7 @@ class OpenFgaClient():
def _get_authorization_model_id(self, options: object) -> str | None:
"""
Return the authorization model ID if specified in the options.
Otherwise return the authorization model ID stored in the client's configuration
Otherwise, return the authorization model ID stored in the client's configuration
"""
authorization_model_id = self._client_configuration.authorization_model_id
if options is not None and "authorization_model_id" in options:
Expand All @@ -153,13 +155,13 @@ class OpenFgaClient():

def set_authorization_model_id(self, value):
"""
Update the authorizaiton model id in the configuration
Update the authorization model id in the configuration
"""
self._client_configuration.authorization_model_id = value

def get_authorization_model_id(self):
"""
Return the authorizaiton model id
Return the authorization model id
"""
return self._client_configuration.authorization_model_id

Expand Down Expand Up @@ -187,7 +189,7 @@ class OpenFgaClient():
:param retryParams.maxRetry(options) - Override the max number of retries on each API request
:param retryParams.minWaitInMs(options) - Override the minimum wait before a retry is initiated
"""
# convert options to kargs
# convert options to kwargs
options = set_heading_if_not_set(options, CLIENT_METHOD_HEADER, "ListStores")
kwargs = options_to_kwargs(options)
api_response = {{#asyncio}}await {{/asyncio}}self._api.list_stores(
Expand Down Expand Up @@ -296,7 +298,7 @@ class OpenFgaClient():

{{#asyncio}}async {{/asyncio}}def read_latest_authorization_model(self, options: dict[str, int|str] = None):
"""
Convenient method of reading the latest authorizaiton model
Convenient method of reading the latest authorization model
:param header(options) - Custom headers to send alongside the request
:param retryParams(options) - Override the retry parameters for this request
:param retryParams.maxRetry(options) - Override the max number of retries on each API request
Expand Down Expand Up @@ -330,7 +332,7 @@ class OpenFgaClient():
)
return api_response

{{#asyncio}}async {{/asyncio}}def read(self, body: TupleKey, options: dict[str, str] = None):
{{#asyncio}}async {{/asyncio}}def read(self, body: ReadRequestTupleKey, options: dict[str, str] = None):
"""
Read changes for specified type
:param body - the tuples we want to read
Expand Down Expand Up @@ -588,7 +590,7 @@ class OpenFgaClient():
kwargs = options_to_kwargs(options)

req_body = ExpandRequest(
tuple_key=TupleKey(
tuple_key=ExpandRequestTupleKey(
relation=body.relation,
object=body.object,
),
Expand Down
Loading

0 comments on commit 19eb501

Please sign in to comment.