Skip to content

Commit

Permalink
feat(python-sdk): add streamed-list-objects endpoint (#469)
Browse files Browse the repository at this point in the history
  • Loading branch information
evansims authored Jan 23, 2025
2 parents bf4cbed + 6325bbe commit be9320e
Show file tree
Hide file tree
Showing 39 changed files with 5,481 additions and 2,574 deletions.
17 changes: 16 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ tag-client-python: test-client-python

.PHONY: build-client-python
build-client-python:
make build-client sdk_language=python tmpdir=${TMP_DIR} library="asyncio"
make build-client-streamed 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
mv ${CLIENTS_OUTPUT_DIR}/fga-python-sdk/test/test_open_fga_api.py ${CLIENTS_OUTPUT_DIR}/fga-python-sdk/test/api/open_fga_api_test.py
Expand Down Expand Up @@ -182,6 +182,21 @@ build-openapi: init get-openapi-doc
sed -i -e 's/#\/definitions\/Object"/#\/definitions\/FgaObject"/g' ${DOCS_CACHE_DIR}/openfga.openapiv2.json
sed -i -e 's/v1.//g' ${DOCS_CACHE_DIR}/openfga.openapiv2.json

.EXPORT_ALL_VARIABLES:
.PHONY: build-client-streamed
build-client-streamed: build-openapi-streamed
SDK_LANGUAGE="${sdk_language}" TMP_DIR="${tmpdir}" LIBRARY_TEMPLATE="${library}"\
./scripts/build_client.sh

.PHONY: build-openapi-streamed
build-openapi-streamed: init get-openapi-doc
cat "${DOCS_CACHE_DIR}/openfga.openapiv2.raw.json" | \
jq '(.. | .tags? | select(.)) |= ["OpenFga"] | (.tags? | select(.)) |= [{"name":"OpenFga"}] | del(.definitions.ReadTuplesParams, .definitions.ReadTuplesResponse, .paths."/stores/{store_id}/read-tuples")' > \
${DOCS_CACHE_DIR}/openfga.openapiv2.json
sed -i -e 's/"Object"/"FgaObject"/g' ${DOCS_CACHE_DIR}/openfga.openapiv2.json
sed -i -e 's/#\/definitions\/Object"/#\/definitions\/FgaObject"/g' ${DOCS_CACHE_DIR}/openfga.openapiv2.json
sed -i -e 's/v1.//g' ${DOCS_CACHE_DIR}/openfga.openapiv2.json

.PHONY: get-openapi-doc
get-openapi-doc:
mkdir -p "${DOCS_CACHE_DIR}"
Expand Down
29 changes: 28 additions & 1 deletion config/clients/python/config.overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"docPrefix": "https://github.com/openfga/python-sdk/blob/main/",
"pythonMinimumRuntime": "3.10",
"openTelemetryDocumentation": "opentelemetry.md",
"supportsStreamedListObjects": "streamed_list_objects",
"files": {
".snyk": {},

Expand Down Expand Up @@ -55,6 +56,24 @@
"templateType": "SupportingFiles"
},

"example/streamed-list-objects/.env.example": {},
"example/streamed-list-objects/.gitignore": {},
"example/streamed-list-objects/README.md": {},
"example/streamed-list-objects/requirements.txt": {},
"example/streamed-list-objects/setup.cfg": {},
"example/streamed-list-objects/setup.py.mustache": {
"destinationFilename": "example/streamed-list-objects/setup.py",
"templateType": "SupportingFiles"
},
"example/streamed-list-objects/asynchronous.py.mustache": {
"destinationFilename": "example/streamed-list-objects/asynchronous.py",
"templateType": "SupportingFiles"
},
"example/streamed-list-objects/synchronous.py.mustache": {
"destinationFilename": "example/streamed-list-objects/synchronous.py",
"templateType": "SupportingFiles"
},

"src/api/__init__.py.mustache": {
"destinationFilename": "openfga_sdk/api/__init__.py",
"templateType": "SupportingFiles"
Expand Down Expand Up @@ -83,7 +102,7 @@
"destinationFilename": "openfga_sdk/client/models/batch_check_item.py",
"templateType": "SupportingFiles"
},
"src/client/models/batch_check_request.py.mustache": {
"src/client/models/batch_check_request.py.mustache": {
"destinationFilename": "openfga_sdk/client/models/batch_check_request.py",
"templateType": "SupportingFiles"
},
Expand Down Expand Up @@ -272,6 +291,10 @@
"destinationFilename": "test/sync/oauth2_test.py",
"templateType": "SupportingFiles"
},
"test/sync/rest_test.py.mustache": {
"destinationFilename": "test/sync/rest_test.py",
"templateType": "SupportingFiles"
},
"test/telemetry/attributes_test.py.mustache": {
"destinationFilename": "test/telemetry/attributes_test.py",
"templateType": "SupportingFiles"
Expand Down Expand Up @@ -316,6 +339,10 @@
"destinationFilename": "test/_/oauth2_test.py",
"templateType": "SupportingFiles"
},
"test/rest_test.py.mustache": {
"destinationFilename": "test/_/rest_test.py",
"templateType": "SupportingFiles"
},
"test/validation_test.py.mustache": {
"destinationFilename": "test/_/validation_test.py",
"templateType": "SupportingFiles"
Expand Down
27 changes: 27 additions & 0 deletions config/clients/python/template/README_calling_api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,33 @@ response = await fga_client.list_objects(body)
# response.objects = ["document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"]
```

#### Streamed List Objects

List the objects of a particular type a user has access to, using the streaming API.

[API Documentation](https://openfga.dev/api/service#/Relationship%20Queries/StreamedListObjects)

```python
# from openfga_sdk import OpenFgaClient
# from openfga_sdk.client.models import ClientListObjectsRequest

# Initialize the fga_client
# fga_client = OpenFgaClient(configuration)

results = []

documents = ClientListObjectsRequest(
type="document",
relation="writer",
user="user:81684243-9356-4421-8fbf-a4f8d36aa31b",
)

async for response in fga_client.streamed_list_objects(request):
results.append(response)

# results = ["document:...", ...]
```

#### List Relations

List the relations a user has on an object.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import asyncio
import os
import sys
import uuid

from dotenv import load_dotenv

sdk_path = os.path.realpath(os.path.join(os.path.abspath(__file__), "..", "..", ".."))
sys.path.insert(0, sdk_path)

from {{packageName}} import (
ClientConfiguration,
Condition,
Expand Down Expand Up @@ -38,6 +44,8 @@ from {{packageName}}.models.fga_object import FgaObject


async def main():
load_dotenv()

credentials = Credentials()
if os.getenv("FGA_CLIENT_ID") is not None:
credentials = Credentials(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ openfga-sdk >= {{packageVersion}}
python-dateutil >= 2.8.2
urllib3 >= 2.1.0
yarl >= 1.9.4
python-dotenv >= 1, <2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FGA_API_URL="http://localhost:8080"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Streamed List Objects example for OpenFGA's Python SDK

This example demonstrates working with the `POST` `/stores/:id/streamed-list-objects` endpoint in OpenFGA using the Python SDK.

## Prerequisites

If you do not already have an OpenFGA instance running, you can start one using the following command:

```bash
docker run -d -p 8080:8080 openfga/openfga
```

## Configure the example

You may need to configure the example for your environment:

```bash
cp .env.example .env
```

Now edit the `.env` file and set the values as appropriate.

## Running the example

Begin by installing the required dependencies:

```bash
pip install -r requirements.txt
```

Next, run the example. You can use either the synchronous or asynchronous client:

```bash
python asynchronous.py
```

```bash
python synchronous.py
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import asyncio
import json
import os
import sys
from operator import attrgetter
from typing import Any

from dotenv import load_dotenv

sdk_path = os.path.realpath(os.path.join(os.path.abspath(__file__), "..", "..", ".."))
sys.path.insert(0, sdk_path)

from {{packageName}} import (
ClientConfiguration,
OpenFgaClient,
)
from {{packageName}}.client.models import (
ClientListObjectsRequest,
ClientTuple,
ClientWriteRequest,
)
from {{packageName}}.models import CreateStoreRequest


class app:
def __init__(
self,
client: OpenFgaClient = None,
configuration: ClientConfiguration = None,
):
self._client = client
self._configuration = configuration

async def fga_client(self, env: dict[str, str] = {}) -> OpenFgaClient:
if not self._client or not self._configuration:
load_dotenv()

if not self._configuration:
self._configuration = ClientConfiguration(
api_url=os.getenv("FGA_API_URL"),
)

self._client = OpenFgaClient(self._configuration)
return self._client


def unpack(
response,
attr: str,
) -> Any:
return attrgetter(attr)(response)


async def main():
async with await app().fga_client() as fga_client:
# Create a temporary store
store = unpack(
await fga_client.create_store(CreateStoreRequest(name="Test Store")),
"id",
)
print(f"Created temporary store ({store})")
fga_client.set_store_id(store)

# Create a temporary authorization model
model = unpack(
await fga_client.write_authorization_model(
json.loads(
'{"schema_version":"1.1","type_definitions":[{"type":"user","relations":{}},{"type":"group","relations":{"member":{"this":{}}},"metadata":{"relations":{"member":{"directly_related_user_types":[{"type":"user"}]}}}},{"type":"folder","relations":{"can_create_file":{"computedUserset":{"object":"","relation":"owner"}},"owner":{"this":{}},"parent":{"this":{}},"viewer":{"union":{"child":[{"this":{}},{"computedUserset":{"object":"","relation":"owner"}},{"tupleToUserset":{"tupleset":{"object":"","relation":"parent"},"computedUserset":{"object":"","relation":"viewer"}}}]}}},"metadata":{"relations":{"can_create_file":{"directly_related_user_types":[]},"owner":{"directly_related_user_types":[{"type":"user"}]},"parent":{"directly_related_user_types":[{"type":"folder"}]},"viewer":{"directly_related_user_types":[{"type":"user"},{"type":"user","wildcard":{}},{"type":"group","relation":"member"}]}}}},{"type":"document","relations":{"can_change_owner":{"computedUserset":{"object":"","relation":"owner"}},"owner":{"this":{}},"parent":{"this":{}},"can_read":{"union":{"child":[{"computedUserset":{"object":"","relation":"viewer"}},{"computedUserset":{"object":"","relation":"owner"}},{"tupleToUserset":{"tupleset":{"object":"","relation":"parent"},"computedUserset":{"object":"","relation":"viewer"}}}]}},"can_share":{"union":{"child":[{"computedUserset":{"object":"","relation":"owner"}},{"tupleToUserset":{"tupleset":{"object":"","relation":"parent"},"computedUserset":{"object":"","relation":"owner"}}}]}},"viewer":{"this":{}},"can_write":{"union":{"child":[{"computedUserset":{"object":"","relation":"owner"}},{"tupleToUserset":{"tupleset":{"object":"","relation":"parent"},"computedUserset":{"object":"","relation":"owner"}}}]}}},"metadata":{"relations":{"can_change_owner":{"directly_related_user_types":[]},"owner":{"directly_related_user_types":[{"type":"user"}]},"parent":{"directly_related_user_types":[{"type":"folder"}]},"can_read":{"directly_related_user_types":[]},"can_share":{"directly_related_user_types":[]},"viewer":{"directly_related_user_types":[{"type":"user"},{"type":"user","wildcard":{}},{"type":"group","relation":"member"}]},"can_write":{"directly_related_user_types":[]}}}}]}'
)
),
"authorization_model_id",
)
print(f"Created temporary authorization model ({model})")

print(f"Writing 100 mock tuples to store.")

# Write mock data
writes = []
for x in range(0, 100):
writes.append(
ClientTuple(
user="user:anne",
relation="owner",
object=f"document:{x}",
)
)

await fga_client.write(
ClientWriteRequest(writes),
{
"authorization_model_id": model,
},
)

print("Listing objects using streaming endpoint:")
results = []

request = ClientListObjectsRequest(
type="document",
relation="owner",
user="user:anne",
)

async for response in fga_client.streamed_list_objects(request):
print(f" {response}")
results.append(response)

print(f"API returned {results.__len__()} objects.")

# Delete the temporary store
try:
await fga_client.delete_store()
print(f"Deleted temporary store ({store})")
except:
pass

print("Finished.")


if __name__ == "__main__":
asyncio.run(main())
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python-dotenv >= 1, <2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
max-line-length=99
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
Python SDK for OpenFGA

API version: 0.1
Website: https://openfga.dev
Documentation: https://openfga.dev/docs
Support: https://discord.gg/8naAwJfWN6
License: [Apache-2.0](https://github.com/openfga/python-sdk/blob/main/LICENSE)

NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT.
"""

from setuptools import find_packages, setup

NAME = "openfga-streamed-list-objects-example"
VERSION = "0.0.1"
REQUIRES = [""]

setup(
name=NAME,
version=VERSION,
description="An example of using the OpenFGA Python SDK with the Streamed List Objects endpoint.",
author="OpenFGA (https://openfga.dev)",
author_email="[email protected]",
url="https://github.com/openfga/python-sdk",
python_requires=">={{pythonMinimumRuntime}}",
packages=find_packages(exclude=["test", "tests"]),
include_package_data=True,
license="Apache-2.0",
)
Loading

0 comments on commit be9320e

Please sign in to comment.