Skip to content

Commit

Permalink
fix(python-sdk): address misc issues, add example (#265)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhamzeh authored Jan 2, 2024
2 parents bd1e59a + 8788c97 commit 21d92cc
Show file tree
Hide file tree
Showing 15 changed files with 488 additions and 7 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
make test-client-python OPEN_API_REF=0f1d73e766d26ac5c004383d741ee0f815c9b1e6 # TODO: Remove OPEN_API_REF after support for conditions
- name: Check for SDK changes
run: ./scripts/commit_push_changes.sh
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Main config
OPENFGA_DOCKER_TAG = v1.4.0
OPEN_API_URL = https://raw.githubusercontent.com/openfga/api/main/docs/openapiv2/apidocs.swagger.json
OPEN_API_REF ?= main
OPEN_API_URL = https://raw.githubusercontent.com/openfga/api/${OPEN_API_REF}/docs/openapiv2/apidocs.swagger.json
OPENAPI_GENERATOR_CLI_DOCKER_TAG = v6.4.0
NODE_DOCKER_TAG = 20-alpine
GO_DOCKER_TAG = 1
Expand Down
7 changes: 7 additions & 0 deletions config/clients/python/CHANGELOG.md.mustache
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## v0.3.3

### [0.3.3](https://{{gitHost}}/{{gitUserId}}/{{gitRepoId}}/compare/v0.3.2...v0.3.3) (2024-01-02)
- fix: correct type hints for list_relations
- fix: handle empty TupleKey in read
- chore: add example project

## v0.3.2

### [0.3.2](https://{{gitHost}}/{{gitUserId}}/{{gitRepoId}}/compare/v0.3.1...v0.3.2) (2023-12-15)
Expand Down
30 changes: 29 additions & 1 deletion config/clients/python/config.overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"sdkId": "python",
"gitRepoId": "python-sdk",
"packageName": "openfga_sdk",
"packageVersion": "0.3.2",
"packageVersion": "0.3.3",
"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",
Expand Down Expand Up @@ -149,6 +149,34 @@
},
"help.py": {
"destinationFilename": "openfga_sdk/help.py"
},
"example/Makefile": {
"destinationFilename": "example/Makefile",
"templateType": "SupportingFiles"
},
"example/README.md": {
"destinationFilename": "example/README.md",
"templateType": "SupportingFiles"
},
"example/example1/auth-model.json": {
"destinationFilename": "example/example1/auth-model.json",
"templateType": "SupportingFiles"
},
"example/example1/example1.py": {
"destinationFilename": "example/example1/example1.py",
"templateType": "SupportingFiles"
},
"example/example1/requirements.txt": {
"destinationFilename": "example/example1/requirements.txt",
"templateType": "SupportingFiles"
},
"example/example1/setup.py": {
"destinationFilename": "example/example1/setup.py",
"templateType": "SupportingFiles"
},
"example/example1/setup.cfg": {
"destinationFilename": "example/example1/setup.cfg",
"templateType": "SupportingFiles"
}
}
}
10 changes: 8 additions & 2 deletions config/clients/python/template/client/client.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ from {{packageName}}.client.models.write_request import ClientWriteRequest
from {{packageName}}.client.models.write_response import ClientWriteResponse
from {{packageName}}.client.models.expand_request import ClientExpandRequest
from {{packageName}}.client.models.list_objects_request import ClientListObjectsRequest
from {{packageName}}.client.models.list_relations_request import ClientListRelationsRequest
from {{packageName}}.client.models.write_single_response import construct_write_single_response
from {{packageName}}.client.models.write_transaction_opts import WriteTransactionOpts
from {{packageName}}.client.models.read_changes_request import ClientReadChangesRequest
Expand Down Expand Up @@ -352,9 +353,14 @@ class OpenFgaClient():
options.pop("continuation_token")
kwargs = options_to_kwargs(options)

if body is None or (body.object is None and body.relation is None and body.user is None):
tuple_key = None
else:
tuple_key = body

api_response = {{#asyncio}}await {{/asyncio}}self._api.read(
ReadRequest(
tuple_key=body,
tuple_key=tuple_key,
page_size=page_size,
continuation_token=continuation_token,
),
Expand Down Expand Up @@ -623,7 +629,7 @@ class OpenFgaClient():
)
return api_response

{{#asyncio}}async {{/asyncio}}def list_relations(self, body: ClientListObjectsRequest, options: dict[str, str] = None): # noqa: E501
{{#asyncio}}async {{/asyncio}}def list_relations(self, body: ClientListRelationsRequest, options: dict[str, str] = None): # noqa: E501
"""
Return all the relations for which user has a relationship with the object
:param body - list relation request
Expand Down
10 changes: 8 additions & 2 deletions config/clients/python/template/client/client_sync.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ from {{packageName}}.client.models.write_request import ClientWriteRequest
from {{packageName}}.client.models.write_response import ClientWriteResponse
from {{packageName}}.client.models.expand_request import ClientExpandRequest
from {{packageName}}.client.models.list_objects_request import ClientListObjectsRequest
from {{packageName}}.client.models.list_relations_request import ClientListRelationsRequest
from {{packageName}}.client.models.write_single_response import construct_write_single_response
from {{packageName}}.client.models.write_transaction_opts import WriteTransactionOpts
from {{packageName}}.client.models.read_changes_request import ClientReadChangesRequest
Expand Down Expand Up @@ -338,9 +339,14 @@ class OpenFgaClient():
options.pop("continuation_token")
kwargs = options_to_kwargs(options)

if body is None or (body.object is None and body.relation is None and body.user is None):
tuple_key = None
else:
tuple_key = body

api_response = self._api.read(
ReadRequest(
tuple_key=body,
tuple_key=tuple_key,
page_size=page_size,
continuation_token=continuation_token,
),
Expand Down Expand Up @@ -593,7 +599,7 @@ class OpenFgaClient():
)
return api_response

def list_relations(self, body: ClientListObjectsRequest, options: dict[str, str] = None): # noqa: E501
def list_relations(self, body: ClientListRelationsRequest, options: dict[str, str] = None): # noqa: E501
"""
Return all the relations for which user has a relationship with the object
:param body - list relation request
Expand Down
48 changes: 48 additions & 0 deletions config/clients/python/template/client/test_client.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,54 @@ class TestOpenFgaClient(IsolatedAsyncioTestCase):
_request_timeout=None
)


@patch.object(rest.RESTClientObject, 'request')
{{#asyncio}}async {{/asyncio}}def test_read_empty_body(self, mock_request):
"""Test case for read with empty body

Get tuples from the store that matches a query, without following userset rewrite rules # noqa: E501
"""
response_body = '''
{
"tuples": [
{
"key": {
"user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
"relation": "reader",
"object": "document:2021-budget"
},
"timestamp": "2021-10-06T15:32:11.128Z"
}
]
}
'''
mock_request.return_value = mock_response(response_body, 200)
configuration = self.configuration
configuration.store_id = store_id
# Enter a context with an instance of the API client
{{#asyncio}}async {{/asyncio}}with OpenFgaClient(configuration) as api_client:
body = TupleKey()
api_response = {{#asyncio}}await {{/asyncio}}api_client.read(
body=body,
options={}
)
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)])
self.assertEqual(api_response, expected_data)
mock_request.assert_called_once_with(
'POST',
'http://api.{{sampleApiDomain}}/stores/01YCP46JKYM8FJCQ37NMBYHE5X/read',
headers=ANY,
query_params=[],
post_params=[],
body={},
_preload_content=ANY,
_request_timeout=None
)

@patch.object(rest.RESTClientObject, 'request')
{{#asyncio}}async {{/asyncio}}def test_write(self, mock_request):
"""Test case for write
Expand Down
48 changes: 48 additions & 0 deletions config/clients/python/template/client/test_client_sync.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,54 @@ class TestOpenFgaClient(IsolatedAsyncioTestCase):
_request_timeout=None
)


@patch.object(rest.RESTClientObject, 'request')
def test_read_empty_body(self, mock_request):
"""Test case for read with empty body

Get tuples from the store that matches a query, without following userset rewrite rules # noqa: E501
"""
response_body = '''
{
"tuples": [
{
"key": {
"user": "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
"relation": "reader",
"object": "document:2021-budget"
},
"timestamp": "2021-10-06T15:32:11.128Z"
}
]
}
'''
mock_request.return_value = mock_response(response_body, 200)
configuration = self.configuration
configuration.store_id = store_id
# Enter a context with an instance of the API client
with OpenFgaClient(configuration) as api_client:
body = TupleKey()
api_response = api_client.read(
body=body,
options={}
)
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)])
self.assertEqual(api_response, expected_data)
mock_request.assert_called_once_with(
'POST',
'http://api.{{sampleApiDomain}}/stores/01YCP46JKYM8FJCQ37NMBYHE5X/read',
headers=ANY,
query_params=[],
post_params=[],
body={},
_preload_content=ANY,
_request_timeout=None
)

@patch.object(rest.RESTClientObject, 'request')
def test_write(self, mock_request):
"""Test case for write
Expand Down
11 changes: 11 additions & 0 deletions config/clients/python/template/example/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
all: run

project_name=example1
openfga_version=latest

run:
python example1/example1.py

run-openfga:
docker pull docker.io/openfga/openfga:${openfga_version} && \
docker run -p 8080:8080 docker.io/openfga/openfga:${openfga_version} run
32 changes: 32 additions & 0 deletions config/clients/python/template/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## Examples of using the OpenFGA Python SDK

A set of examples on how to call the OpenFGA Python SDK

### Examples
Example 1:
A bare-bones example. It creates a store, and runs a set of calls against it including creating a model, writing tuples and checking for access.

### Running the Examples

Prerequisites:
- `docker`
- `make`
- `python` 3.11+

#### Run using a published SDK

Steps:
1. Clone/Copy the example folder
2. If you have an OpenFGA server running, you can use it, otherwise run `make run-openfga` to spin up an instance (you'll need to switch to a different terminal after - don't forget to close it when done)
3. Run `make run` to run the example

#### Run using a local unpublished SDK build

Steps:
1. Build the SDK
2. Change to the example directory
3. Install the local SDK `pip install -e $LOCAL_DIR`
- For example, if your local SDK is located at `$HOME/projects/python-sdk`, run `pip install -e $HOME/projects/python-sdk`
- For advanced users: The above just updates `requirements.txt` and the source of openfga-sdk. If you know how to manually edit this, feel free to do that as well.
4. If you have an OpenFGA server running, you can use it, otherwise run `make run-openfga` to spin up an instance (you'll need to switch to a different terminal after - don't forget to close it when done)
5. Run `make run` to run the example
73 changes: 73 additions & 0 deletions config/clients/python/template/example/example1/auth-model.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "document",
"relations": {
"reader": {
"this": {}
},
"writer": {
"this": {}
},
"owner": {
"this": {}
}
},
"metadata": {
"relations": {
"reader": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"writer": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"owner": {
"directly_related_user_types": [
{
"type": "user"
}
]
},
"conditional_reader": {
"directly_related_user_types": [
{
"condition": "name_starts_with_a",
"type": "user"
}
]
}
}
}
}
],
"conditions": {
"ViewCountLessThan200": {
"name": "ViewCountLessThan200",
"expression": "ViewCount < 200",
"parameters": {
"ViewCount": {
"type_name": "TYPE_NAME_INT"
},
"Type": {
"type_name": "TYPE_NAME_STRING"
},
"Name": {
"type_name": "TYPE_NAME_STRING"
}
}
}
}
}

Loading

0 comments on commit 21d92cc

Please sign in to comment.