-
Notifications
You must be signed in to change notification settings - Fork 34
feat: add execute_api_request method for calling arbitrary endpoints #252
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
Open
kcbiradar
wants to merge
21
commits into
openfga:main
Choose a base branch
from
kcbiradar:feat/add-raw-request-method
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+2,514
−3,244
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
f4dec14
feat: add raw_request method
kcbiradar 3c2e66a
update: handling double-JSON-encodes
kcbiradar 3f85dfa
addRawRequestMethod: fix testcases & remove trailing spaces
kcbiradar e9c47d9
Merge branch 'main' into feat/add-raw-request-method
SoulPancake b6b72aa
addRawRequestMethod: update-changes-requested
kcbiradar 6188d9e
updates
kcbiradar a34de52
updated-requested-changes
kcbiradar 5cc73ea
update-suggested-changes
kcbiradar 3eacc7b
Merge branch 'main' into feat/add-raw-request-method
SoulPancake 07e354d
Merge branch 'main' into feat/add-raw-request-method
SoulPancake 256d6e1
feat: refactor method to execute api method
SoulPancake 1e71e79
fix: tetss and refactor
SoulPancake 1ba7b3d
feat: example for existing endpoints
SoulPancake 51b92ae
fix: lint
SoulPancake 4569a6a
fix: cleanup
SoulPancake b79b235
feat: execute api request api layer refactor
SoulPancake 9946cf8
fix: changelog and ruff lint
SoulPancake 826f1d9
feat: remove unused import
SoulPancake 88b86fb
Merge branch 'main' into feat/add-raw-request-method
SoulPancake 0535b1c
feat: address comments
SoulPancake 785af00
fix: streaming stuff
SoulPancake File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
273 changes: 273 additions & 0 deletions
273
example/execute-api-request/execute_api_request_example.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,273 @@ | ||
| # ruff: noqa: E402 | ||
|
|
||
| """ | ||
| execute_api_request example — calls real OpenFGA endpoints and compares | ||
| the results with the regular SDK methods to verify correctness. | ||
|
|
||
| Requires a running OpenFGA server (default: http://localhost:8080). | ||
| export FGA_API_URL=http://localhost:8080 # optional, this is the default | ||
| python3 execute_api_request_example.py | ||
| """ | ||
|
|
||
| import asyncio | ||
| import os | ||
| import sys | ||
|
|
||
|
|
||
| sdk_path = os.path.realpath(os.path.join(os.path.abspath(__file__), "..", "..", "..")) | ||
| sys.path.insert(0, sdk_path) | ||
|
|
||
| from openfga_sdk import ( | ||
| ClientConfiguration, | ||
| CreateStoreRequest, | ||
| Metadata, | ||
| ObjectRelation, | ||
| OpenFgaClient, | ||
| RelationMetadata, | ||
| RelationReference, | ||
| TypeDefinition, | ||
| Userset, | ||
| Usersets, | ||
| WriteAuthorizationModelRequest, | ||
| ) | ||
| from openfga_sdk.client.models import ( | ||
| ClientCheckRequest, | ||
| ClientTuple, | ||
| ClientWriteRequest, | ||
| ) | ||
| from openfga_sdk.credentials import Credentials | ||
|
|
||
|
|
||
| async def main(): | ||
| api_url = os.getenv("FGA_API_URL", "http://localhost:8080") | ||
|
|
||
| configuration = ClientConfiguration( | ||
| api_url=api_url, | ||
| credentials=Credentials(), | ||
| ) | ||
|
|
||
| async with OpenFgaClient(configuration) as fga_client: | ||
| print("=== Setup ===") | ||
|
|
||
| # Create a test store via the SDK | ||
| store = await fga_client.create_store( | ||
| CreateStoreRequest(name="execute_api_request_test") | ||
| ) | ||
| fga_client.set_store_id(store.id) | ||
| print(f"Created store: {store.id}") | ||
|
|
||
| # Write an authorization model | ||
| model_resp = await fga_client.write_authorization_model( | ||
| WriteAuthorizationModelRequest( | ||
| schema_version="1.1", | ||
| type_definitions=[ | ||
| TypeDefinition(type="user"), | ||
| TypeDefinition( | ||
| type="document", | ||
| relations=dict( | ||
| writer=Userset(this=dict()), | ||
| viewer=Userset( | ||
| union=Usersets( | ||
| child=[ | ||
| Userset(this=dict()), | ||
| Userset( | ||
| computed_userset=ObjectRelation( | ||
| object="", relation="writer" | ||
| ) | ||
| ), | ||
| ] | ||
| ) | ||
| ), | ||
| ), | ||
| metadata=Metadata( | ||
| relations=dict( | ||
| writer=RelationMetadata( | ||
| directly_related_user_types=[ | ||
| RelationReference(type="user"), | ||
| ] | ||
| ), | ||
| viewer=RelationMetadata( | ||
| directly_related_user_types=[ | ||
| RelationReference(type="user"), | ||
| ] | ||
| ), | ||
| ) | ||
| ), | ||
| ), | ||
| ], | ||
| ) | ||
| ) | ||
| auth_model_id = model_resp.authorization_model_id | ||
| fga_client.set_authorization_model_id(auth_model_id) | ||
| print(f"Created model: {auth_model_id}") | ||
|
|
||
| # Write a tuple | ||
| await fga_client.write( | ||
| ClientWriteRequest( | ||
| writes=[ | ||
| ClientTuple( | ||
| user="user:anne", | ||
| relation="writer", | ||
| object="document:roadmap", | ||
| ), | ||
| ] | ||
| ) | ||
| ) | ||
| print("Wrote tuple: user:anne → writer → document:roadmap") | ||
|
|
||
| print("\n=== execute_api_request ===\n") | ||
|
|
||
| print("1. ListStores (GET /stores)") | ||
| raw = await fga_client.execute_api_request( | ||
| operation_name="ListStores", | ||
| method="GET", | ||
| path="/stores", | ||
| query_params={"page_size": 100}, | ||
| ) | ||
| sdk = await fga_client.list_stores() | ||
| body = raw.json() | ||
| assert raw.status == 200, f"Expected 200, got {raw.status}" | ||
| assert "stores" in body | ||
| assert len(body["stores"]) == len(sdk.stores), ( | ||
| f"Count mismatch: {len(body['stores'])} vs {len(sdk.stores)}" | ||
| ) | ||
| print(f" ✅ {len(body['stores'])} stores (status {raw.status})") | ||
|
|
||
| print("2. GetStore (GET /stores/{store_id})") | ||
| raw = await fga_client.execute_api_request( | ||
| operation_name="GetStore", | ||
| method="GET", | ||
| path="/stores/{store_id}", | ||
| path_params={"store_id": store.id}, | ||
| ) | ||
| sdk = await fga_client.get_store() | ||
| body = raw.json() | ||
| assert raw.status == 200 | ||
| assert body["id"] == sdk.id | ||
| assert body["name"] == sdk.name | ||
| print(f" ✅ id={body['id']}, name={body['name']}") | ||
|
|
||
| print( | ||
| "3. ReadAuthorizationModels (GET /stores/{store_id}/authorization-models)" | ||
| ) | ||
| raw = await fga_client.execute_api_request( | ||
| operation_name="ReadAuthorizationModels", | ||
| method="GET", | ||
| path="/stores/{store_id}/authorization-models", | ||
| path_params={"store_id": store.id}, | ||
| ) | ||
| sdk = await fga_client.read_authorization_models() | ||
| body = raw.json() | ||
| assert raw.status == 200 | ||
| assert len(body["authorization_models"]) == len(sdk.authorization_models) | ||
| print(f" ✅ {len(body['authorization_models'])} models") | ||
|
|
||
| print("4. Check (POST /stores/{store_id}/check)") | ||
| raw = await fga_client.execute_api_request( | ||
| operation_name="Check", | ||
| method="POST", | ||
| path="/stores/{store_id}/check", | ||
| path_params={"store_id": store.id}, | ||
| body={ | ||
| "tuple_key": { | ||
| "user": "user:anne", | ||
| "relation": "viewer", | ||
| "object": "document:roadmap", | ||
| }, | ||
| "authorization_model_id": auth_model_id, | ||
| }, | ||
| ) | ||
| sdk = await fga_client.check( | ||
| ClientCheckRequest( | ||
| user="user:anne", | ||
| relation="viewer", | ||
| object="document:roadmap", | ||
| ) | ||
| ) | ||
| body = raw.json() | ||
| assert raw.status == 200 | ||
| assert body["allowed"] == sdk.allowed | ||
| print(f" ✅ allowed={body['allowed']}") | ||
|
|
||
| print("5. Read (POST /stores/{store_id}/read)") | ||
| raw = await fga_client.execute_api_request( | ||
| operation_name="Read", | ||
| method="POST", | ||
| path="/stores/{store_id}/read", | ||
| path_params={"store_id": store.id}, | ||
| body={ | ||
| "tuple_key": { | ||
| "user": "user:anne", | ||
| "object": "document:", | ||
| }, | ||
| }, | ||
| ) | ||
| body = raw.json() | ||
| assert raw.status == 200 | ||
| assert "tuples" in body | ||
| assert len(body["tuples"]) >= 1 | ||
| print(f" ✅ {len(body['tuples'])} tuples returned") | ||
|
|
||
| print("6. CreateStore (POST /stores)") | ||
| raw = await fga_client.execute_api_request( | ||
| operation_name="CreateStore", | ||
| method="POST", | ||
| path="/stores", | ||
| body={"name": "raw_request_test_store"}, | ||
| ) | ||
| body = raw.json() | ||
| assert raw.status == 201, f"Expected 201, got {raw.status}" | ||
| assert "id" in body | ||
| new_store_id = body["id"] | ||
| print(f" ✅ created store: {new_store_id}") | ||
|
|
||
| print("7. DeleteStore (DELETE /stores/{store_id})") | ||
| raw = await fga_client.execute_api_request( | ||
| operation_name="DeleteStore", | ||
| method="DELETE", | ||
| path="/stores/{store_id}", | ||
| path_params={"store_id": new_store_id}, | ||
| ) | ||
| assert raw.status == 204, f"Expected 204, got {raw.status}" | ||
| print(f" ✅ deleted store: {new_store_id} (status 204 No Content)") | ||
|
|
||
| print("8. Custom headers (GET /stores/{store_id})") | ||
| raw = await fga_client.execute_api_request( | ||
| operation_name="GetStoreWithHeaders", | ||
| method="GET", | ||
| path="/stores/{store_id}", | ||
| path_params={"store_id": store.id}, | ||
| headers={"X-Custom-Header": "test-value"}, | ||
| ) | ||
| assert raw.status == 200 | ||
| print(f" ✅ custom headers accepted (status {raw.status})") | ||
|
|
||
| print("9. StreamedListObjects (POST /stores/{store_id}/streamed-list-objects)") | ||
| chunks = [] | ||
| async for chunk in fga_client.execute_streamed_api_request( | ||
| operation_name="StreamedListObjects", | ||
| method="POST", | ||
| path="/stores/{store_id}/streamed-list-objects", | ||
| path_params={"store_id": store.id}, | ||
| body={ | ||
| "type": "document", | ||
| "relation": "viewer", | ||
| "user": "user:anne", | ||
| "authorization_model_id": auth_model_id, | ||
| }, | ||
| ): | ||
| chunks.append(chunk) | ||
| assert len(chunks) >= 1, f"Expected at least 1 chunk, got {len(chunks)}" | ||
| # Each chunk has the shape {"result": {"object": "..."}} or {"error": {...}} | ||
| objects = [c["result"]["object"] for c in chunks if "result" in c] | ||
| assert "document:roadmap" in objects, f"Expected document:roadmap in {objects}" | ||
| print(f" ✅ {len(chunks)} chunks, objects={objects}") | ||
|
|
||
| print("\n=== Cleanup ===") | ||
| await fga_client.delete_store() | ||
| print(f"Deleted test store: {store.id}") | ||
|
|
||
| print("\nAll execute_api_request examples completed successfully.\n") | ||
|
|
||
|
|
||
| asyncio.run(main()) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.